block(三)揭开神秘面纱(上)
block到底是什么
我们使用clang的rewrite-objc命令来获取转码后的代码。
1、block的底层实现
我们来看看最简单的一个block:
[caption id="attachment_1981" align="alignnone" width="733"]
图一[/caption]
这个block仅仅打印栈变量i和j的值,其被clang转码为:
[caption id="attachment_1984" align="alignnone" width="745"]
图二[/caption]
首先是一个结构体__main_block_impl_0(从图二中的最后一行可以看到,block是一个指向__main_block_impl_0的指针,初始化后被类型强转为函数指针),其中包含的__block_impl是一个公共实现(学过c语言的同学都知道,__main_block_impl_0的这种写法表示其可以被类型强转为__block_impl类型):
|
1
2
3
4
5
6
|
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr;}; |
isa指针说明block可以成为一个objc 对象。
__main_block_impl_0的意思是main函数中的第0个block的implementation,这就是这个block的主体了。
这个结构体的构造函数的参数:
- block实际执行代码所在的函数的指针,当block真正被执行时,实际上是调用了这个函数,其命名也是类似的方式。
- block的描述结构体,注意这个结构体声明结束时就创建了一个唯一的desc,这个desc包含了block的大小,以及复制和析构block时需要额外调用的函数。
- 接下来是block所引用到的变量们
- 最后是一个标记值,内部实现需要用到的。(我用计算器看了一下,570425344这个值等于1<<29,即BLOCK_HAS_DESCRIPTOR这个枚举值)
所以,我们可以看到:
- 为什么上一篇我们说j已经不是原来的j了,因为j是作为参数传入了block的构造函数,进行了值复制。
- 带有__block标记的变量会被取地址来传入构造函数,为修改其值奠定了基础
接下来是block执行函数__main_block_func_0:
其唯一的参数是__main_block_impl_0的指针,我们看到printf语句的数据来源都取自__cself这个指针,比较有意思的是i的取值方式(带有__block标记的变量i被转码为一个结构体),先取__forward指针,再取i,这为将i复制到堆中奠定了基础。
再下来是预定义好的两个复制/释放辅助函数,其作用后面会讲到。
最后是block的描述信息结构体 __main_block_desc_0,其包含block的内存占用长度,已经复制/释放辅助函数的指针,其声明结束时,就创建了一个名为__main_block_desc_0_DATA的结构体,我们看它构造时传入的值,这个DATA结构体的作用就一目了然了:
长度用sizeof计算,辅助函数的指针分别为上面预定义的两个辅助函数。
注意,如果这个block没有使用到需要在block复制时进行copy/retian的变量,那么desc中不会有辅助函数
至此,一个block所有的部件我们都看齐全了,一个主体,一个真正的执行代码函数,一个描述信息(可能包含两个辅助函数)。
2、构造一个block
我们进入main函数:
图一中的第三行(block的声明),在图二中,转化为一个函数指针的声明,并且都没有被赋予初始值。
而图一中的最后一行(创建一个block),在图二中,成为了对__main_block_impl_0的构造函数的调用,传入的参数的意义上面我们已经讲过了。
所以构造一个block就是创建了__main_block_impl_0 这个c++类的实例。
3、调用一个block
调用一个block的写法很简单,与调用c语言函数的语法一样:
|
1
|
blk(); |
其转码后的语句:
|
1
|
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk); |
将blk这个函数指针类型强转为__block_impl类型,然后取其执行函数指针,然后将此指针类型强转为返回void*并接收一个__block_impl*的函数指针,最后调用这个函数,传入强转为__block_impl*类型的blk,
即调用了前述的函数__main_block_func_0
4、objective-c类成员函数中的block
源码如下:
|
1
2
3
4
5
6
|
- (void)of1{ OBJ1* oj = self; void (^oblk)(void) = ^{ printf("%d\n", oj.oi);}; Block_copy(oblk);} |
这里我故意将self赋值给oj这个变量,是为了验证前一章提出的一个结论:无法通过简单的间接引用self来防止retain循环,要避免循环,我们需要__block标记(多谢楼下网友的提醒)
转码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct __OBJ1__of1_block_impl_0 { struct __block_impl impl; struct __OBJ1__of1_block_desc_0* Desc; OBJ1 *oj; __OBJ1__of1_block_impl_0(void *fp, struct __OBJ1__of1_block_desc_0 *desc, OBJ1 *_oj, int flags=0) : oj(_oj) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; }};static void __OBJ1__of1_block_func_0(struct __OBJ1__of1_block_impl_0 *__cself) { OBJ1 *oj = __cself->oj; // bound by copy printf("%d\n", ((int (*)(id, SEL))(void *)objc_msgSend)((id)oj, sel_registerName("oi")));} |
objc方法中的block与c中的block并无太多差别,只是一些标记值可能不同,为了标记其是objc方法中的blcok。
注意其构造函数的参数:OBJ1 *_oj
这个_oj在block复制到heap时,会被retain,而_oj与self根本就是相等的,所以,最终retain的就是self,所以如果当前实例持有了这个block,retain循环就形成了。
而一旦为其增加了__block标记:
|
1
2
3
4
5
|
- (void)of1{ __block OBJ1 *bSelf = self; ^{ printf("%d", bSelf.oi); };} |
其转码则变为:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
//增加了如下行struct __Block_byref_bSelf_0 { void *__isa;__Block_byref_bSelf_0 *__forwarding; int __flags; int __size; void (*__Block_byref_id_object_copy)(void*, void*); void (*__Block_byref_id_object_dispose)(void*); OBJ1 *bSelf;};static void __Block_byref_id_object_copy_131(void *dst, void *src) { _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);}static void __Block_byref_id_object_dispose_131(void *src) { _Block_object_dispose(*(void * *) ((char*)src + 40), 131);}//声明处变为 __block __Block_byref_bSelf_0 bSelf = {(void*)0,(__Block_byref_bSelf_0 *)&bSelf, 33554432, sizeof(__Block_byref_bSelf_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, self}; |
clang为我们的bSelf结构体创建了自己的copy/dispose辅助函数,33554432(即1<<25 BLOCK_HAS_COPY_DISPOSE)这个值告诉系统,我们的bSelf结构体具有copy/dispose辅助函数。
而131这个参数(二进制1000 0011,即BLOCK_FIELD_IS_OBJECT (3) |BLOCK_BYREF_CALLER(128))
中的BLOCK_BYREF_CALLER在内部实现中告诉系统不要进行retain或者copy,
也就是说,在 __block bSelf 被复制至heap上时,系统会发现有辅助函数,而辅助函数调用后,并不retain或者copy 其结构体内的bSelf。
这样就避免了循环retain。
小结:
当我们创建一个block,并调用之,编译器为我们做的事情如下:
1.创建block所有的部件代码:一个主体,一个真正的执行代码函数,一个描述信息(可能包含两个辅助函数)。
2.将我们的创建代码转码为block_impl的构造语句。
3.将我们的执行语句转码为对block的执行函数的调用。
block(三)揭开神秘面纱(上)的更多相关文章
- block(三)揭开神秘面纱(上)-b
block到底是什么 我们使用clang的rewrite-objc命令来获取转码后的代码. 1.block的底层实现 我们来看看最简单的一个block: [caption id="attac ...
- block(四)揭开神秘面纱(下)
看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开): http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntim ...
- block(四)揭开神秘面纱(下)-b
看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开): http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntim ...
- ASP.NET Identity系列01,揭开神秘面纱
早在2005年的时候,微软随着ASP.NET 推出了membership机制,十年磨一剑,如今的ASP.NET Identity是否足够强大,一起来体会. 在VS2013下新建项目,选择"A ...
- 第二波分析:德国是2018世界杯夺冠最大热门? Python数据分析来揭开神秘面纱… (附源代码)
2018年,世界杯小组赛已经在如火如荼的进行中.在上篇文章的基础上[2018世界杯:用Python分析热门夺冠球队],我们继续分析世界杯32强的实力情况,以期能够更进一步分析本次世界杯的夺冠热门球队. ...
- 揭开自然拼读法(Phonics)的神秘面纱
揭开自然拼读法(Phonics)的神秘面纱 自然拼读法 (Phonics),是指看到一个单词,就可以根据英文字母在单词里的发音规律把这个单词读出来的一种方法.即从“字母发音-字母组合发音-单词-简单 ...
- 揭开GrowingIO无埋点的神秘面纱
揭开GrowingIO无埋点的神秘面纱 早在研究用户行为分析的时候,就发现国内的GrowingIO在宣传无埋点技术,最近正好抽出时间来研究一下所谓的无埋点到底是什么样的. 我分六部分来分析一下无埋 ...
- 带你揭开ATM的神秘面纱
相信大家都用过ATM取过money吧,但是有多少人真正是了解ATM的呢?相信除了ATM从业者外了解的人寥寥无几吧,鄙人作为一个从事ATM软件开发的伪专业人士就站在我的角度为大家揭开ATM的神秘面纱吧. ...
- 揭开Future的神秘面纱——任务执行
前言 此文承接之前的博文 解开Future的神秘面纱之取消任务 补充一些任务执行的一些细节,并从全局介绍程序的运行情况. 系列目录 揭开Future的神秘面纱——任务取消 揭开Future的神秘面纱— ...
随机推荐
- STL string常用操作指令
s.insert(pos,args); 在pos之前插入args指定的字符.pos可以是一个下标或一个迭代器.接受下标的版本返回一个指向s的引用;接受迭代器的版本返回指向第一个插入字符的迭代器. s. ...
- ECMAScript 5/6/7兼容性速查表
http://kangax.github.io/compat-table/es5/ 秒查ECMAScript在各大浏览器的兼容性,点击右上角按钮可以“在5/6/7/非标”之间切换.做JavaScrip ...
- Linux Shell逻辑运算符和表达式详解
Shell 逻辑运算符涉及以下几种类型,只要适当选择,可以解决我们很多复杂的判断,达到事半功倍效果. 一.逻辑判断1.关于文件与目录的逻辑判断-f 常用.判断『文件』是否为普通文件,比如: if [ ...
- 【转】VC中TRACE
原文网址:http://blog.csdn.net/yangsen2016/article/details/1636996 TRACE宏对于VC下程序调试来说是很有用的东西,有着类似printf的功能 ...
- 15个提高编程技巧的JavaScript工具
原文地址:http://www.imooc.com/wenda/detail/243523 JavaScript脚本库是一个预先用JavaScript语言写好的库,它方便了我们开发基于JavaScri ...
- Manacher算法----最长回文子串
题目描述 给定一个字符串,求它的最长回文子串的长度. 分析与解法 最容易想到的办法是枚举所有的子串,分别判断其是否为回文.这个思路初看起来是正确的,但却做了很多无用功,如果一个长的子串包含另一个短一些 ...
- ssh秘钥交换详解与实现 diffie-hellman-group-exchange-sha
ssh的DH秘钥交换是一套复合几种算法的秘钥交换算法.在RFC4419中称为diffie-hellman-groupX-exchange-shaX 的算法(也有另一种单纯的 rsaX-shaX 交换算 ...
- C# - 创建List属性的简单方法
不用担心List没有创建问题. private ObservableCollection<EquipmentItem> _optionalCollection; public Observ ...
- C# - List操作- 去掉重复
ChangeList里面会有重复的数据,这时可以这样去掉重复的item // Remove duplicated info var dup = ChangeList.Where(item => ...
- [Matlab] Attempt to execute SCRIPT *** as a function
Attempt to execute SCRIPT *** as a function 问题: 在运行MATLAB程序的时候,出现如题的报错. 原因: 在系统中,现有的.m文件有的与***函数重名,所 ...