新建工程,并实现以下代码:
#import <Foundation/Foundation.h>
void testFunc() {
int a =10;
__block int b = 20;
void (^testBlock)(int) = ^(int c){
int d = a + b + c;
NSLog(@"d=%d", d);
};
//修改值不会影响testBlock内的计算结果
a = 20;
//修改值会影响testBlock内的计算结果。
b = 40;
testBlock(30);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testFunc();
}
return 0;
}
在工程目录启动命令行输入以下命令:
$ clang -rewrite-objc main.m
将生成main.cpp文件拉至最下,可见如下代码:
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
struct __testFunc_block_impl_0 {
struct __block_impl impl;
struct __testFunc_block_desc_0* Desc;
int a;
__Block_byref_b_0 *b; // by ref
__testFunc_block_impl_0(void *fp, struct __testFunc_block_desc_0 *desc, int _a, __Block_byref_b_0 *_b, int flags=0) : a(_a), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __testFunc_block_func_0(struct __testFunc_block_impl_0 *__cself, int c) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
int a = __cself->a; // bound by copy
int d = a + (b->__forwarding->b) + c;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1y_tktqm9p53zz3khyhzvqxtb9m0000gn_T_main_2edacd_mi_0, d);
}
static void __testFunc_block_copy_0(struct __testFunc_block_impl_0*dst, struct __testFunc_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __testFunc_block_dispose_0(struct __testFunc_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __testFunc_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __testFunc_block_impl_0*, struct __testFunc_block_impl_0*);
void (*dispose)(struct __testFunc_block_impl_0*);
} __testFunc_block_desc_0_DATA = { 0, sizeof(struct __testFunc_block_impl_0), __testFunc_block_copy_0, __testFunc_block_dispose_0};
void testFunc() {
int a =10;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
void (*testBlock)(int) = ((void (*)(int))&__testFunc_block_impl_0((void *)__testFunc_block_func_0, &__testFunc_block_desc_0_DATA, a, (__Block_byref_b_0 *)&b, 570425344));
a = 20;
(b.__forwarding->b) = 40;
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
testFunc();
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
1. block 的底层结构
1.1 __testFunc_block_impl_0
struct __testFunc_block_impl_0 {
struct __block_impl impl;
struct __testFunc_block_desc_0* Desc;
int a;
__Block_byref_b_0 *b; // by ref
__testFunc_block_impl_0(void *fp, struct __testFunc_block_desc_0 *desc, int _a, __Block_byref_b_0 *_b, int flags=0) : a(_a), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__testFunc_block_impl_0是该block以C++实现的一个结构体,且从命名可以看出其为testFunc方法中的第0个block,通常包含两个成员变量__block_impl impl,__testBlock_block_desc_0* Desc和一个构造函数。
- __block_impl 结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
- 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_DATA是一个__blockTest_block_desc_0的一个实例。
reserved:Block版本升级所需的预留区空间,在这里为0。
Block_size:Block大小(sizeof(struct __blockTest_block_impl_0))。
1.2 __testFunc_block_func_0
static void __testFunc_block_func_0(struct __testFunc_block_impl_0 *__cself, int c) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
int a = __cself->a; // bound by copy
int d = a + (b->__forwarding->b) + c;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_1y_tktqm9p53zz3khyhzvqxtb9m0000gn_T_main_2edacd_mi_0, d);
}
__testFunc_block_func_0就是block在执行时调用的函数,参数是一个__testFunc_block_impl_0类型的指针。
1.3 testFunc()
void testFunc() {
int a =10;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
void (*testBlock)(int) = ((void (*)(int))&__testFunc_block_impl_0((void *)__testFunc_block_func_0, &__testFunc_block_desc_0_DATA, a, (__Block_byref_b_0 *)&b, 570425344));
a = 20;
(b.__forwarding->b) = 40;
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
}
- 定义Block
void (*testBlock)(int) = ((void (*)(int))&__testFunc_block_impl_0((void *)__testFunc_block_func_0, &__testFunc_block_desc_0_DATA, a, (__Block_byref_b_0 *)&b, 570425344));
testBlock实际上是一个指针,指向一个通过构造函数实例化的__testFunc_block_impl_0结构体实例。初始化时需要两个参数:
__testFunc_block_func_0:block块的函数指针;
__testFunc_block_desc_0_DATA:作为静态全局变量初始化__main_block_desc_0的结构体实例指针。
- 调用Block
((void (*)(__block_impl *, int))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock, 30);
上面方法调用通过testBlock->FuncPtr指针找到__blockTest_block_func_0函数并且转成(void (*)(__block_impl *, int))类型,然后将((__block_impl *) testBlock)作为参数传给这个函数进行调用。
2. 变量的截获(__block)
文章开始处的testFunc()中定义了两个变量:
int a =10;
__block int b = 20;
经过clang命令处理后的.cpp文件的__testFunc_block_func_0中,又看到以下代码:
__Block_byref_b_0 *b = __cself->b; // bound by ref
int a = __cself->a; // bound by copy
在clang后的testFunc()中,__block int b = 20;变成了以下代码:
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
其中,还多了针对 b 的结构体 __Block_byref_b_0 的定义:
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
int b;
};
可见,日常使用block想改变外部变量的值需要加__block修饰,原因就在于:
block截获普通变量时,是值传递;
截获__block修饰的变量时,是地址传递;
另外,还可以试图定义静态局部变量、全局变量等,来观察clang后代码的变化。
静态局部变量也变成指针;全局变量仍然为全局直接作参传递。
3. 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中寻找弱引用的依赖对象,进行清除处理。
- __strong(block执行过程中的强制持有)
当在block外使用 __weak 修饰对象,虽然可以防止循环引用,但是这个对象在block正在执行的过程中也可能会变成nil,这就可能会带来一些未知问题。这种情况一般在多线程回调过程中比较常见,如子线程处理完任务后回调时,不能保证block中的原对象还未被释放。
TestObj *obj = [[TestObj alloc] init];
__weak TestObj *weakObj = obj;
NSLog(@"before block retainCount:%zd",[obj arcDebugRetainCount]);
block = ^(){
NSLog(@"TestObj对象地址:%@",weakObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模拟一个耗时的任务
}
NSLog(@"耗时的任务 结束 TestObj对象地址:%@",weakObj);
});
};
NSLog(@"after block retainCount:%zd",[obj arcDebugRetainCount]);
block();
//打印结果:
//DemoWeek[19247:6816518] before block retainCount:1
//DemoWeek[19247:6816518] after block retainCount:1
//DemoWeek[19247:6816518] TestObj对象地址:<TestObj: 0x602000006af0>
//DemoWeek[19247:6816518] TestObj 对象已释放
//DemoWeek[19247:6816544] 耗时的任务 结束 TestObj对象地址:(null)
正确的做法是,在block执行的开始检验弱引用的对象是否还存在,如果还存在则使用__strong进行强引用。这样,在block执行的整个过程中,该对象就不会被置为nil,而在block执行完毕后,对象的引用计数就会-1,这样就不会导致对象无法释放。
block = ^(){
__strong TestObj *strongObj = weakObj;
if(! strongObj) return;
NSLog(@"TestObj对象地址:%@",strongObj);
dispatch_async(dispatch_queue_create(DISPATCH_QUEUE_PRIORITY_DEFAULT, NULL), ^{
for (int i = 0; i < 1000000; i++) {
// 模拟一个耗时的任务
}
NSLog(@"耗时的任务 结束 TestObj对象地址:%@",strongObj);
});
//打印结果:
//DemoWeek[19280:6819437] before block retainCount:1
//DemoWeek[19280:6819437] after block retainCount:1
//DemoWeek[19280:6819437] TestObj对象地址:<TestObj: 0x602000006b30>
//DemoWeek[19280:6819464] 耗时的任务 结束 TestObj对象地址:<TestObj: 0x602000006b30>
//DemoWeek[19280:6819464] TestObj 对象已释放
4. block 的存储区域
上面代码中结构体__block_impl中的isa指针指向_NSConcreteStackBlock类型的类对象,其实该类对象总共有三种类型:
_NSConcreteStackBlock // 栈
_NSConcreteMallocBlock // 堆
_NSConcreteGlobalBlock // 数据区
block不同存储区域,实例如下:
void (^globalBlock)(void) = ^{
NSLog(@"Global Block");
};
int main() {
globalBlock();
NSLog(@"%@",[globalBlock class]); // 打印:__NSGlobalBlock__
void (^stackBlock)(void) = ^{ // 没有截获自动变量的Block
NSLog(@"Stack Block");
};
stackBlock();
NSLog(@"%@",[stackBlock class]); // 打印:__NSStackBlock__
int i = 1;
void (^mallocBlock)(void) = ^{ // 截获自动变量i的Block
NSLog(@"Malloc Block:%d", i);
};
mallocBlock();
NSLog(@"%@",[mallocBlock class]);//打印:__NSMallocBlock__
}
block 是一个对象,所以 block 理论上是可以 retain/release 的,但是 block 在创建的时候它的内存是默认分配在栈(stack)上,而不是堆(heap)上的,所以它的作用域仅限创建时候的上下文(函数,方法···),当你在该作用域外调用该 block 时,程序就会奔溃.
如果其所属的栈作用域结束,该block就会被废弃,对于超出block作用域仍需使用block的情况,从clang过的block相关代码中可见,其提供了将block从栈上复制到堆上的方法来解决这种问题。即便Block栈作用域已结束,但被拷贝到堆上的block还可以继续存在。
复制到堆上的block实例,会修改其成员变量isa为_NSConcreteMallocBlock类对象:
impl.isa = &_NSConcreteMallocBlock;
在ARC有效时,大多数情况下编译器会进行判断,自动生成将Block从栈上复制到堆上的代码,以下几种情况栈上的block会自动复制到堆上:
调用block的copy方法;
将block作为函数返回值时;
将block赋值给__strong修改的变量时;
向Cocoa框架含有usingBlock的方法或者GCD的API传递Block参数时。
非ARC情况下向方法的参数中传递block,则需要开发者手动调用copy方法复制。
网友评论