上一篇文章我们探究了一下__block变量的存储域。这一篇文章我们研究一下Block是如何截获对象的。
一、栈block截获对象
首先我们看一下栈block截获对象会是什么情况。
1.1 栈block截获__strong对象
OC代码如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
NSLog(@"step1 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
void (^__weak block)(void) = ^{
NSLog(@"step3 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
};
NSLog(@"step2 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
block();
NSLog(@"step4 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
NSLog(@"step5 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
return 0;
}
控制台打印结果如下:
2019-07-09 16:28:03.143161+0800 BlockDemo[28846:845053] step1 -- arrM的引用计数为:1
2019-07-09 16:28:03.143340+0800 BlockDemo[28846:845053] step2 -- arrM的引用计数为:2
2019-07-09 16:28:03.143356+0800 BlockDemo[28846:845053] step3 -- arrM的引用计数为:2
2019-07-09 16:28:03.143389+0800 BlockDemo[28846:845053] step4 -- arrM的引用计数为:2
2019-07-09 16:28:03.143420+0800 BlockDemo[28846:845053] step5 -- arrM的引用计数为:2
我们可以看到,step1到step2过程中arrM对象引用计数+1,那么为什么会出现这个情况呢?我们clang一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 被强引用了
NSMutableArray *__strong arrM;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *__strong _arrM, int flags=0) : arrM(_arrM) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSMutableArray *__strong arrM = __cself->arrM; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_qr_j931zwx56pl1pjydym04_8rr0000gn_T_main_4ce6e0_mi_1, ((NSNumber *(*)(Class, SEL, long))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithLong:"), (CFGetRetainCount((__bridgeCFTypeRef)(arrM)))));
}
__main_block_impl_0
结构体中的成员NSMutableArray *__strong arrM;
会强引用arrM
对象,在实例化__main_block_impl_0
结构体的时候,会调用objc_retain
函数使arrM
对象的引用计数+1。
那么为什么不是__main_block_func_0
函数中第一句NSMutableArray *__strong arrM = __cself->arrM; // bound by copy
使arrM
对象的引用计数+1呢?我们稍微修改一下OC代码,也就是把block();
block的调用注释掉,代码如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
NSLog(@"step1 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
void (^__weak block)(void) = ^{
NSLog(@"step3 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
};
NSLog(@"step2 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
// block();
NSLog(@"step4 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
NSLog(@"step5 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
return 0;
}
我们再看一下控制台的打印情况:
2019-07-20 00:50:32.337811+0800 BlockDemo[3835:360334] step1 -- arrM的引用计数为:1
2019-07-20 00:50:32.338017+0800 BlockDemo[3835:360334] step2 -- arrM的引用计数为:2
2019-07-20 00:50:32.338032+0800 BlockDemo[3835:360334] step4 -- arrM的引用计数为:2
2019-07-20 00:50:32.338042+0800 BlockDemo[3835:360334] step5 -- arrM的引用计数为:2
em...说明确实是在实例化__main_block_impl_0
结构体的时候,强引用的arrM
对象。但是为什么NSMutableArray *__strong arrM = __cself->arrM; // bound by copy
没有使arrM
引用计数+1呢?留给未来的自己去解决了。
1.2 栈block截获__weak对象
OC代码如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
__weak typeof(NSMutableArray *) weakArrM = arrM;
NSLog(@"step1 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
void (^__weak block)(void) = ^{
NSLog(@"step3 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
};
NSLog(@"step2 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
block();
NSLog(@"step4 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
NSLog(@"step5 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
return 0;
}
控制台打印结果如下:
2019-07-09 17:13:36.327298+0800 BlockDemo[31615:970330] step1 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327448+0800 BlockDemo[31615:970330] step2 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327459+0800 BlockDemo[31615:970330] step3 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327467+0800 BlockDemo[31615:970330] step4 -- weakArrM的引用计数为:2
2019-07-09 17:13:36.327475+0800 BlockDemo[31615:970330] step5 -- weakArrM的引用计数为:2
我们可以看到,step1到step2过程中weakArrM
对象引用计数不变,我们clang一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// __weak,弱引用
__weak typeof(NSMutableArray *) weakArrM;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __weak typeof(NSMutableArray *) _weakArrM, int flags=0) : weakArrM(_weakArrM) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0
结构体中的成员__weak typeof(NSMutableArray *) weakArrM;
弱引用weakArrM
对象,所以在实例化__main_block_impl_0
结构体的时候,weakArrM
对象的引用计数不会+1;
二、堆block截获对象
接下来我们看一下堆block截获对象会是什么情况。
2.1 堆block截获__strong对象
OC代码如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
NSLog(@"step1 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
void (^block)(void) = ^{
NSLog(@"step3 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
};
NSLog(@"step2 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
block();
NSLog(@"step4 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
NSLog(@"step5 -- arrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
return 0;
}
控制台打印如下:
2019-07-09 16:26:17.214917+0800 BlockDemo[28763:838840] step1 -- arrM的引用计数为:1
2019-07-09 16:26:17.215060+0800 BlockDemo[28763:838840] step2 -- arrM的引用计数为:3
2019-07-09 16:26:17.215071+0800 BlockDemo[28763:838840] step3 -- arrM的引用计数为:3
2019-07-09 16:26:17.215080+0800 BlockDemo[28763:838840] step4 -- arrM的引用计数为:3
2019-07-09 16:26:17.215087+0800 BlockDemo[28763:838840] step5 -- arrM的引用计数为:3
我们知道了,__strong对象被栈block捕获时,引用计数会+1,可是被堆block捕获时,为什么arrM
的引用计数多加了1呢?我们clang看一下:
// __main_block_desc_0结构体
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
// copy函数
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->arrM, (void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);}
// dispose函数
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);}
我们知道,栈block复制到堆上的时候,__main_block_desc_0
结构体多了void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
和void (*dispose)(struct __main_block_impl_0*);
两个函数指针成员,为了控制捕获的对象的生命周期。在栈block复制到堆上的时候,对__strong对象引用计数+1,堆block销毁的时候,__strong对象引用计数-1。为了保证捕获的__strong对象不会在堆block销毁之前引用计数变为0而销毁。copy
函数指针指向的__main_block_copy_0
函数的内部实际上调用了_Block_object_assign
函数,_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 ((flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// object 引用计数+1
_Block_retain_object(object);
// 赋值
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } 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];
********/
/// 在mrc的情况下,你对对象添加__block, block是不会对这个对象引用计数+1
*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_object_assign
内调用了_Block_retain_object(object);
函数,使arrM
引用计数+1。
2.2 堆block截获__weak对象
OC代码如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
__weak typeof(NSMutableArray *) weakArrM = arrM;
NSLog(@"step1 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
void (^block)(void) = ^{
NSLog(@"step3 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
};
NSLog(@"step2 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
block();
NSLog(@"step4 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
NSLog(@"step5 -- weakArrM的引用计数为:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
return 0;
}
控制台打印如下:
2019-07-09 17:34:54.507969+0800 BlockDemo[32534:1023819] step1 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508120+0800 BlockDemo[32534:1023819] step2 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508131+0800 BlockDemo[32534:1023819] step3 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508139+0800 BlockDemo[32534:1023819] step4 -- weakArrM的引用计数为:2
2019-07-09 17:34:54.508146+0800 BlockDemo[32534:1023819] step5 -- weakArrM的引用计数为:2
我们发现堆block截获__weak对象的结果和栈block截获__weak对象的结果一致。原因有两点:
1、 __main_block_impl_0
结构体中的成员__weak typeof(NSMutableArray *) weakArrM;
弱引用weakArrM
对象,所以在实例化__main_block_impl_0
结构体的时候,weakArrM
对象的引用计数不会+1;
2、 堆block拷贝__weak对象时调用objc_copyWeak
函数,objc_copyWeak
函数如下:
void objc_copyWeak(id *dst, id *src)
{
// 根据scr获取指向的对象obj,retain obj
// 函数取出附有__weak修饰符变量所引用的对象并retain 引用计数+1
id obj = objc_loadWeakRetained(src);
// 调用objc_initWeak 方法
objc_initWeak(dst, obj);
// release obj 引用计数-1
objc_release(obj);
}
该函数会先调用objc_loadWeakRetained
取得对象(同时也导致导致引用计数+1), 然后调用objc_initWeak
,将weakArrM
这个变量指向取得的对象,最终调用一次 release,引用计数-1,一前一后相互抵消,最终引用计数没变。
三、总结
我们总结一下,ARC环境下:
- 栈block和堆block捕获__strong对象时,都会先强引用__strong对象,使其引用计数+1,堆block在从栈复制到堆的过程中又拷贝了一遍__strong对象,为的是延长__strong对象的生命周期。
- 栈block和堆block捕获__weak对象时,只是会处理__weak对象的弱引用,并没有使__weak对象的引用计数改变(要是能改的话,__weak和__strong岂不是笑话...)。