iOS底层探索之Block(四)——Block的探索和源码分析

Block的本质是什么吗?__Block底层又做了什么呢?

在上一篇博客中,已经探索到block的本质是结构体(__main_block_impl_0)继承自__block_implblock可以捕获外部变量,通过__block修饰内部可以变更外部变量的值。 那么本篇博客将对继续对block的底层原理进行分析。

Block探索分析

iOS底层探索之Block(一)——初识Block(你知道几种Block呢?)

iOS底层探索之Block(二)——如何解决Block循环引用问题?

iOS底层探索之Block(三)——Block的本质

1. block追根溯源

在以往的分析都是找到分析对象的出处,然后看相应的源码进行分析,那么block是来自哪个库呢,现在还不得而知,我们现在尝试的去寻找一下。

汇编查看流程

block代码举例

通过简单的 block代码去查看汇编的调用情况,是否会有不一样的发现呢!

objc_retainBlock

从👆上图汇编的流程中可以发现,调用了一个objc_retainBlockobjc 开头的不就是libobjc.A.dylib源码库嘛!那么我再去验证一下,通过符号断点看看,到底是不是libobjc.A.dylib

下符号断点验证

下符号断点

通过符号断点,也验证了确实是来自我们熟悉的libobjc.A.dylib源码库,如下所示:

符号断点跟踪

再次跑一次代码,确实走到了下的符号断点处,也发现了是来自libobjc.A.dylib,验证了上面的猜想,然后jmp跳转到_Block_copy,源码中也可以验证:
objc_retainBlock

从源码中可以知道调用objc_retainBlock返回的是_Block_copy,但是在源码中并没有搜索到_Block_copy的方法实现在哪里。

搜索_Block_copy

既然源码中没有_Block_copy的实现,大胆猜测一下,是不是不在libobjc.A.dylib里面呢?那么去下_Block_copy符号断点看看不就知道了啊!如下:

_Block_copy符号断点

通过下_Block_copy符号断点的跟踪,发现_Block_copy是来自于libsystem_blocks.dylib这个库,但是这个libsystem_blocks.dylib并没有开源,这一波操作就很烦了。那该怎么办呢?这里有两种办法,我们已经知道是来自libsystem_blocks.dylib就可以进行反汇编,还有一种就是找libclosure来代替,也是可以的。
libclosure源码工程

libclosure-79的工程中搜索_Block_copy是可以找到的,来自于Block_layout的结构体,是在Block_private.h文件中。

Block_layout

Block_layout结构体里面有 isa、标记flagsinvoke函数、descriptor描述等。

clang获取的cpp文件中也可以看到block源码的出处,来自Block_private.h,如下图所示:

cpp 文件查看block出处

通过对比还发现,在cpp文件中block定义的结构体__block_impl和源码中Block_layout的结构体是一致的,如下图所示:

对比图

小结:通过汇编调试,下符号断点,最终追根溯源到block是来自于libsystem_blocks.dylib,但是其并没有开源,可以通过对libsystem_blocks.dylib进行反汇编或者通过libclosure来代替源码工程来进行源码分析。

2. 汇编查看block捕获变量前后变化

block 捕获外部变量,在编译时是栈block,在运行时会copy堆区,变成堆block

变化前

下面就来分析这种内存变化是何时发生的,如下图所示:
读寄存器看 block类型变化

通过汇编调试,读取寄存器,发现当调用objc_retainBlock时,读取寄存器x0这里是模拟器就是rax,分析block的数据状态还是在栈区的,那么继续往下走流程,看看调用_Block_copy之后是有什么样的变化。

变化后

继续走,当调用_Block_copy之后变化如下:

读寄存器看 block类型变化

当调用_Block_copy之后变化的变化是,从栈区(NSStackBlock)变成堆区(NSMallocBlock)block了,地址发生了改变,从栈区拷贝到了堆区。

这也就验证了 block捕获了外部变量,在编译时是栈block,在运行时通过_Block_copycopy到堆区,变成堆block

3. _Block_copy源码分析

上面已经定位到 block的源码了,那么具体看看源码吧

  • Block_layout
truct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • isa isa指向确定block类型
  • flags 标识码
  • reserved 保留字段
  • invoke 函数,也就是FuncPtr
  • descriptor 相关附加信息
    BLOCK_DESCRIPTOR
  • flags


    在这里插入图片描述
  • _Block_copy源码分析


    _Block_copy源码分析
  1. flags也就是引用计数进行判断,如果是BLOCK_NEEDS_FREE已经释放了,直接返回aBlock
  2. 是否是全局的 block,也是直接返回aBlock
  3. 如果不是全局的那么就是栈 block或者是堆 block,但是此时是编译期不可能是堆区的block,如果编译期就开辟内存,对编译器压力太大了。所以编译器就标记为栈 block,当编译器知道你捕获到外部变量,到运行时就进行相关的内存开辟操作(malloc),在进行memmove拷贝一份
  4. 对其他一些信息,包括invoke、签名(ptrauth_signed_block_descriptors)信息也包装进result
  5. 最后isa = _NSConcreteMallocBlock返回一堆区的 block

在上面汇编查看的时候,打印了捕获变量的前后变化,lldb调试打印信息中有signatureinvokecopydispose等信息,这些是什么呢?

在这里插入图片描述

这个signature就是签名,还记得消息转发的时候这种[NSMethodSignature signatureWithObjCTypes:"v8@?0"];代码吗?

这是Type Encodings,类型编码。iOS提供了一个叫@encode的指令,可以将具体的类型表示成字符串编码!在分析类的结构的时候也介绍过。

  • v 表示viod,无返回值
  • 8 表示占了8个字节
  • @ 表示 参数id self
  • 未知类型(用于函数指针)
  • 0 表示id0号位开始

从下图中也可以看出这些信息,如下:


signature

在控制台可以通过 po 打印po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]查看signature具体信息。

还记得上面介绍了flagsdescriptor 相关附加信息吗

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;
    uintptr_t size;
};

Block_descriptor_1里面reserved就是保留字段,sizeblock的大小。

如果#define BLOCK_DESCRIPTOR_2 1,也就是为Block_descriptor_2的时候,才有上面控制台打印的copydispose

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;
    BlockDisposeFunction dispose;
};

Block_descriptor_3是可选的参数。而这里就通过flag字段来判断block是否存在Block_descriptor_3的相关属性

#define BLOCK_DESCRIPTOR_3 1
struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
Block_descriptor的get方法
  • 通过Block_descriptorget方法可以发现,Block_descriptor_2可以通过Block_descriptor_1地址平移的方式获取
  • 获取Block_descriptor_3时会判断Block_descriptor_2是否存在,如果不存在,就不需要添加Block_descriptor_2的地址空间。

lldb调试验证,如下

地址平移

更多内容持续更新

🌹 喜欢就点个赞吧👍🌹

🌹 觉得有收获的,可以来一波,收藏+关注,评论 + 转发,以免你下次找不到我😁🌹

🌹欢迎大家留言交流,批评指正,互相学习😁,提升自我🌹

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

推荐阅读更多精彩内容