内容简介:通过clang命令查看编译器是如何实现Block的,在终端输入下面我们一个一个来看
void blockTest() { void (^block)(void) = ^{ NSLog(@"Hello World!"); }; block(); } int main(int argc, char * argv[]) { @autoreleasepool { blockTest(); } } 复制代码
通过clang命令查看编译器是如何实现Block的,在终端输入 clang -rewrite-objc main.m
,然后会在当前目录生成 main.cpp
的C++文件,代码如下:
struct __blockTest_block_impl_0 { struct __block_impl impl; struct __blockTest_block_desc_0* Desc; __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0); } static struct __blockTest_block_desc_0 { size_t reserved; size_t Block_size; } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)}; void blockTest() { void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } int main(int argc, char * argv[]) { /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; blockTest(); } } static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 }; 复制代码
下面我们一个一个来看
__blockTest_block_impl_0
struct __blockTest_block_impl_0 { struct __block_impl impl; struct __blockTest_block_desc_0* Desc; __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 复制代码
__blockTest_block_impl_0
是 Block
的C++实现,是一个结构体,从命名可以看出表示 blockTest
中的第一个( 0
) Block
。通常包含两个成员变量 __block_impl impl
, __blockTest_block_desc_0* Desc
和一个构造函数。
__block_impl
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; }; 复制代码
__block_impl也是一个结构体
- *isa:isa指针,指向一个类对象,有三种类型:_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock,本例中是_NSConcreteStackBlock类型。
- Flags:block 的负载信息(引用计数和类型信息),按位存储。
- Reserved:保留变量。
- *FuncPtr:一个指针,指向
Block
执行时调用的函数,也就是Block
需要执行的代码块。在本例中是__blockTest_block_func_0
函数。
__blockTest_block_desc_0
static struct __blockTest_block_desc_0 { size_t reserved; size_t Block_size; } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0)}; 复制代码
__blockTest_block_desc_0
是一个结构体,包含两个成员变量:
- reserved:
Block
版本升级所需的预留区空间,在这里为0。 - Block_size:
Block
大小(sizeof(struct __blockTest_block_impl_0))
。
__blockTest_block_desc_0_DATA
是一个 __blockTest_block_desc_0
的一个实例。
__blockTest_block_func_0
__blockTest_block_func_0
就是 Block
的执行时调用的函数,参数是一个 __blockTest_block_impl_0
类型的指针。
static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_0048d2_mi_0); } 复制代码
blockTest
void blockTest() { void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA)); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } 复制代码
第一部分,定义Block
void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA)); 复制代码
我们看到 block
变成了一个指针,指向一个通过 __blockTest_block_impl_0
构造函数实例化的结构体 __blockTest_block_impl_0
实例, __blockTest_block_impl_0
在初始化的时候需要两个个参数:
-
__blockTest_block_func_0
:Block
块的函数指针。 -
__blockTest_block_desc_0_DATA
:作为静态全局变量初始化__main_block_desc_0
的结构体实例指针。
第二部分,调用Block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); 复制代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)
通过 block->FuncPtr
指针找到 __blockTest_block_func_0
函数并且转成 (void (*)(__block_impl *))
类型。
((__block_impl *)block)
然后将 block
作为参数传给这个函数调用。
Flags
在 __block_impl
中我们看到 Flags
,现在来详细讲一讲。
在这里Block_private.h可以看到 Flags
的具体信息:
// Values for Block_layout->flags to describe block objects enum { BLOCK_DEALLOCATING = (0x0001), // runtime BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 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 }; 复制代码
引用 浅谈 block(1) - clang 改写后的 block 结构 的解释:
也就是说,一般情况下,一个 block
的 flags 成员默认设置为 0。如果当 block
需要 Block_copy()
和 Block_release
这类拷贝辅助函数,则会设置成 1 << 25
,也就是 BLOCK_HAS_COPY_DISPOSE
类型。可以搜索到大量讲述 Block_copy
方法的博文,其中涉及到了 BLOCK_HAS_COPY_DISPOSE
。
总结一下枚举类的用法,前 16 位即起到标记作用,又可记录引用计数:
- BLOCK_DEALLOCATING :释放标记。一般常用 BLOCK_NEEDS_FREE 做 位与 操作,一同传入 Flags ,告知该 block 可释放。
- BLOCK_REFCOUNT_MASK :一般参与判断引用计数,是一个可选用参数。
- BLOCK_NEEDS_FREE :通过设置该枚举位,来告知该 block 可释放。意在说明 block 是 heap block ,即我们常说的 _NSConcreteMallocBlock 。
- BLOCK_HAS_COPY_DISPOSE :是否拥有拷贝辅助函数(a copy helper function)。
- BLOCK_HAS_CTOR :是否拥有 block 析构函数(dispose function)。
- BLOCK_IS_GC :是否启用 GC 机制(Garbage Collection)。
- BLOCK_HAS_SIGNATURE :与 BLOCK_USE_STRET 相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用。
block截获变量
截获auto变量值
我们看到直接在 block
修改变量会提示错误,为什么呢?
void blockTest() { int num = 10; void (^block)(void) = ^{ NSLog(@"%d",num); }; num = 20; block(); } int main(int argc, char * argv[]) { @autoreleasepool { blockTest(); } } 复制代码
打印结果是10,clang改写后的代码如下:
struct __blockTest_block_impl_0 { struct __block_impl impl; struct __blockTest_block_desc_0* Desc; int num; __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) { int num = __cself->num; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_3c2714_mi_0,num); } void blockTest() { int num = 10; void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, num)); num = 20; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } 复制代码
__blockTest_block_impl_0
多了一个成员变量 int num;
,再看看构造函数 __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int _num, int flags=0)
,可以看到第三个参数只是变量的值,这也就解释了为什么打印的是10,因为** block
截获的是值**。
使用static修饰变量
void blockTest() { static int num = 10; void (^block)(void) = ^{ NSLog(@"%d",num); num = 30; }; num = 20; block(); NSLog(@"%d",num); } 复制代码
可以在 block
内部修改变量了,同时打印结果是20,30。clang改写后的代码如下:
struct __blockTest_block_impl_0 { struct __block_impl impl; struct __blockTest_block_desc_0* Desc; int *num; __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int *_num, int flags=0) : num(_num) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) { int *num = __cself->num; // bound by copy NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_0,(*num)); (*num) = 30; } void blockTest() { static int num = 10; void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, #)); num = 20; NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_5a95f6_mi_1,num); ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); } 复制代码
__blockTest_block_impl_0
多了一个成员变量 int *num;
,和上面不同的是,这次** block
截获的是指针**,所以可以在内部通过指针修改变量的值,同时在外部修改变量的值, block
也能"感知到"。那么为什么之前传递指针呢?因为变量是栈上,作用域是函数 blockTest
内,那么有可能变量比 block
先销毁,这时候 block
再通过指针去访问变量就会有问题。而 static
修饰的变量不会被销毁,也就不用担心。
全局变量
int num = 10; void blockTest() { void (^block)(void) = ^{ NSLog(@"%d",num); num = 30; }; num = 20; block(); NSLog(@"%d",num); } 复制代码
打印结果是20,30。clang改写后的代码如下:
int num = 10; struct __blockTest_block_impl_0 { struct __block_impl impl; struct __blockTest_block_desc_0* Desc; __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, int flags=0) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) { NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_1875c6_mi_0,num); num = 30; } 复制代码
非常简单,在初始化 __blockTest_block_impl_0
并没有把 num
作为参数, __blockTest_block_func_0
中也是直接访问全局变量。
总结:
变量类型 | 是否捕获到block内部 | 访问方式 |
---|---|---|
局部auto变量 | 是 | 值传递 |
局部static变量 | 是 | 指针传递 |
全局变量 | 否 | 直接访问 |
使用__block修饰变量
void blockTest() { __block int num = 10; void (^block)(void) = ^{ NSLog(@"%d",num); num = 30; }; num = 20; block(); NSLog(@"%d",num); } 复制代码
效果和使用static修饰变量一样,clang改写后的代码如下:
struct __Block_byref_num_0 { void *__isa; __Block_byref_num_0 *__forwarding; int __flags; int __size; int num; }; struct __blockTest_block_impl_0 { struct __block_impl impl; struct __blockTest_block_desc_0* Desc; __Block_byref_num_0 *num; // by ref __blockTest_block_impl_0(void *fp, struct __blockTest_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void __blockTest_block_func_0(struct __blockTest_block_impl_0 *__cself) { __Block_byref_num_0 *num = __cself->num; // bound by ref NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_0,(num->__forwarding->num)); (num->__forwarding->num) = 30; } static void __blockTest_block_copy_0(struct __blockTest_block_impl_0*dst, struct __blockTest_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);} static void __blockTest_block_dispose_0(struct __blockTest_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);} static struct __blockTest_block_desc_0 { size_t reserved; size_t Block_size; void (*copy)(struct __blockTest_block_impl_0*, struct __blockTest_block_impl_0*); void (*dispose)(struct __blockTest_block_impl_0*); } __blockTest_block_desc_0_DATA = { 0, sizeof(struct __blockTest_block_impl_0), __blockTest_block_copy_0, __blockTest_block_dispose_0}; void blockTest() { __attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)#, 0, sizeof(__Block_byref_num_0), 10}; void (*block)(void) = ((void (*)())&__blockTest_block_impl_0((void *)__blockTest_block_func_0, &__blockTest_block_desc_0_DATA, (__Block_byref_num_0 *)#, 570425344)); (num.__forwarding->num) = 20; ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block); NSLog((NSString *)&__NSConstantStringImpl__var_folders_04_xwbq8q6n0p1dmhhd6y51_vbc0000gp_T_main_018b76_mi_1,(num.__forwarding->num)); } 复制代码
哇,难受啊兄dei,怎么多出来这么多东西,没关系,慢慢分析。
__blockTest_block_impl_0
多出来一个成员变量 __Block_byref_num_0 *num;
,我们看到经过 __block
修饰的变量类型变成了结构体 __Block_byref_num_0
, __blockTest_block_impl_0
多出来一个成员变量 __Block_byref_num_0 *num;
, block
捕获的是 __Block_byref_num_0
类型指针 ,
__Block_byref_num_0
我们看到 __Block_byref_num_0
是一个结构体,并且有一个 isa
,因此我们可以知道它其实就是一个对象。同时还有一个 __Block_byref_a_0 *
类型的 __forwarding
和 num
, num
我们能猜到就是用来保存变量的值, __forwarding
就有一点复杂了,后面慢慢讲。
__blockTest_block_copy_0和**__blockTest_block_dispose_0**
__blockTest_block_copy_0
中调用的是 _Block_object_assign
, __blockTest_block_dispose_0
中调用的是 _Block_object_dispose
。
函数 | 调用时机 |
---|---|
__blockTest_block_copy_0 | __block 变量结构体实例从栈拷贝到堆时 |
__blockTest_block_dispose_0 | __block 变量结构体实例引用计数为0时 |
关于 _Block_object_assign
和 _Block_object_dispose
更详细代码可以在runtime.c中查看。
BLOCK_FIELD_IS_BYREF
我们看到 _Block_object_assign
和 _Block_object_dispose
中都有个参数值为8, BLOCK_FIELD_IS_BYREF
类型,什么意思呢?在Block_private.h中可以查看到:
// Runtime support functions used by compiler when generating copy/dispose helpers // Values for _Block_object_assign() and _Block_object_dispose() parameters enum { // see function implementation for a more complete description of these fields and combinations BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ... BLOCK_FIELD_IS_BLOCK = 7, // a block variable BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines. }; 复制代码
-
BLOCK_FIELD_IS_OBJECT
:OC对象类型 -
BLOCK_FIELD_IS_BLOCK
:是一个block -
BLOCK_FIELD_IS_BYREF
:在栈上被__block
修饰的变量 -
BLOCK_FIELD_IS_WEAK
:被__weak
修饰的变量,只在Block_byref
管理内部对象内存时使用 -
BLOCK_BYREF_CALLER
:处理Block_byref
内部对象内存的时候会加的一个额外标记(告诉内部实现不要进行retain或者copy)
__blockTest_block_desc_0我们可以看到它多了两个回调函数指针 *copy
和 *dispose
,这两个指针会被赋值为 __main_block_copy_0
和 __main_block_dispose_0
最后我们看到访问 num
是这样的:
__Block_byref_num_0 *num = __cself->num; // bound by ref (num->__forwarding->num) = 30; 复制代码
下面就讲一讲为什么要这样。
Block的内存管理
在前面我们讲到 __block_impl
指向的_NSConcreteStackBlock类型的类对象,其实总共有三种类型:
类型 | 存储区域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 数据区 |
_NSConcreteMallocBlock | 堆 |
前面也讲到 copy
和 dispose
,在 ARC 环境下,有哪些情况编译器会自动将栈上的把 Block
从栈上复制到堆上呢?
Block从栈中复制到堆 |
---|
调用Block的copy实例方法时 |
Block作为函数返回值返回时 |
在带有usingBlock的Cocoa方法或者GCD的API中传递Block时候 |
将block赋给带有__strong修饰符的id类型或者Block类型时 |
当 Bock
从栈中复制到堆, __block
也跟着变化:
- 当
Block
在栈上时,__block
的存储域是栈,__block
变量被栈上的Block
持有。 - 当
Block
被复制到堆上时,会通过调用Block
内部的copy
函数,copy函数内部会调用_Block_object_assign
函数。此时__block
变量的存储域是堆,__block
变量被堆上的Block
持有。 - 当堆上的
Block
被释放,会调用Block
内部的dispose
,dispose
函数内部会调用_Block_object_dispose
,堆上的__block
被释放。
- 当多个栈上的
Block
使用栈上的__block
变量,__block
变量被栈上的多个Block
持有。 - 当
Block0
被复制到堆上时,__block
也会被复制到堆上,被堆上Block0
持有。Block1
仍然持有栈上的__block
,原栈上__block
变量的__forwarding
指向拷贝到堆上之后的__block
变量。 - 当
Block1
也被复制到堆上时,堆上的__block
被堆上的Block0
和Block1
只有,并且__block
的引用计数+1。 - 当堆上的
Block
都被释放,__block
变量结构体实例引用计数为0,调用_Block_object_dispose
,堆上的__block
被释放。
下图是描述 __forwarding
变化。这也就能解释 __forwarding
存在的意义:
__forwarding 保证在栈上或者堆上都能正确访问对应变量
int main(int argc, char * argv[]) { int num = 10; NSLog(@"%@",[^{ NSLog(@"%d",num); } class]); void (^block)(void) = ^{ NSLog(@"%d",num); }; NSLog(@"%@",[block class]); } 复制代码
打印结果:
2019-05-04 18:40:48.470228+0800 BlockTest[35824:16939613] __NSStackBlock__ 2019-05-04 18:40:48.470912+0800 BlockTest[35824:16939613] __NSMallocBlock__ 复制代码
我们可以看到第一个 Block
没有赋值给 __strong
指针,而第二个 Block
没有赋值给 __strong
指针,所以第一个在栈上,而第二个在堆上。
Block截获对象
int main(int argc, char * argv[]) { { Person *person = [[Person alloc] init]; person.name = @"roy"; NSLog(@"%@",[^{ NSLog(@"%@",person.name); } class]); NSLog(@"%@",@"+++++++++++++"); } NSLog(@"%@",@"------------"); } 复制代码
打印结果:
@interface Person : NSObject @property (nonatomic, strong) NSString *name; @end @implementation Person - (void)dealloc { NSLog(@"-------dealloc-------"); } @end typedef void(^Block)(void); int main(int argc, char * argv[]) { { Person *person = [[Person alloc] init]; person.name = @"roy"; NSLog(@"%@",[^{ NSLog(@"%@",person.name); } class]); NSLog(@"%@",@"+++++++++++++"); } NSLog(@"%@",@"------------"); } 复制代码
我们看到当 Block
内部访问了对象类型的auto对象时,如果 Block
是在栈上,将不会对auto对象产生强引用。
auto Strong 对象
typedef void(^Block)(void); int main(int argc, char * argv[]) { Block block; { Person *person = [[Person alloc] init]; person.name = @"roy"; block = ^{ NSLog(@"%@",person.name); }; person.name = @"david"; NSLog(@"%@",@"+++++++++++++"); } NSLog(@"%@",@"------------"); block (); } 复制代码
打印结果是
2019-05-04 17:46:27.083280+0800 BlockTest[33745:16864251] +++++++++++++ 2019-05-04 17:46:27.083934+0800 BlockTest[33745:16864251] ------------ 2019-05-04 17:46:27.084018+0800 BlockTest[33745:16864251] david 2019-05-04 17:46:27.084158+0800 BlockTest[33745:16864251] -------dealloc------- 复制代码
我们看到是先打印的 david
再调用 Person
的析构方法 dealloc
,在终端输入 clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m -fobjc-arc
,clang在ARC环境下改写后的代码如下:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__strong person; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _person, int flags=0) : person(_person) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 复制代码
我们看到 __main_block_impl_0
中的 Person *__strong person;
成员变量。
Block
截获了auto对象,当 Block
被拷贝到堆上, Block
强引用auto对象,这就能解释了为什么超出了 person
的作用域, person
没有立即释放,当 Block
释放之后,会自动去掉对该对象的强引用,该对象就会被释放了。
auto Weak 对象
typedef void(^Block)(void); int main(int argc, char * argv[]) { Block block; { Person *person = [[Person alloc] init]; person.name = @"roy"; __weak Person *weakPerson = person; block = ^{ NSLog(@"%@",weakPerson.name); }; weakPerson.name = @"david"; NSLog(@"%@",@"+++++++++++++"); } NSLog(@"%@",@"------------"); block (); } 复制代码
打印结果是
2019-05-04 17:49:38.858554+0800 BlockTest[33856:16869229] +++++++++++++ 2019-05-04 17:49:38.859218+0800 BlockTest[33856:16869229] -------dealloc------- 2019-05-04 17:49:38.859321+0800 BlockTest[33856:16869229] ------------ 2019-05-04 17:49:38.859403+0800 BlockTest[33856:16869229] (null) 复制代码
直接在终端输入 clang -rewrite-objc main.m
会报 cannot create __weak reference because the current deployment target does not support weak ref
错误。需要用 clang -rewrite-objc -fobjc-arc -fobjc-runtime=macosx-10.13 main.m
, -fobjc-arc
代表当前是ARC环境 -fobjc-runtime=macosx-10.13
:代表当前运行时环境,缺一不可,clang之后的代码:
struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; Person *__weak weakPerson; __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; 复制代码
我们看到 __main_block_impl_0
中的 Person *__weak weakPerson;
成员变量。
总结:
- 当
Block
内部访问了对象类型的auto对象时,如果Block
是在栈上,将不会对auto对象产生强引用。 - 如果block被拷贝到堆上,会调用
Block
内部的copy函数,copy函数内部会调用_Block_object_assign
函数,_Block_object_assign
会根据auto对象的修饰符(__strong
,__weak
,__unsafe_unretained
)做出相应的操作,当使用的是__strong
时,将会对person
对象的引用计数加1,当为__weak
时,引用计数不变。 - 如果
Block
从对上移除,会调用block内部的dispose
函数,内部会调用_Block_object_dispose
函数,这个函数会自动释放引用的auto
对象。
Block循环引用
@interface Person : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, copy) void (^block)(void); - (void)testReferenceSelf; @end @implementation Person - (void)testReferenceSelf { self.block = ^ { NSLog(@"self.name = %s", self.name.UTF8String); }; self.block(); } - (void)dealloc { NSLog(@"-------dealloc-------"); } @end int main(int argc, char * argv[]) { Person *person = [[Person alloc] init]; person.name = @"roy"; [person testReferenceSelf]; } 复制代码
打印结果是 self.name = roy
, Person
的析构方法 dealloc
并没有执行,这是典型的循环引用,下面我们研究研究为啥会循环引用。clang改写后的代码如下:
struct __Person__testReferenceSelf_block_impl_0 { struct __block_impl impl; struct __Person__testReferenceSelf_block_desc_0* Desc; Person *const __strong self; __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __strong _self, int flags=0) : self(_self) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) { ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, self, 570425344))); ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))(); } 复制代码
我们看到本来Person中 testReferenceSelf
方法是没有参数的,但是转成C++之后多出来两个参数: * self
和 _cmd
,再看看 __Person__testReferenceSelf_block_impl_0
中多出来一个成员变量 Person *const __strong self;
,因此我们知道Person中 block
捕获了 self
, block
强引用 self
,同时 self
也强引用 block
,因此形成循环引用。
Weak解除循环引用
@implementation Person - (void)testReferenceSelf { __weak typeof(self) weakself = self; self.block = ^ { __strong typeof(self) strongself = weakself; NSLog(@"self.name = %s", strongself.name.UTF8String); }; self.block(); } - (void)dealloc { NSLog(@"-------dealloc-------"); } @end 复制代码
打印结果:
2019-05-04 19:27:48.274358+0800 BlockTest[37426:17007507] self.name = roy 2019-05-04 19:27:48.275016+0800 BlockTest[37426:17007507] -------dealloc------- 复制代码
我们看到Person对象被正常释放了,说明不存在循环引用,为什么呢?clang改写后的代码如下:
struct __Person__testReferenceSelf_block_impl_0 { struct __block_impl impl; struct __Person__testReferenceSelf_block_desc_0* Desc; Person *const __weak weakself; __Person__testReferenceSelf_block_impl_0(void *fp, struct __Person__testReferenceSelf_block_desc_0 *desc, Person *const __weak _weakself, int flags=0) : weakself(_weakself) { impl.isa = &_NSConcreteStackBlock; impl.Flags = flags; impl.FuncPtr = fp; Desc = desc; } }; static void _I_Person_testReferenceSelf(Person * self, SEL _cmd) { __attribute__((objc_ownership(weak))) typeof(self) weakself = self; ((void (*)(id, SEL, void (*)()))(void *)objc_msgSend)((id)self, sel_registerName("setBlock:"), ((void (*)())&__Person__testReferenceSelf_block_impl_0((void *)__Person__testReferenceSelf_block_func_0, &__Person__testReferenceSelf_block_desc_0_DATA, weakself, 570425344))); ((void (*(*)(id, SEL))())(void *)objc_msgSend)((id)self, sel_registerName("block"))(); } 复制代码
可以看到 __Person__testReferenceSelf_block_impl_0
结构体中weakself成员是一个 __weak
修饰的Person类型对象,也就是说 __Person__testReferenceSelf_block_impl_0
对Person的依赖是弱依赖。weak修饰变量是在runtime中进行处理的,在Person对象的Dealloc方法中会调用weak引用的处理方法,从weak_table中寻找弱引用的依赖对象,进行清除处理。
最后
好了,关于Block就写到这里了,花了五一的三天时间解决了一个基础知识点,如释重负,写的真心累。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- Docker实现原理之 - OverlayFS实现原理
- 微热山丘,探索 IoC、AOP 实现原理(二) AOP 实现原理
- 带你了解vue计算属性的实现原理以及vuex的实现原理
- Docker原理之 - CGroup实现原理
- AOP如何实现及实现原理
- webpack 实现 HMR 及其实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。