内容简介:通过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 及其实现原理
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
An Introduction to the Analysis of Algorithms
Robert Sedgewick、Philippe Flajolet / Addison-Wesley Professional / 1995-12-10 / CAD 67.99
This book is a thorough overview of the primary techniques and models used in the mathematical analysis of algorithms. The first half of the book draws upon classical mathematical material from discre......一起来看看 《An Introduction to the Analysis of Algorithms》 这本书的介绍吧!