当前位置: 首页 > news >正文

【iOS】—— 浅看block源码

block

文章目录

  • block
    • 如何通过终端clang生成源码cpp文件
    • block实质
    • 截获自动变量
    • 全局变量和静态变量的截获
    • __block说明符
    • iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题

如何通过终端clang生成源码cpp文件

之前在学习block中学习的比较浅,只看了oc高级编程书上有的源码,学习的比较浅,这节来对之前的作以补充。
首先来看看怎么将oc文件生成cpp的源码文件:
在这里插入图片描述

将项目大文件里的二级文件,打开终端 cd 并将此文件拖入:
然后再输入:

clang -rewrite-objc main.m

在这里插入图片描述

回车打开之前的项目文件,即可看到生成了一个cpp文件,打开就是oc的c++源码。
打开后发现有60000多行,很抽象,关键部分看最前面和最后面即可。

block实质

我们现在时文件中写一个最简单的block,看看源码是什么:

int main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...void (^blk)(void) = ^{printf("Block\n");};blk();}return 0;
}

源码:

//经过clang转换后的C++代码
struct __block_impl {void *isa;//指向所属类的指针int Flags;//标志性参数,暂时没用到所以默认为0int Reserved;//今后版本升级所需的区域大小。void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};struct __main_block_impl_0 {struct __block_impl impl;//上面点1中的结构体的变量struct __main_block_desc_0* Desc;//上面点2中的结构体的指针 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself {printf("Block\n");
}static struct __main_block_desc_0 {size_t reserved;  //今后版本升级所需区域的大小(一般填0)size_t Block_size;   //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}

我们来分开说一下每部分的意义:
1.第一个结构体

struct __block_impl {void *isa;//指向所属类的指针int Flags;//标志性参数,暂时没用到所以默认为0int Reserved;//今后版本升级所需的区域大小。void *FuncPtr;//函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。
};
  • 结构体的名称:impl即implementation的缩写,换句话说这一部分是block的实现部分结构体。
  • void *isa:声明一个不确定类型的指针,用于保存Block结构体实例。
  • int Flags:标识符。
  • int Reserved:今后版本升级所需的区域大小。
  • void *FuncPtr:函数指针,指向实际执行的函数,也就是block中花括号里面的代码内容。

2.在介绍struct __main_block_impl_0结构体之前,先介绍一下这个结构体里的static struct __main_block_desc_0结构体
这个结构体在上面一坨源码的最底下:

static struct __main_block_desc_0 {size_t reserved;  //今后版本升级所需区域的大小(一般填0)size_t Block_size;   //Block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  • 第一个成员变量指的是今后版本升级所需区域的大小(一般填0)。
  • 第二个成员变量是Block的大小。
    __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};:
    1⃣️.这就是和我们平时用结构体一样,在定义完最后写一个结构体实例变量,变量名就是__main_block_desc_0_DATA。
    2⃣️.其中reserved为0,Block_size是sizeof(struct __main_block_impl_0)。

3.接下来看struct __main_block_impl_0结构体

struct __main_block_impl_0 {struct __block_impl impl;//上面点1中的结构体的变量struct __main_block_desc_0* Desc;//上面点2中的结构体的指针__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
  • 第一个成员变量是impl,也就是上面点1中的结构体的变量。
  • 第二个成员变量是Desc指针,就是上面点2中的结构体的指针。
  • 剩下的代码是:初始化含有这些结构体的__main_block_impl_0结构体的构造函数,给结构体里的元素赋值。

4.然后是static void __main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself {printf("Block\n");
}

这个就比较简单了,Blcok执行的实际代码块。也是点3中fp指针指向的函数。括号中的参数__cself是相当于OC语言版的self,代表的是Block本身。

5.最后来看main函数

int main(int argc, const char * argv[]) {void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);return 0;
}

这两句代码不长但是比较恶心,我们先来看第一句,把强制类型转换去一下试试:

//加了个临时变量temp,看着更方便点
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &temp;

该源代码将__main_block_impl_0结构体类型的自动变量,也就是栈上生成的__main_block_impl_0结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk。
第二行代码就是相当于源代码中的blk(),即使用该Block部分。去掉转换部分就是:

(*blk->impl.FuncPtr)(blk);

这是使用函数指针调用函数。由Block语法转换的__main_block_impl_0函数的指针被赋值成员变量FunPtr中。

截获自动变量

int main(int argc, const char * argv[]) {int dmy = 256;int val = 10;const char  *fmt = "val = %d\n";void (^blk)(void) = ^{printf(fmt, val);};blk();return 0;
}

转化后的代码:

//经过clang转换后的C++代码
struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself {const char *fmt = __cself->fmt;int val = __cself->val; printf(fmt, val);
}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};int main(int argc, const char * argv[]) {int dmy = 256;int val = 10;const char *fmt = "val = %d\n";void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));return 0;
}

与上次不同的是在Block语法表达式中使用的自动变量被当作成员变量追加到了__main_block_impl_0结构体中:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt;int val;
}

初始化该结构体实例的构造函数也发生了变化

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) 

初始化时自动变量fmt和val进行了赋值操作:

	impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;fmt = "val = %d\n";val = 10;

在转换后的代码中,截获到__main_block_impl_0结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义,所以之后即使改变自动变量的值也不会对Block语法中的内容有所变化。

总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例(即Block自身中)。
总结一下就是说:
调用__main_block_impl_0的构造函数,构造函数正常有两个参数,func_0和desc_0,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0时,直接通过__cself(相当于self,其传递的参数也是其本身)的a、b(此时的a,b就是成员变量中的,因为上面的构造函数已经赋值了)

全局变量和静态变量的截获

我们以以下代码为例:

int global_val = 10; // 全局变量
static int static_global_val = 20; // 静态全局变量int main(int argc, const char * argv[]) {@autoreleasepool {static int static_val = 30; // 静态局部变量void (^myLocalBlock)(void) = ^{global_val *= 1;static_global_val *= 2;static_val *= 3;printf("static_val = %d, static_global_val = %d, global_val = %d\n",static_val, static_global_val, global_val);};myLocalBlock();}return 0;
}

源码:

struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};int global_val = 10;
static int static_global_val = 20;struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *static_val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *static_val = __cself->static_val; // bound by copyglobal_val *= 1;static_global_val *= 2;(*static_val) *= 3;printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_val);}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; static int static_val = 30;void (*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);}return 0;
}

我们主要来看下这段代码:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {int *static_val = __cself->static_val; // bound by copyglobal_val *= 1;static_global_val *= 2;(*static_val) *= 3;printf("static_val = %d, static_global_val = %d, global_val = %d\n",(*static_val), static_global_val, global_val);}

我们发现只有static_val是从__cself中获取的值

再来看看上面的一段代码:

struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;int *static_val;__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_val, int flags=0) : static_val(_static_val) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};
  • 我们发现全局变量,静态全局变量,我们的Block都没有用结构体去特地保存它
  • 只有对于我们的静态局部变量会来保存,但这里要注意,我们使用的不是int static_val,而是int *static_val
  • 也就是说我们使用一个指针来保存的静态局部变量
  • 它会直接保存该变量的地址,之后的操作也是直接对该值本身进行操作,而不是向之前截获的那些变量,等于是重新开辟空间进行保存

产生这个问题的原因:
原因在于,我们的静态变量是存在数据区的,在程序结束前它其实一直都会存在,之所以会被称为局部,只是说出了作用域无法调用到它了,并不是说这块数据不存在了。因此我们只要自己准备好一个指针,保证出了作用域依然能调用到他就行;而对于自动变量,它们真正的问题在于一但出了作用域,直接被释放了,所以要在结构体里开辟空间重新存放,进行值传递
在这里插入图片描述

__block说明符

来看书上的例子:

int main(int argc, const char * argv[]) {__block int val = 10;void (^blk)(void) = ^{val = 1;printf("val = %d\n", val);};blk();return 0;
}

转换后的源码:

struct __Block_byref_val_0 {void *__isa;__Block_byref_val_0 *__forwarding;int __flags;int __size;int val;
};struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;__Block_byref_val_0 *val; // by ref__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {impl.isa = &_NSConcreteStackBlock;impl.Flags = flags;impl.FuncPtr = fp;Desc = desc;}
};static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_val_0 *val = __cself->val; (val->__forwarding->val) = 1;printf("val = %d\n", (val->__forwarding->val));
}static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, BLOCK_FIELD_IS_BYREF);
}static void __main_block_dispose_0(struct __main_block_impl_0*src {_Block_object_dispose((void*)src->val, BLOCK_FIELD_IS_BYREF);
}static struct __main_block_desc_0 {size_t reserved;size_t Block_size;void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0
};int main(int argc, const char * argv[]) {__Block_byref_val_0 val = {0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);return 0;
}

和没加修饰符的比较来说,主要有两处不同:

struct __Block_byref_val_0 {void *__isa;__Block_byref_val_0 *__forwarding;int __flags;int __size;int val;
};

在第一个结构体中,我们发现多了一个__Block_byref_val_0 *__forwarding,这个相当于指向该结构体本身的一个指针。

第二个地方:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {__Block_byref_val_0 *val = __cself->val; (val->__forwarding->val) = 1;printf("val = %d\n", (val->__forwarding->val));
}

在这里我们发现打印的并不是__Block_byref_val_0 *val而是(val->__forwarding->val)。

最后来看一下主函数:

int main(int argc, const char * argv[]) {__Block_byref_val_0 val = {0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &val, 0x22000000);return 0;
}

看下其中赋值部分:

__Block_byref_val_0 val = {0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 10};

这个__block变量val变为了__Block_byref_val_0结构体变量。通过调用 static void __main_block_func_0函数(通过__Block_byref_val_0结构体成员变量__forwarding访问成员变量val),将10赋给val。

iOS开发“强弱共舞”——weak和strong配套使用解决block循环引用问题

__weak是为了解决循环引用

如果一个对象A持有了一个block,同时block内又持有了对象A,为了解决循环引用我们要在用__weak修饰完对象A后再去持有它,这样就解决了循环引用。

__strong可以防止block持有的对象提前释放

我们用GCD延迟方法打印self的信息:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{[self dismissViewControllerAnimated:YES completion:nil];__weak typeof(self) weakSelf = self;void (^Block) (void) = ^{dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", weakSelf);});};Block();
}

输出结果:

在这里插入图片描述
点击屏幕,当前控制器消失,同时被销毁掉,5秒后打印的weakSelf就是一个(null),而我们如果在block内使用__strong后就能保证再打印完strongSelf之后再释放当前控制器。

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{[self dismissViewControllerAnimated:YES completion:nil];__weak typeof(self) weakSelf = self;void (^Block) (void) = ^{__strong typeof(self) strongSelf = weakSelf;dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{NSLog(@"%@", strongSelf);});};Block();
}

请添加图片描述

http://www.lryc.cn/news/33452.html

相关文章:

  • I.MX6ULL_Linux_系统篇(23) busybox文件系统构建
  • shpjs将.zip文件转成geoJson
  • eBay是不是一定要养号?是的
  • 宝塔(二):升级JDK版本
  • 【LeetCode】螺旋矩阵 [M](数组)
  • 实验二:动态规划
  • 华为机试 HJ27 查找兄弟单词
  • 佩戴舒适的蓝牙耳机有哪些?佩戴舒适的蓝牙耳机推荐
  • ESXI主机安装Zabbix 6.2
  • 【Linux 网络编程1】使用UDP/TCP编写套接字,多进程/多线程版本的TCP编写的套接字,将套接字封装
  • 滑动窗口求最大和最小
  • 跟ChatGPT学量化,菜鸟炒股也不怕
  • 扬帆优配|2600亿新能源巨头狂飙!外资唱多中国:再涨15%
  • ChatGPT技术与商业模式及产业发展布局方案
  • CIMCAI port ai shipping ai artificial intelligence smart port
  • 《数据解构》HashMap源码解读
  • Databend 开源周报 第 83 期
  • Spring | 基础
  • windows7安装sql server 2000安装步骤 及安装过程中遇到的问题和解决方式
  • Python 开发-批量 FofaSRC 提取POC 验证
  • Linux系统中部署软件
  • PHP常用框架介绍与比较
  • Umi + React + Ant Design Pro 项目实践(一)—— 项目搭建
  • MySQL知识点总结(1)
  • day45第九章动态规划(二刷)
  • 第十四届蓝桥杯第三期模拟赛原题与详解
  • client打包升级
  • Blazor_WASM之3:项目结构
  • OperWrt 包管理系统02
  • 人人都学会APP开发 提高就业竞争力 简单实用APP应用 安卓浏览器APP 企业内部通用APP制作 制造业通用APP