Block底层实现分析02-__block使用

注:分析参考 MJ底层原理班 内容,本着自己学习原则记录

本文使用的源码为objc4-723

转 C++ 使用的命令 :
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc -fobjc-arc -fobjc-runtime=ios-8.0.0 main.m

1 block 内部不能修改基本数据类型 auto 变量分析

1.1 转码后从函数作用域和值捕获逻辑分析

1.2 static 的基本数据变量可以在 block 内修饰分析

1.3 全局变量当然能在 block 内被修改

  • 因为全局变量的全局特性:全局访问,所以可以随处修改


2 __block修饰 auto 变量

2.1 一般情况我们都不想改变基本数据类型的 auto 变量类型,但还是希望能够在 block 内部修改 auto 变量的值

  • 也就是说不想使用static修饰(这样会改变变量内存位置,static修饰时会使变量从栈区变成数据区,变量的声明周期被无限延长)
  • 也不会将基本数据类型的 auto 变量类型 转为 全局变量类型

2.2 使用__block修饰 auto 变量

  • 可以让auto变量在 block 内被修改


  • __block不能修饰静态变量(static)


  • __block不能修饰全局变量、静态变量(static)

2.3 __block修改 auto变量的转码分析

  1. 编译器会将__block变量包装成一个对象
  2. __block变量的 age 对象内存分析
源码对象 内存结构 备注

3 __block修饰的 age 及 block 外面访问的 age 对比

3.1 这些 age 都是同一个 age 吗?

3.2 通过打印和源码分析

3.2.1 从打印上看很明显,后两个(2、3) age 地址相同;
3.2.2 从源码上看,2和3 的 age 访问方式都是age.__forwarding->age,被
__block修饰后的 age 它是__Block_byref_age_0类型结构体
3.2.3 从源码上看,第1个打印 age 地址并不是我们期待的,因为打印出来的这个 age 地址是__Block_byref_age_0类型结构体内int age成员变量的地址值

3.3 通过内存地址叠加分析

3.3.1 执行__block int age = 10;代码后,age 变量即变成__Block_byref_age_0类型的结构体,那么此时 age 的地址就是该结构体第一个成员变量的地址,age 结构体的地址 等于 __isa 指向的值

struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

3.3.2 而后续的(2、3)访问的 age,都是通过age.__forwarding->age方式访问,访问的 age 实际是结构体内部的 int age变量,那么 age成员变量地址应该是结构体起始地址加上age 成员变量前的其他成员量占的内存字节数之和

3.3.3 通过底层转换形式,再次打印 age 结构体地址

通过上图方式,我们已经能够正确地获取到 age 结构体的地址,那么验证一下,age 成员变量地址,是不是等于 age 结构体地址加上age 成员变量之前的其他变量所占内存字节之和呢?(答案当然是肯定啦)即:0x100604600 + (8+8+4+4) = 0x100604618

image.png

4 __block的内存管理

我们可以从Block底层实现分析01的第6点知道,当 block 内部访问的变量是对象类型或被__block修饰的基本变量类型时,block 的结构体中 Desc 结构体内会多出两个用于处理对象引用问题的函数成员变量,如下:

重点区别在struct __main_block_desc_0的结构体成员上

4.1 访问基本 auto 变量 block 转 C++后源码

4.2 访问对象类型的 auto 变量的 block 转 C++后源码

访问对象类型的 auto 变量struct __main_block_desc_0的结构体成员比访问`基本 auto 变量的多了两个成员

void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);

4.3 内存管理函数的实现

它们的作用就是用来处理对象类型的 auto 变量引用计数问题

4.4 访问__block修饰的 auto 变量的 block 转 C++后源码

4.5 分别对比block 访问__weak对象__block对象默认 strong 对象内存管理

  1. OC 测试代码


  2. 转C++后


block(指定都是堆block,应为只有堆block才会引用对象)内部访问的对象类型,会根据对应的强弱修饰符__strong/__weak,调用对应的函数_Block_object_assign进行内存处理

  • strong 修饰的,block 会强引用对象
  • weak 修饰,block 不会强引用对象
  • __block修饰,block 会强引用该变量对象

4.6 __block的内存管理总结

  1. 当block在栈上时,并不会对__block变量产生强引用

  2. 当block被copy到堆时

  • 会调用block内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会对__block变量形成强引用(retain)
  1. 当block从堆中移除时
  • 会调用block内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
  • _Block_object_dispose函数会自动释放引用的__block变量(release)

4.7 __block修饰的对象age 变量,仍然是存在于栈上,栈上变量,堆上的block,如何关联引用?

  • 实际上,当 block 被 copy 到堆上时,其访问的__block变量也会被 copy 到堆上,如下情况图解
  1. 将 block copy到堆



  2. 废弃 block



5 block内部访问 对象类型的auto变量__block变量的内存管理总结

  1. 当block在栈上时,对它们都不会产生强引用

  2. 当block拷贝到堆上时,都会通过copy函数来处理它们
    __block变量(假设变量名叫做a)

_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
  1. 当block从堆上移除时,都会通过dispose函数来释放它们
    __block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
  1. 对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);

6 __block__forwarding指针

下述访问 age 的情况:



不知道你会不会奇怪,为什么访问的 age 是通过age->__forwarding->age方式访问?为什么要绕一大个弯来获取呢?
其实这个与变量的栈堆拷贝有关的,从第4.7点中我们知道,__block修饰的变量会随 block 的栈堆位置变化而相应地发生变化,如下图示:

  • 将 block copy到堆



那在__block变量被拷贝时候,__forwarding指针也会相应发生变化

  • 证据1:同样的age->__forwarding->age访问方式,1的情况是,__block变量age结构体 还在空间,所以内部的 age 成员变量地址是栈地址样式(比较长😆),而2和3时候,__block变量 age 已经 copy 到控件,所以对应的 age 成员变量地址变为堆地址样式(比较短😆)

你同样可以打印一个局部int height变量地址,与其他打印的地址进行对比,同在堆或栈的变量或对象的地址不会相差太远(后续有机会,会详细分析堆栈内存结构相关知识)

  • 证据2,在 MRC 环境下,下面图中代码足以证明 age 结构体的forwarding 指针会如前面__forwarding变化图那样,在 age结构体被堆栈block 捕获后,其值会发生对应的变化

7 被__block修饰的对象类型

7.1 被__block修饰的基本数据类型变量,在底层会将基本变量包装成成一个结构体对象

7.2 那被__block修饰的对象类型呢?

7.3 对象类型会被包装成 __Block_byref_person_0类型 block,block__block personperson之间的关系

7.4 总结:被__block修饰的对象类型内存管理

  1. __block变量在栈上时,不会对指向的对象产生强引用

  2. __block变量被copy到堆时

  • 会调用__block变量内部的copy函数
  • copy函数内部会调用_Block_object_assign函数
  • _Block_object_assign函数会根据所指向对象的修饰符(__strong__weak__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
  • 注意:这里仅限于ARC时会retain,MRC时不会retain
  1. 如果__block变量从堆上移除
  • 会调用__block变量内部的dispose函数
  • dispose函数内部会调用_Block_object_dispose函数
    _Block_object_dispose函数会自动释放指向的对象(release

8 验证7.4总结

8.1 首先将环境调至 MRC,执行下述代码,理解清楚:栈 block堆 block栈的__block对象,和堆的__block对象的获取,可以参考上述第6点去理解

这里就不再用文字详细叙述了(用文字分析思考及逻辑变化过程需要太多篇幅了😝,如有不懂,欢迎微信QQ讨论,底部评论不会及时回复,而且评论区不能贴图解析很麻烦)
Demo:BlockTest-__block-对象类型01

8.2 验证:当__block变量在栈上时,不会对指向的对象产生强引用

  • MRC 环境


当 Person 第一次执行完 release 操作时,即销毁,证明 __block person 结构体并没有强引用 person。
Demo:BlockTest-__block-对象类型02

8.3 验证:当__block变量被copy到堆时

  1. MRC 环境
  • 通过对栈 block 进行 copy 操作,转为堆 block,堆 block 内捕获的 __block person 结构体也会被 copy 到堆上,但是__block person 结构体不会对 person 对象进行强引用


Demo:BlockTest-__block-对象类型03

  1. ARC 环境
  • __strong


  • __weak


Demo:BlockTest-__block-对象类型04

前面测试用的代码,开始没想过要保存的,后面为了方便自己调试和截图就补上一点点 demo 代码了,不过没有也没关系,截图也已经非常清晰,自己敲一遍好了,理解更深刻。


文/Jacob_LJ(简书作者)
PS:如非特别说明,所有文章均为原创作品,著作权归作者所有,转载需联系作者获得授权,并注明出处,所有打赏均归本人所有!

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

推荐阅读更多精彩内容