ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

iOS 底层探索篇 ——block(下)

2021-08-24 22:03:42  阅读:203  来源: 互联网

标签:__ BLOCK iOS descriptor block copy Block 底层


@[TOC](iOS 底层探索篇 —— block(下))

block底层源码

block 编译成一个什么样的结构
block invoke-> isa->签名 ->捕获 -> 保存 -> 释放

1. block底层分析

要探究block底层是什么样的一个结构,那么就定义一个block,然后xcrun 一下。
在这里插入图片描述
在这里插入图片描述
xcrun之后,打开生成的cpp文件,看到生成的main函数。
在这里插入图片描述
把函数的类型强转去掉之后得到下面的代码。这里看到 void(*block)(void) 等于__main_block_impl_0函数的返回值,而block调用等于block->FuncPtr(block)。
在这里插入图片描述
搜索一下__main_block_impl_0,那么就说明block等于这个结构体,之前的方法是这个结构体的构造函数。在main函数中定义了一个int a,而结构体这里也有一个int a,两者之间有什么联系呢?
在这里插入图片描述
这里试着把block里面的a去掉,然后重新xcrun一下。这里看到结构体里的a消失了。并且构造函数里面的参数以及a(_a)消失了。a(_a)是一个c++的一个语法,会讲传进来的参数赋值给成员变量a。
那么也就是说:block在底层捕获了a,并形成了相对应的成员变量。
在这里插入图片描述
把int 换成NSObject试一下,发现确实形成了相对应的成员变量。(这里用的NSObject,所以换到viewController文件中,重新xcrun了)。
在这里插入图片描述
看到block的结构体,注意到这里的isa指向的是_NSConcreteStackBlock,这里捕获到了外界变量,为什么还是stackBlock呢?那么是不是编译时的时候是_NSConcreteStackBlock,到运行时的时候动态变成了mallocBlock的呢?
同时注意到这里传进来的参数fp,赋值给了impl的FuncPtr。fp是 __main_block_func_0,那么也就是说 impl.FuncPtr = __main_block_func_0。搜索__main_block_func_0,发现里面是函数实现。那么也就是说funcPtr里面存的是block的函数实现。而之前的 block->FuncPtr(block);其实也就是调用了__main_block_func_0。

这里说明了block对fp进行了函数式保存。这里看到int a = __cself->a,__cself是传进来的参数也就是block自身,那么__cself->a也就是block结构体的成员变量a。这里的int a 进行了值拷贝。
在这里插入图片描述

2. __ block 的作用

在声明a的时候,添加__block修饰。
在这里插入图片描述
重新xcrun看到结构体和函数里的int a变成了 __Block_byref_a_0 a。
在这里插入图片描述
再来看到main函数,这里的a的结构也变成了__Block_byref_a_0结构体,并且进行了初始化。并且这里保存的是&a也就是a的地址。这里省略了int a = 18 的步骤,&a就是这个a的地址。
在这里插入图片描述
接下来找到__Block_byref_a_0结构体,这里的
__forwarding也就是a的地址。
在这里插入图片描述
这样block里面保存的就是a的地址,__Block_byref_a_0 *a 等于__cself->a 也就是a的地址。这个时候,__main_block_func_0函数内部的a,就和main函数的a相同了,因为他们的地址是一样的,指向同一片内存空间。这个时候,在block内部修改a的值,外部的a的值也会修改了。
在这里插入图片描述
所以,__block的作用是生成了__Block_byref_a_0结构体,并且是将指针地址传给了block,这样就能达到修改同一片内存空间的效果。

3. block底层copy处理

打下断点后运行下面的代码。
在这里插入图片描述
发现这里有调用一个objc_retainBlock,然后走到objc_retainBlock里面。
在这里插入图片描述
在这里插入图片描述
也可以通过下符号断点的方式来进入objc_retainBlock。
在这里插入图片描述
接下来去libobjc源码中搜索objc_retainBlock,发现里面调用了_Block_copy。在libobjc中没有搜到_Block_copy,那么就去下符号断点。
在这里插入图片描述
发现在libsystem_blocks.dylib里面,这个框架不是开源的。这里可以用反汇编来进行分析,也可以通过替代工程libclosure79来分析。
在这里插入图片描述
来到libclosure79里搜索_Block_copy。这里看到block真正从底层结构Block_layout。
在这里插入图片描述
Block_layout里面的成员变量有:

  1. isa:指向表明block类型的类

  2. flags:标识符,按bit位表示一些block的附加信息,类似于isa中的位域,其中flags的种类有以下几种,主要重点关注BLOCK_HAS_COPY_DISPOSE 和 BLOCK_HAS_SIGNATURE。 BLOCK_HAS_COPY_DISPOSE 决定是否有 Block_descriptor_2。BLOCK_HAS_SIGNATURE 决定是否有 Block_descriptor_3

  3. reserved:保留信息,可以理解预留位置,猜测是用于存储block内部变量信息

  4. invoke:是一个函数指针,指向block的执行代码

  5. descriptor:block的附加信息,里面有很多内容,比如是否正在析构、是否有析构函数,是否有keep函数等。有三类: Block_descriptor_1是一定存在的,Block_descriptor_2 和 Block_descriptor_3都是可选参数。

在这里插入图片描述
接下来去看一下flags里面是什么内容。看到有以下这些标识符。这里主要重点关注BLOCK_HAS_COPY_DISPOSE 和 BLOCK_HAS_SIGNATURE。 BLOCK_HAS_COPY_DISPOSE 决定是否有 Block_descriptor_2。BLOCK_HAS_SIGNATURE 决定是否有 Block_descriptor_3
在这里插入图片描述
回到block copy 方法读取寄存器,看到这里消息接收者是 NSGlobalBlock类型。
在这里插入图片描述
回到viewController让block捕获外界变量后重新运行。
在这里插入图片描述
重新读取寄存器后输出,发现是NSStackBlock,并且多了copy和dispose。这里的block应该是MallocBlock,为什么还是NSStackBlock呢?猜想是在copy里面做了处理,将NSStackBlock变为MallocBlock。
在这里插入图片描述
在copy 方法 return的地方打下断点,运行过去后重新读取寄存器,发现果然变成了NSMallocBlock类型。
在这里插入图片描述
接下来去看源码是如何操作的。
首先这里会判断block的状态,如果是BLOCK_NEEDS_FREE 或者BLOCK_IS_GLOBAL 就不继续下面的操作,直接返回aBlock。
在这里插入图片描述
如果两者都不是,那么就会对block进行处理。这里是编译期过来的,所以只能是栈block,也就是stackBlock。因为如果在编译期进行内存开辟的话,那么对编译器的压力太大,所以在编译时都标记为栈block。而到了运行时,发现这个栈block还捕获了外界的变量,那么就会根据这个block copy一份,并且将其isa改为_NSConcreteMallocBlock。这里的操作是拿到block的大小,然后根据这个大小开辟一个新的内存空间,然后调用memmove进行拷贝,然后拷贝invoke等其他属性,并且对标识符进行处理,最后才改变block的isa。所以_Block_copy操作之后,就会把栈block变为堆block。
在这里插入图片描述
之前读取寄存器的时候,发现其还打印出了 signature: “v8@?0”,这个就是block的签名。打印一下。有了签名,那么就可以做相对应的hook的处理。block的invoke其实也是消息发送,如果消息失效或者发生问题,那么就会进入消息转发流程。在消息转发的慢速转发流程过程中,必须要用到签名才能进行相关的处理。
在这里插入图片描述
之前的分析中,Block_layout还有一个descriptor,descriptor里面有什么呢?发现里面装着reserved和size,那么读取寄存器时候打印出来的copy 和dispose 存在哪里呢?
在这里插入图片描述
这里看到,copy 和 dispose 在Block_descriptor_2里面。这里涉及到了内存的连续可选。Block的类型 不一样,那么其相对应的结构也就不一样,所以这里通过标识符来判断内存的可选。这里的标识符就是BLOCK_DESCRIPTOR_2 和 BLOCK_DESCRIPTOR_3。
在这里插入图片描述
那么标识符是由什么来决定的呢?在setter里面没有搜到有用的信息,那么就从对应的getter方法来查找。搜索Block_descriptor_2,看到这里的getter。这里要拿到_Block_descriptor_2或者_Block_descriptor_3,都要 += sizeof(struct Block_descriptor_1) ,那么也就是说Block_descriptor_1是一定存在的,而_Block_descriptor_3则还需要通过aBlock->flags & BLOCK_HAS_COPY_DISPOSE来判断是否存在Block_descriptor_2来决定是否需要进行 += sizeof(struct Block_descriptor_2) 的操作。
在这里插入图片描述
之前打印出来的堆block这里有copy,dispose 和 signature,那么也就是说,这个堆block里面有Block_descriptor_2和_Block_descriptor_3。
在这里插入图片描述
既然是内存平移得到的,那么我们就可以通过内存平移得到Block_descriptor_2和_Block_descriptor_3。这里isa 8 个 字节, int32_t 4个字节, 那么 flags 和 reserved 就是8个字节。invoke 8个字节
在这里插入图片描述
x/8gx 打印一下MallocBlock,发现果然平移16位后为invoke。
在这里插入图片描述
descriptor是一个Block_descriptor_1* 类型,也就是8字节,所以0x00000001006c40d0就是descriptor。
x/8gx 打印出descriptor,那么前面16个字节就是存的descriptor1,往后16个字节存descriptor2,再往后16个字节存的是descriptor3。而0x00000001006c1308和0x00000001006c1344也确实是之前打印出来的copy和dispose的地址。打印0x00000001006c3477也打印出了签名。
在这里插入图片描述
在这里插入图片描述

_Block_object_assign

这里看到有个__ViewController__viewDidLoad_block_desc_0结构体,其默认等于__ViewController__viewDidLoad_block_desc_0_DATA,也就是说里面的copy等于__ViewController__viewDidLoad_block_copy_0,dispose等于__ViewController__viewDidLoad_block_dispose_0。而这里的copy和dispose都是descriptor2里面的东西。
在这里插入图片描述
看到__ViewController__viewDidLoad_block_dispose_0方法,发现这里面调用了_Block_object_assign这个方法,这个方法是做什么的呢?
在这里插入图片描述
在源码中搜索_Block_object_assign,发现这样一段注释。_Block_object_assign和_Block_object_dispose里面的flags将会根据对象的不同而设置不同的值。

  • 普通对象,即没有其他的引用类型 BLOCK_FIELD_IS_OBJECT (3)
  • block类型作为变量 BLOCK_FIELD_IS_BLOCK (7)
  • 经过__block修饰的变量 BLOCK_FIELD_IS_BYREF (8)

标签:__,BLOCK,iOS,descriptor,block,copy,Block,底层
来源: https://blog.csdn.net/LinShunIos/article/details/119883001

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有