当前位置:首页>优优资讯 > 软件教程 > 电脑软件教程 > block介绍:揭开神秘面纱(上)

block介绍:揭开神秘面纱(上)

作者:本站整理 时间:2015-09-16


  block到底是什么
 
  我们使用clang的rewrite-objc命令来获取转码后的代码。
 
  1、block的底层实现
 
  这个block仅仅打印栈变量i和j的值,其被clang转码为:
 

 
  首先是一个结构体__main_block_impl_0(从图二中的最后一行可以看到,block是一个指向__main_block_impl_0的指针,初始化后被类型强转为函数指针),其中包含的__block_impl是一个公共实现(学过c语言的同学都知道,__main_block_impl_0的这种写法表示其可以被类型强转为__block_impl类型):
 
  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语言函数的语法一样:
 
  blk();
 
  其转码后的语句:
 
  ((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
 
  源码如下:
 
  - (void)of1
  {
  OBJ1* oj = self;
  void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};
  Block_copy(oblk);
  }
 
  这里我故意将self赋值给oj这个变量,是为了验证前一章提出的一个结论:无法通过简单的间接引用self来防止retain循环,要避免循环,我们需要__block标记(多谢楼下网友的提醒)
 
  转码如下:
 
  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标记:
 
  - (void)of1
  {
  __block OBJ1 *bSelf = self;
  ^{ printf("%d", bSelf.oi); };
  }其转码则变为:
  //增加了如下行
  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的执行函数的调用。
 
  下一篇我们将剖析runtime.c的源码,并理解block的堆栈转换。
 

相关文章

相关推荐

最新攻略

用户评论

(已有0条评论)
表情
注:您的评论需要经过审核才能显示哦,请文明发言!
还没有评论,快来抢沙发吧!