上一篇文章我們探究了一下__block變量的存儲域。這一篇文章我們研究一下Block是如何截獲對象的。
一甥郑、棧block截獲對象
首先我們看一下棧block截獲對象會是什么情況。
1.1 棧block截獲__strong對象
OC代碼如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
NSLog(@"step1 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
void (^__weak block)(void) = ^{
NSLog(@"step3 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
};
NSLog(@"step2 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
block();
NSLog(@"step4 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
NSLog(@"step5 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
return 0;
}
控制臺打印結(jié)果如下:
2019-07-09 16:28:03.143161+0800 BlockDemo[28846:845053] step1 -- arrM的引用計數(shù)為:1
2019-07-09 16:28:03.143340+0800 BlockDemo[28846:845053] step2 -- arrM的引用計數(shù)為:2
2019-07-09 16:28:03.143356+0800 BlockDemo[28846:845053] step3 -- arrM的引用計數(shù)為:2
2019-07-09 16:28:03.143389+0800 BlockDemo[28846:845053] step4 -- arrM的引用計數(shù)為:2
2019-07-09 16:28:03.143420+0800 BlockDemo[28846:845053] step5 -- arrM的引用計數(shù)為:2
我們可以看到,step1到step2過程中arrM對象引用計數(shù)+1携茂,那么為什么會出現(xiàn)這個情況呢?我們clang一下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 被強(qiáng)引用了
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
結(jié)構(gòu)體中的成員NSMutableArray *__strong arrM;
會強(qiáng)引用arrM
對象诅岩,在實例化__main_block_impl_0
結(jié)構(gòu)體的時候讳苦,會調(diào)用objc_retain
函數(shù)使arrM
對象的引用計數(shù)+1带膜。
那么為什么不是__main_block_func_0
函數(shù)中第一句NSMutableArray *__strong arrM = __cself->arrM; // bound by copy
使arrM
對象的引用計數(shù)+1呢?我們稍微修改一下OC代碼鸳谜,也就是把block();
block的調(diào)用注釋掉膝藕,代碼如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
NSLog(@"step1 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
void (^__weak block)(void) = ^{
NSLog(@"step3 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
};
NSLog(@"step2 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
// block();
NSLog(@"step4 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
NSLog(@"step5 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
return 0;
}
我們再看一下控制臺的打印情況:
2019-07-20 00:50:32.337811+0800 BlockDemo[3835:360334] step1 -- arrM的引用計數(shù)為:1
2019-07-20 00:50:32.338017+0800 BlockDemo[3835:360334] step2 -- arrM的引用計數(shù)為:2
2019-07-20 00:50:32.338032+0800 BlockDemo[3835:360334] step4 -- arrM的引用計數(shù)為:2
2019-07-20 00:50:32.338042+0800 BlockDemo[3835:360334] step5 -- arrM的引用計數(shù)為:2
em...說明確實是在實例化__main_block_impl_0
結(jié)構(gòu)體的時候,強(qiáng)引用的arrM
對象咐扭。但是為什么NSMutableArray *__strong arrM = __cself->arrM; // bound by copy
沒有使arrM
引用計數(shù)+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的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
void (^__weak block)(void) = ^{
NSLog(@"step3 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
};
NSLog(@"step2 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
block();
NSLog(@"step4 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
NSLog(@"step5 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
return 0;
}
控制臺打印結(jié)果如下:
2019-07-09 17:13:36.327298+0800 BlockDemo[31615:970330] step1 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327448+0800 BlockDemo[31615:970330] step2 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327459+0800 BlockDemo[31615:970330] step3 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327467+0800 BlockDemo[31615:970330] step4 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:13:36.327475+0800 BlockDemo[31615:970330] step5 -- weakArrM的引用計數(shù)為:2
我們可以看到草描,step1到step2過程中weakArrM
對象引用計數(shù)不變览绿,我們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
結(jié)構(gòu)體中的成員__weak typeof(NSMutableArray *) weakArrM;
弱引用weakArrM
對象穗慕,所以在實例化__main_block_impl_0
結(jié)構(gòu)體的時候饿敲,weakArrM
對象的引用計數(shù)不會+1;
二逛绵、堆block截獲對象
接下來我們看一下堆block截獲對象會是什么情況怀各。
2.1 堆block截獲__strong對象
OC代碼如下:
int main(int argc, const char * argv[]) {
NSMutableArray *arrM = [[NSMutableArray alloc] init];
NSLog(@"step1 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
void (^block)(void) = ^{
NSLog(@"step3 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
};
NSLog(@"step2 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
block();
NSLog(@"step4 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
NSLog(@"step5 -- arrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(arrM))));
return 0;
}
控制臺打印如下:
2019-07-09 16:26:17.214917+0800 BlockDemo[28763:838840] step1 -- arrM的引用計數(shù)為:1
2019-07-09 16:26:17.215060+0800 BlockDemo[28763:838840] step2 -- arrM的引用計數(shù)為:3
2019-07-09 16:26:17.215071+0800 BlockDemo[28763:838840] step3 -- arrM的引用計數(shù)為:3
2019-07-09 16:26:17.215080+0800 BlockDemo[28763:838840] step4 -- arrM的引用計數(shù)為:3
2019-07-09 16:26:17.215087+0800 BlockDemo[28763:838840] step5 -- arrM的引用計數(shù)為:3
我們知道了,__strong對象被棧block捕獲時术浪,引用計數(shù)會+1瓢对,可是被堆block捕獲時,為什么arrM
的引用計數(shù)多加了1呢胰苏?我們clang看一下:
// __main_block_desc_0結(jié)構(gòu)體
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函數(shù)
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函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->arrM, 3/*BLOCK_FIELD_IS_OBJECT*/);}
我們知道硕蛹,棧block復(fù)制到堆上的時候,__main_block_desc_0
結(jié)構(gòu)體多了void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
和void (*dispose)(struct __main_block_impl_0*);
兩個函數(shù)指針成員硕并,為了控制捕獲的對象的生命周期法焰。在棧block復(fù)制到堆上的時候,對__strong對象引用計數(shù)+1倔毙,堆block銷毀的時候埃仪,__strong對象引用計數(shù)-1。為了保證捕獲的__strong對象不會在堆block銷毀之前引用計數(shù)變?yōu)?而銷毀陕赃。copy
函數(shù)指針指向的__main_block_copy_0
函數(shù)的內(nèi)部實際上調(diào)用了_Block_object_assign
函數(shù)卵蛉,_Block_object_assign
源碼地址。我們來看一下_Block_object_assign
函數(shù)的內(nèi)部實現(xiàn):
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 引用計數(shù)+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是不會對這個對象引用計數(shù)+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
內(nèi)調(diào)用了_Block_retain_object(object);
函數(shù),使arrM
引用計數(shù)+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的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
void (^block)(void) = ^{
NSLog(@"step3 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
};
NSLog(@"step2 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
block();
NSLog(@"step4 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
NSLog(@"step5 -- weakArrM的引用計數(shù)為:%@", @(CFGetRetainCount((__bridge CFTypeRef)(weakArrM))));
return 0;
}
控制臺打印如下:
2019-07-09 17:34:54.507969+0800 BlockDemo[32534:1023819] step1 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508120+0800 BlockDemo[32534:1023819] step2 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508131+0800 BlockDemo[32534:1023819] step3 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508139+0800 BlockDemo[32534:1023819] step4 -- weakArrM的引用計數(shù)為:2
2019-07-09 17:34:54.508146+0800 BlockDemo[32534:1023819] step5 -- weakArrM的引用計數(shù)為:2
我們發(fā)現(xiàn)堆block截獲__weak對象的結(jié)果和棧block截獲__weak對象的結(jié)果一致桑滩。原因有兩點:
1、 __main_block_impl_0
結(jié)構(gòu)體中的成員__weak typeof(NSMutableArray *) weakArrM;
弱引用weakArrM
對象,所以在實例化__main_block_impl_0
結(jié)構(gòu)體的時候运准,weakArrM
對象的引用計數(shù)不會+1幌氮;
2、 堆block拷貝__weak對象時調(diào)用objc_copyWeak
函數(shù)胁澳,objc_copyWeak
函數(shù)如下:
void objc_copyWeak(id *dst, id *src)
{
// 根據(jù)scr獲取指向的對象obj该互,retain obj
// 函數(shù)取出附有__weak修飾符變量所引用的對象并retain 引用計數(shù)+1
id obj = objc_loadWeakRetained(src);
// 調(diào)用objc_initWeak 方法
objc_initWeak(dst, obj);
// release obj 引用計數(shù)-1
objc_release(obj);
}
該函數(shù)會先調(diào)用objc_loadWeakRetained
取得對象(同時也導(dǎo)致導(dǎo)致引用計數(shù)+1), 然后調(diào)用objc_initWeak
,將weakArrM
這個變量指向取得的對象,最終調(diào)用一次 release,引用計數(shù)-1韭畸,一前一后相互抵消宇智,最終引用計數(shù)沒變。
三胰丁、總結(jié)
我們總結(jié)一下随橘,ARC環(huán)境下:
- 棧block和堆block捕獲__strong對象時,都會先強(qiáng)引用__strong對象锦庸,使其引用計數(shù)+1机蔗,堆block在從棧復(fù)制到堆的過程中又拷貝了一遍__strong對象,為的是延長__strong對象的生命周期甘萧。
- 棧block和堆block捕獲__weak對象時萝嘁,只是會處理__weak對象的弱引用,并沒有使__weak對象的引用計數(shù)改變(要是能改的話扬卷,__weak和__strong豈不是笑話...)牙言。