iOS--block的本质和变量捕获机制

学习笔记,如有错误,欢迎批评指正!!! 仅供学习交流...

block的本质

  1. block是带有自动变量(局部变量)的匿名函数(不带名称的函数)
    1. 带有自动变量的值:block保持自动变量的值。
  2. block的本质是一个OC对象,它内部有一个isa指针。

直接上代码,查看block的底层结构

void (^block1)(void) = ^{
            NSLog(@"This is block!!!");
        };

接下来,使用终端命令:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o xxx.cpp

然后,查看xxx.cpp文件,如下图所示(请忽略文件命名 🤣...)

1.jpg

接下来,我们把重点代码复制下来

//block转化成c++的结构体
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  //构造函数(类似于OC的init方法)
  __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;
  }
};

////封装了 block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_zt_xy2f_gsx0mlbrzmvts93gjqm0000gn_T_main_b2615a_mi_0);
        }

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; 

        //定义block变量
        void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        //执行block内部的代码
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    }
    return 0;
}

对 main 函数里边的代码 进行简化:

//定义block变量
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

//执行block内部的代码
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);

继续简化:

//定义block变量
//调用 __main_block_impl_0 构造函数,返回结构体对象,最后取地址,复制给 block1
//返回的结构体对象就是 struct __main_block_impl_0
void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

//执行block内部的代码
block1->FuncPtr(block1);

block的底层机构大致就是这样的:

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;
  //构造函数(类似于OC的init方法)
  __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;
  }
};

接下来,我们看一下这段代码:

void (*block1)(void) = &__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);

可以看到,__main_block_impl_0 函数传递了两个参数 (void *)__main_block_func_0&__main_block_desc_0_DATA

第一个参数 (void *)__main_block_func_0,这个函数里边封装的就是 block的执行逻辑代码

最终 这个函数指针赋值给了 struct __block_impl里边的void *FuncPtr。也就是说 FuncPtr里边存储的就是将来要执行的 block函数的地址。

2.jpg

接着,我们看 block的执行代码

((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);

//简化之后
block1->FuncPtr(block1);

可以看到,执行block内部的代码,其实是找到 FuncPtr指针,然后去执行。

block1之所以可以直接找到 FuncPtr,是因为 将 block1强制转换成了 __block_impl 这种类型,然后再找到 __block_impl里边的 FuncPtr

__main_block_impl_0 这种类型的block 之所以 能够转换成为 __block_impl类型,是因为 __main_block_impl_0里边的第一个成员就是 __block_impl类型的,这两个的地址是一样的。

从另一种方面来说,由于__main_block_impl_0类型的第一个成员的类型是 struct __block_impl,所以 __main_block_impl_0也可以直接看成是如下结构:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __main_block_impl_0 {
  //struct __block_impl impl;
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
  struct __main_block_desc_0* Desc;
  //构造函数(类似于OC的init方法)
  __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;
  }
};

这样,就可以简单明了的理解 block为什么可以直接找到 FuncPtr了。

其实,我们还可以通过以下代码查看 block

void (^block1)(void) = ^{
            NSLog(@"This is block!!!");
        };
        
block1();
              
NSLog(@"%@ %@ %@ %@",[block1 class]
                      ,[[block1 class] superclass]
                      ,[[[block1 class] superclass] superclass]
                      ,[[[[block1 class] superclass] superclass] superclass]);

输出结果:
3.jpg

可以看到,block最终继承自 NSObject,再次证明 block其实就是一个OC对象

变量的捕获

为了保证 block 内部能够正常访问 外部变量的值, block有个变量捕获机制

C语言的函数中可能使用的变量

  • 自动变量(局部变量)-- 离开作用域就会自动销毁
  • 函数的参数
  • 静态局部变量
  • 静态全局变量
  • 全局变量

其中,静态局部变量静态全局变量全局变量,虽然这些变量的作用域不同,但是在整个程序当中,一个变量总保持在一个内存区域。因此,即使在多个函数中使用这些变量,这些变量的值总是保持不变。

那么,哪些变量可以被捕获到block内部呢?先看下边的结论

局部变量会被捕获到block内部,全局变量不会被捕获到block内部。

局部变量的捕获

直接上代码:
4.jpg

定义了 两个变量 a 和 b, 运行,查看输出结果

02-block变量捕获[2020:161392] a = 10, b = 20

接着看转换的c++代码。

5.jpg

可以看出,变量a 和 变量b 都被捕获到了 block 内部。并且,变量a 和 变量b 的捕获方式是一模一样的,都是直接把值传递进去(值传递)。

//这两句代码是等效的  默认前边都是有一个 auto 关键字
int a = 10; 
auto int a = 10; 

接下来,修改代码,如下图:

6.jpg

从运行输出结果可以看出 static修饰的局部变量 和 auto修饰的局部变量的结果是不一样的。

重新生成一下xxx.cpp代码,查看一下:

7.jpg

可以看出static修饰的局部变量 传递的是 地址值,被block捕获到内部的是一个指针。(指针传递)

关于局部变量的捕获:

  1. auto类型:会被捕获, 捕获方式:值传递
  2. static类型:会被捕获, 捕获方式:指针传递

全局变量的捕获

继续修改代码,如下如所示:

8.jpg

全局变量a 和 静态全局变量b 获取到的都是最新的值。

重新生成cpp文件

9.jpg

可以看出,全局变量a 和 静态全局变量b 并没有被捕获到 block内部。

总结:

10.jpg

结论
11.jpg

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 200,738评论 5 472
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,377评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 147,774评论 0 333
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,032评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,015评论 5 361
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,239评论 1 278
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,724评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,374评论 0 255
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,508评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,410评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,457评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,132评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,733评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,804评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,022评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,515评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,116评论 2 341

推荐阅读更多精彩内容