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的主体了。

这个结构体的构造函数的参数:

  1. block实际执行代码所在的函数的指针,当block真正被执行时,实际上是调用了这个函数,其命名也是类似的方式。
  2. block的描述结构体,注意这个结构体声明结束时就创建了一个唯一的desc,这个desc包含了block的大小,以及复制和析构block时需要额外调用的函数。
  3. 接下来是block所引用到的变量们
  4. 最后是一个标记值,内部实现需要用到的。(我用计算器看了一下,570425344这个值等于1<<29,即BLOCK_HAS_DESCRIPTOR这个枚举值)

所以,我们可以看到:

  1. 为什么上一篇我们说j已经不是原来的j了,因为j是作为参数传入了block的构造函数,进行了值复制。
  2. 带有__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(三)揭开神秘面纱(上)的更多相关文章

  1. block(三)揭开神秘面纱(上)-b

    block到底是什么 我们使用clang的rewrite-objc命令来获取转码后的代码. 1.block的底层实现 我们来看看最简单的一个block: [caption id="attac ...

  2. block(四)揭开神秘面纱(下)

    看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开): http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntim ...

  3. block(四)揭开神秘面纱(下)-b

    看此篇时,请大家同时打开两个网址(或者下载它们到本地然后打开): http://llvm.org/svn/llvm-project/compiler-rt/trunk/lib/BlocksRuntim ...

  4. ASP.NET Identity系列01,揭开神秘面纱

    早在2005年的时候,微软随着ASP.NET 推出了membership机制,十年磨一剑,如今的ASP.NET Identity是否足够强大,一起来体会. 在VS2013下新建项目,选择"A ...

  5. 第二波分析:德国是2018世界杯夺冠最大热门? Python数据分析来揭开神秘面纱… (附源代码)

    2018年,世界杯小组赛已经在如火如荼的进行中.在上篇文章的基础上[2018世界杯:用Python分析热门夺冠球队],我们继续分析世界杯32强的实力情况,以期能够更进一步分析本次世界杯的夺冠热门球队. ...

  6. 揭开自然拼读法(Phonics)的神秘面纱

    揭开自然拼读法(Phonics)的神秘面纱 自然拼读法  (Phonics),是指看到一个单词,就可以根据英文字母在单词里的发音规律把这个单词读出来的一种方法.即从“字母发音-字母组合发音-单词-简单 ...

  7. 揭开GrowingIO无埋点的神秘面纱

    揭开GrowingIO无埋点的神秘面纱   早在研究用户行为分析的时候,就发现国内的GrowingIO在宣传无埋点技术,最近正好抽出时间来研究一下所谓的无埋点到底是什么样的. 我分六部分来分析一下无埋 ...

  8. 带你揭开ATM的神秘面纱

    相信大家都用过ATM取过money吧,但是有多少人真正是了解ATM的呢?相信除了ATM从业者外了解的人寥寥无几吧,鄙人作为一个从事ATM软件开发的伪专业人士就站在我的角度为大家揭开ATM的神秘面纱吧. ...

  9. 揭开Future的神秘面纱——任务执行

    前言 此文承接之前的博文 解开Future的神秘面纱之取消任务 补充一些任务执行的一些细节,并从全局介绍程序的运行情况. 系列目录 揭开Future的神秘面纱——任务取消 揭开Future的神秘面纱— ...

随机推荐

  1. GET方式的中文编码与解码

  2. Wireshark "The NPF driver isn’t running…"(可见的驱动本质上是一个系统服务,使用net start 启动)

    前几天重装系统,装上了windows7 RC系统.昨天开始尝试装上了wireshark 这款很强大的网络监视软件,满心欢喜的打开,可是每次打开都会弹出“The NPF driver isn't run ...

  3. 【Latex】怎么写中文?

    最近总有这么几种情况:一.作业很简单,想用Latex敲,但是英语不过硬,用中文吧配中文环境就要配置半天.二.越来越多的朋友问我怎么搞中文输入,我也确实没啥帮助人家的好办法,所以只好自己研究研究怎么配置 ...

  4. java_接口的应用

    package com.test; interface USB{ //创建一个USB接口,所有的操作要按照这个标准来工作 void start();//默认为public void stop(); } ...

  5. 搭建Android环境

    1.相关文件下载: 1.1.Java jdk下载: JDK下载地址 http://www.oracle.com/technetwork/java/javase/downloads/jre7-downl ...

  6. 分享一个option样式传递给select当前选中样式

    今天遇到一个很是纠结的问题,需求又改了!原生的select给option加样式,结果发现select选中仍是默认样式,如下图:

  7. 初学者学Java设计模式(一)------单例设计模式

    单例设计模式 单例设计模式是指一个类只会生成一个对象,优点是他可以确保所有对象都访问唯一实例. 具体实现代码如下: public class A { public static void main(S ...

  8. Window vagrant 安装部署【转】

    回想以前,想要安装个虚拟机是多么的麻烦.先要费尽心机找到想要的操作系统镜像文件,然后安装虚拟化软件,按照其提供的GUI界面操作一步步创建,整个过程费时费力.但是,自从使用了Vagrant以后,咱腰不酸 ...

  9. 在cygwin下编译c语言

    #include <stdio.h> int main (void) { printf("Hello World!\n"); ; } 1.保存到cygwin工作目录下 ...

  10. Ubantu下编译Linux Kernel

    wget https://www.kernel.org/pub/linux/kernel/v3.x/linux-3.9.3.tar.gztar -xzf linux-3.9.3.tar.gzcd li ...