这是我参与8月更文挑战的第24天,活动详情查看:8月更文挑战
写在前面: iOS底层原理探究是本人在平时的开发和学习中不断积累的一段进阶之
路的。 记录我的不断探索之旅,希望能有帮助到各位读者朋友。
目录如下:
- iOS 底层原理探索 之 alloc
- iOS 底层原理探索 之 结构体内存对齐
- iOS 底层原理探索 之 对象的本质 & isa的底层实现
- iOS 底层原理探索 之 isa - 类的底层原理结构(上)
- iOS 底层原理探索 之 isa - 类的底层原理结构(中)
- iOS 底层原理探索 之 isa - 类的底层原理结构(下)
- iOS 底层原理探索 之 Runtime运行时&方法的本质
- iOS 底层原理探索 之 objc_msgSend
- iOS 底层原理探索 之 Runtime运行时慢速查找流程
- iOS 底层原理探索 之 动态方法决议
- iOS 底层原理探索 之 消息转发流程
- iOS 底层原理探索 之 应用程序加载原理dyld (上)
- iOS 底层原理探索 之 应用程序加载原理dyld (下)
- iOS 底层原理探索 之 类的加载
- iOS 底层原理探索 之 分类的加载
- iOS 底层原理探索 之 关联对象
- iOS底层原理探索 之 魔法师KVC
- iOS底层原理探索 之 KVO原理|8月更文挑战
- iOS底层原理探索 之 重写KVO|8月更文挑战
- iOS底层原理探索 之 多线程原理|8月更文挑战
- iOS底层原理探索 之 GCD函数和队列
- iOS底层原理探索 之 GCD原理(上)
- iOS底层 - 关于死锁,你了解多少?
- iOS底层 - 单例 销毁 可否 ?
- iOS底层 - Dispatch Source
- iOS底层 - 一个栅栏函 拦住了 数
- iOS底层 - 不见不散 的 信号量
- iOS底层 GCD - 一进一出 便成 调度组
- iOS底层原理探索 - 锁的基本使用
- iOS底层 - @synchronized 流程分析
- iOS底层 - 锁的原理探索
- iOS底层 - 带你实现一个读写锁
- iOS底层 - 谈Objective-C block的实现(上)
- iOS底层 - 谈Objective-C block的实现(下)
以上内容的总结专栏
细枝末节整理
前言
今天,我们从底层结构开始,揭开Block的面纱,从流程开始,解释上一篇中面试题的疑问。
Block 底层
欲探寻Block底层,必要将其编译。
我们自定义一个block:
xcrun
一下,将其编译成cpp文件,在看其底层实现和底层结构:
static void _I_BlockViewController_viewDidLoad(BlockViewController * self, SEL _cmd) {
((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("BlockViewController"))}, sel_registerName("viewDidLoad"));
int superman = 100;
//__BlockViewController__viewDidLoad_block_impl_0 函数的调用
void(*myBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0,
&__BlockViewController__viewDidLoad_block_desc_0_DATA,
superman));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
/** 上面两行我们将类型去掉整理一下
void(*myBlock)(void) = __BlockViewController__viewDidLoad_block_impl_0(
__BlockViewController__viewDidLoad_block_func_0,
__BlockViewController__viewDidLoad_block_desc_0_DATA,
superman );
myBlock->FuncPtr(myBlock);
*/
}
block_impl
可以看到 myBlock
等于 __BlockViewController__viewDidLoad_block_impl_0
,那么,我们找一下其实现的内容,:
struct __BlockViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
int superman;
__BlockViewController__viewDidLoad_block_impl_0(void *fp,
struct __BlockViewController__viewDidLoad_block_desc_0 *desc,
int _superman, int flags=0) : superman(_superman) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
它是一个结构体,这个结构体就等于 结构体的构造函数;构造函数传三个参数,最后一个是 superman
并且结构体中有一个成员就是 superman (在构造函数中将传参过来的 superman
赋值给了 结构体成员的 superman ),这里很奇怪,和我们自定义的变量名一样。
这里会不会是Block根据捕获的外部变量自动生成的成员(并在构造函数中完成赋值)呢?
我们验证下这个猜想:
将 myBlock 改为如下内容:
xrun 之后:
这里少了一个参数,结构体也没有了 superman 那个成员:
void(*myBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0(
(void *)__BlockViewController__viewDidLoad_block_func_0,
&__BlockViewController__viewDidLoad_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
...
struct __BlockViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
__BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们再进一步做一下验证,来证明我们的猜想是对的:
同样的 xcrun 一下看结果:
AppDelegate *app = ((AppDelegate *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("alloc"));
void(*myBlock)(void) = ((void (*)())&__BlockViewController__viewDidLoad_block_impl_0((void *)__BlockViewController__viewDidLoad_block_func_0, &__BlockViewController__viewDidLoad_block_desc_0_DATA, app, 570425344));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
...
struct __BlockViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
AppDelegate *app;
__BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, AppDelegate *_app, int flags=0) : app(_app) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
isa
通过编译的代码,我们也可以看到 在编译阶段,Block是一个栈Block (impl.isa = &_NSConcreteStackBlock;
); 但是,我们前两片关于Block的总结,Block捕获外部变量后,并且不是弱引用,这种情况的Block是一个堆Block。也就是说,
在编译时 先编译成了栈 Block, 到之后的 运行时 才会变成堆Block,
那么是怎么变成堆Block的呢? 这也是我们接下来要继续探索的内容;
FuncPtr
在构造函数中,我们传参的第一个 fp
赋值给了 FuncPtr
; 传进去的是 __BlockViewController__viewDidLoad_block_func_0
是一个函数:
static void __BlockViewController__viewDidLoad_block_func_0(struct __BlockViewController__viewDidLoad_block_impl_0 *__cself) {
int superman = __cself->superman; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_w3_ptw7ymvs5pjf98xqvjnp86wc0000gn_T_BlockViewController_385088_mi_0, superman);
}
随后就在下一行,进行了 FuncPtr
的调用执行。
Block的成员变量FuncPtr在构造函数中对要执行的内容 进行了保存,如果没有调用,那,就不会调用方法执行。 所以这也就是一定要调用执行的原因
方法内部
int superman = __cself->superman; // bound by copy
__cself
就是 __BlockViewController__viewDidLoad_block_impl_0
( 因为 myBlock->FuncPtr(myBlock); )
那么,这里的 int superman 和 结构体内部的 superman 是一样(值相同,地址不同);
__block 做了什么
下面我们 加上 __block 在试一下:
- 在Block结构体内部: 之前是:
int superman;
加入 __block 后:
__Block_byref_superman_0 *superman; // by ref
- 在
viewDidLoad
函数内部调用中: 之前是:
int superman = 100;
加入 __block 后:
// 结构体的初始化
// 编译器省略了一行代码: int superman = 100;
__Block_byref_superman_0 superman = {
(void*)0,
(__Block_byref_superman_0 *)&superman,
0,
sizeof(__Block_byref_superman_0),
100
};
//__attribute__((__blocks__(byref))) __Block_byref_superman_0 superman = {(void*)0,(__Block_byref_superman_0 *)&superman, 0, sizeof(__Block_byref_superman_0), 100};
我们看下 这个结构体 __Block_byref_superman_0
:
struct __Block_byref_superman_0 {
void *__isa;
__Block_byref_superman_0 *__forwarding;
int __flags;
int __size;
int superman;
};
可以看到是将 &superman
赋值给了 __Block_byref_superman_0
结构体 superman
的 __forwarding
成员。
*__forwarding = &superman(100)
在 Block 结构体内部的构造函数中: 将 _superman->__forwarding
赋值给了 Block 内部 成员变量 superman ;
struct __BlockViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __BlockViewController__viewDidLoad_block_desc_0* Desc;
__Block_byref_superman_0 *superman; // by ref
__BlockViewController__viewDidLoad_block_impl_0(void *fp, struct __BlockViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_superman_0 *_superman, int flags=0) : superman(_superman->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block 整理
上述的流程是,在 添加了 __block 之后:
-
原先的变量类型从 int 变为了 结构体
__Block_byref_superman_0
;(我们定义的 100 取地址存放在结构体成员变量 *__forwarding 中 取地址存放在指针中 --- *__forwarding 指向 superman 的地址 ) -
原先是将 superman 赋值给block结构体成员superman,变成了将 _superman->__forwarding指针 赋值给 block 成员 *superman指针 ;
-
最后我们再看 FuncPtr 方法的调用也就是
__BlockViewController__viewDidLoad_block_func_0
:(方法内部 __cself 是Block 本身 )
原来是 :
// 这里的 superman 和 我们 Block 中的 superman 不一样哦
// 值相同,地址不同 - 值拷贝
int superman = __cself->superman; // bound by copy
加入 __block 后:
// 这里的 superman 就和 我们外界 __block 的 superman 是相同
// 这里是传的地址进来,是一个指针赋值 (整理中的第一条)
// 所以这里的 superman 是可以修改的
__Block_byref_superman_0 *superman = __cself->superman; // bound by ref
(superman->__forwarding->superman)++;
- __block 做了下面这些操作 :
生成了结构体__Block_byref_superman_0
,并且传给 Block
的是指针地址,就达到了修改同一片内存空间的效果。
总结
- Block 会根据捕获的外部变量自动生成相应的成员变量(并在构造函数中完成外部变量赋值给成员变量的操作)
- 外界变量加了 __block 修饰后 ,变量会在底层 生成了结构体
__Block_byref_superman_0
,并且传给Block
内部的__Block_byref_superman_0*superman指针的是指针地址,如此,经__block修饰后的变量和Block内部的变量指向同一片内存空间, 就达到了修改同一片内存空间的效果。
Block底层拷贝
我们在 __BlockViewController__viewDidLoad_block_func_0
这个函数附近还看到了一些方法 如(下面 为了表述方便我们 用 xxx 替代 __BlockViewController__viewDidLoad_block
):
xxx_copy_0
、xxx_dispose_0
xxx_desc_0
以及在 xxx_copy_0
中的 _Block_object_assign
等,这些都是什么呢? 还有就是上一节我们分析 为什么在编译时是 栈Block, 到运行时的时候才会是 堆Block ,运行时处理了哪些呢? 接下来我们就探索以上这些问题。
既然要探索运行时的Block是如何将编译时的栈Block操作成堆Block的,那么,我们那就将代码运行起来,跑在真机上断点调试一下。
打开汇编看汇编流程:
这里来到了 objc_retainBlock
这里。
在objc源码中:
id objc_retainBlock(id x) {
return (id)_Block_copy(x);
}
...
// 创建一个基于堆的Block副本,或者简单地添加一个对现有Block的引用
// 这必须与Block_release配对以恢复内存,即使在运行时也是如此
// 在Objective-C的垃圾收集。
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// 丢失引用,如果是基于堆的和最后的引用,则恢复内存
BLOCK_EXPORT void _Block_release(const void *aBlock)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// 由编译器使用。不要自己调用这个函数
BLOCK_EXPORT void _Block_object_assign(void *, const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// 由编译器使用。不要自己调用这个函数
BLOCK_EXPORT void _Block_object_dispose(const void *, const int)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
// 由编译器使用。不要自己调用这个函数
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteStackBlock[32]
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
经过一番寻找,我们在 libclosure 中找到了 _Block_copy
;
_Block_copy
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {// 栈 - 堆 (编译期间生成的栈Block)
// Its a stack block. Make a copy.
size_t size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);
if (!result) return NULL;
memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#if __has_feature(ptrauth_signed_block_descriptors)
if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
uintptr_t oldDesc = ptrauth_blend_discriminator(
&aBlock->descriptor,
_Block_descriptor_ptrauth_discriminator);
uintptr_t newDesc = ptrauth_blend_discriminator(
&result->descriptor,
_Block_descriptor_ptrauth_discriminator);
result->descriptor =
ptrauth_auth_and_resign(aBlock->descriptor,
ptrauth_key_asda, oldDesc,
ptrauth_key_asda, newDesc);
}
#endif
#endif
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
...
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa; //栈or堆 Block
volatile int32_t flags; // 包含参考数(标识)
int32_t reserved; //流程的数据
BlockInvokeFunction invoke; // 函数的调用
struct Block_descriptor_1 *descriptor; // 描述(是否正在析构、是否有keep函数等)
// imported variables
};
...
// 用于描述块对象的Block_layout->标志的值
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler
#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
BLOCK_SMALL_DESCRIPTOR = (1 << 22), // compiler
#endif
BLOCK_IS_NOESCAPE = (1 << 23), // compiler
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
我们读取一下 x0 寄存器,看一下消息的接受者。
是一个全局Block。
下面我们捕获一下外部的变量:
断点读取一下x0 :
这里是一个栈Block。
我们在汇编代码的最后,在 ret 那一行打上断点,再看 x0寄存器(函数返回值存放在这里):
此时这里是一个堆Block,我们经过了 _Block_copy
函数之后,就从栈Block成为了堆Block;
如此: 编译器 编译完代码后, 只生成了 栈Block
,因为,在编译期 没有 alloc 的重定义处理,且需要申请内存空间一系列的处理, 堆内存的开辟操作 放在编译期 大大增加了编译器的压力,因此, 编译期间 没有 堆Block
只会默认标记一个 栈Block
;等到运行时 经过 判断是 栈Block
并且 捕获了外部变量
就会将 此 栈Block
完整的拷贝一份 到 堆空间
, Block 的isa 再 指定为 堆Block
。
isa 重新标记
_Block_copy
函数流程中, 如果 是 需要释放的Block或者是一个全局的Block会返回出去,接下来 编译期生成的 栈Block 并且捕获了外部变量的话 会进行一个拷贝(按大小开辟内存空间,memmove 拷贝一份到result返回值,对签名、flage等进行处理,isa 重新标记为 _NSConcreteMallocBlock 堆Block ),
在打印 x0寄存器堆时候,我们打印出了以下信息:
(lldb) po 0x0000000281238e10
<__NSMallocBlock__: 0x281238e10>
signature: "v8@?0"
invoke : 0x1028bde58 (/private/var/containers/Bundle/Application/CFEA7FC5-6956-47CE-B759-AF1F32E7DDD5/bb.app/bb`__29-[ViewController viewDidLoad]_block_invoke)
copy : 0x1028bde90 (/private/var/containers/Bundle/Application/CFEA7FC5-6956-47CE-B759-AF1F32E7DDD5/bb.app/bb`__copy_helper_block_e8_32s)
dispose : 0x1028bdec8 (/private/var/containers/Bundle/Application/CFEA7FC5-6956-47CE-B759-AF1F32E7DDD5/bb.app/bb`__destroy_helper_block_e8_32s)
签名
这里的 invoke 是函数的调用者, signature 是Block的签名,我们验证下:
1个参数,无返回值(v),参数从 0 号位开始, 是一个 对象类型,也是一个Block类型 (@?)
(我们为什么要关注这个签名呢? 如果进行底层的copy或hook的话,需要我们在想要调用的地方invoke一下, 这其实是一层消息,涉及到消息机制,消息流程的最后一个阶段 必须要获取到签名,才能进行 invocation。)
这里对于签名以及isa我们大致了解了;但是下面的 copy函数 dispose函数 这两个是什么呢?
Block结构
从上面的 _Block_copy 流程,在最后对 isa 进行了赋值,我们看下Block的结构
struct Block_layout {
void * __ptrauth_objc_isa_pointer isa; //栈or堆 Block
volatile int32_t flags; // 包含参考数(标识)
int32_t reserved; //流程的数据
BlockInvokeFunction invoke; // 函数的调用
// 描述(是否正在析构、是否有keep函数等)
// 从descriptor 开始是可选参数(内存的连续可选)
struct Block_descriptor_1 *descriptor;
// imported variables
};
我们发现如果定义类型为 BLOCK_DESCRIPTOR_2
,才会有 copy 和 dispose。
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
我们就去定位下 什么时候是 BLOCK_DESCRIPTOR_2
这个类型,却发现,太多,找不到。
我们没找到其如何生成的(可能是编译器帮我们搞定的就省略了),但是我们可以知道其如何get到的。如下:
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
通过 _Block_get_descriptor += Block_descriptor_1
就得到了 Block_descriptor_2
什么意思呢?
如下图所示(因为内存连续,通过内存平移得到 Block_descriptor_2
以此类推 Block_descriptor_3 也可以通过内存平移获得):
Block_descriptor_3 中有 signature 和 layout;
#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 必然有 Block_descriptor_3 和 Block_descriptor_2 。
既然这样,我们可以通过 x/10gx 来看一下内存:
上面这张图完美的诠释了 Block 的内存结构, 有 invoke descriptor (copy dispose) signature 这些数据,且全部打印出来一一验证。
捕获变量
前面的探索中,关于 _Block_copy
是一个栈拷贝到堆过程,但是通过 xcrun 我们在编译后的代码中发现了一个 _Block_object_assign
,如下:
__BlockViewController__viewDidLoad_block_copy_0
static void __BlockViewController__viewDidLoad_block_copy_0(struct __BlockViewController__viewDidLoad_block_impl_0*dst, struct __BlockViewController__viewDidLoad_block_impl_0*src) {
_Block_object_assign((void*)&dst->superman,
(void*)src->superman,
8/*BLOCK_FIELD_IS_BYREF*/);
}
这里有点奇怪,
这两个 copy 并没有什么关系的
这里其实是传参 我们上面图片中 红色框框住的 copy ;如下:
static struct __BlockViewController__viewDidLoad_block_desc_0 {
size_t reserved;
size_t Block_size;
//这里 这里
void (*copy)(struct __BlockViewController__viewDidLoad_block_impl_0*, struct __BlockViewController__viewDidLoad_block_impl_0*);
void (*dispose)(struct __BlockViewController__viewDidLoad_block_impl_0*);
} __BlockViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __BlockViewController__viewDidLoad_block_impl_0),
//这里 这里
__BlockViewController__viewDidLoad_block_copy_0,
__BlockViewController__viewDidLoad_block_dispose_0};
Block 其实就是一个对象,我们之前是对其 初始化赋值的一个过程。其中有一个 Block_descriptor_1 , 还有一个Block_descriptor_2 ( 2 里面有 copy 和 dispose 函数 , 初始化的时候,也要初始化,就是给他们一个函数的实现。) 也就是上面的__BlockViewController__viewDidLoad_block_copy_0
,就是copy函数的实现。
在 copy 函数的实现过程中 有调用一个 _Block_object_assign
源码定位后发现下面的注释内容:
是对捕获变量的类型的处理
The flags parameter of _Block_object_assign and _Block_object_dispose is set to
* BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object,
* BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
* BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)
验证一下:
我们这里是一个 8
是因为我们这个例子是 __block 修饰的变量。
下面我们修改为一个OC对象来试一下:
下面,我们就进入到 _Block_object_assign
方法的实现。
_Block_object_assign
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// 普通的对象类型 直接 retain 操作
// _Block_retain_object_default = dose nothind
// 交给系统级别默认的 arc 操作
// _Block_retain_object_default = fn (arc)
_Block_retain_object(object);
// block外的对象object的值 赋值给 block 中的变量 --
// 他们指向相同的内存空间 它们的地址并不一样
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 类型 会进行 _Block_copy 操作
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*dest = object;
break;
default:
break;
}
}
_Block_byref_copy
__block 修饰的在这里会按照原来的对象大小,开辟内从空间将值赋值过去,原来的forwarding
和创建的forwarding
是一样的。
这个forwarding
最终是赋值给了block函数执行中的那个变量。
这里结合在Block底层那一小节中的 整理 来看,你的思路会更清晰一些。
这也就是为什么 __block
修饰的变量它们( block实现体外 的变量 和 内部的变量 )是同一片内存空间,它是指针拷贝。就是下面两行代码解释的很清楚:
- copy->forwarding = copy;
- src->forwarding = copy;
static struct Block_byref *_Block_byref_copy(const void *arg) {
struct Block_byref *src = (struct Block_byref *)arg;
// __block 内存是一样 同一个家伙
//
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
// 被捕获的变量进来了
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
// 捕获到了外界的变量 - 内存处理 - 对外界变量生命周期的保存
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
byref_keep
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; //= __Block_byref_id_object_copy_131
BlockByrefDestroyFunction byref_destroy; // = __Block_byref_id_object_dispose_131
};
这两个Function等于什么呢?-- Block_byref
// __block 修饰的变量 编译后会 是一个结构体
// 结构体
struct Block_byref {
void * __ptrauth_objc_isa_pointer isa; // 8
struct Block_byref *forwarding; // 8
volatile int32_t flags; // contains ref count//4
uint32_t size; // 4
};
这里结合在Block底层那一小节中的 整理 来看,你的思路会更清晰一些。
__block 修饰的对象就是如下的一个结构体
__Block_byref_superman_0
struct __Block_byref_superman_0 {
void *__isa;
__Block_byref_superman_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *superman;
};
viewDidLoad中 对结构体 赋值如下 :
byref_keep 函数赋值的分别就是 :
__Block_byref_id_object_copy_131,
__Block_byref_id_object_dispose_131,
copy 函数调用的就是 如下内容:
// block 捕获的是 -> Block_byref 捕获的是 -> Object对象
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
这里函数的 (char*)dst + 40
是什么意思呢?
这里要看回我们的结构体 __Block_byref_superman_0
本身了,内存平移 40字节 后 就是 其内部成员 NSObject *superman;
也就是copy函数传参的是 Object 然后这里会去到_Block_object_assign
先进入到 BLOCK_FIELD_IS_BYREF
再进入到 BLOCK_FIELD_IS_OBJECT
补充
在探索 __weak 修饰的block 的时候 还是一个 8 ,为什么呢?
其实在 _Block_object_assign
源码中有细节:
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
总结
一个被 __block
修饰的变量 会在编译时编译为 Block_byref
类型的结构体:
- block 进行一下 copy --> 从
栈
空间 拷贝 到堆
空间; - 接下来 block 会进行捕获变量 Block_byref --> 对 整个Block_byref对象 进行
拷贝
(整个的对象) - Block_byref 在 copy 函数调用时 对内部的 object 成员 进行
拷贝
综上, 这就是 __block
修饰的变量的三层拷贝 。
- block 捕获变量的时候会 对类型进行划分;
- Block_byref(结构体) 类型 进行拷贝保存, 通过 byref_keep 函数 对内部的objec 对象进行保存;
释放函数
大体也是一个逆向的过程 ( Block_byref(结构体)会进行一个 byref_destroy 那个释放函数 类似copy )
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}