Block
一蚪缀、什么是block
1誓竿、block是什么
下面是一個(gè)簡(jiǎn)單的block:
int main(int argc, const char * argv[]) {
@autoreleasepool {
^{printf("這是一個(gè)block");}();
}
return 0;
}
對(duì)其執(zhí)行clang -rewrite-objc編譯轉(zhuǎn)換成C++實(shí)現(xiàn)蓖扑,得到以下代碼:
struct __block_impl {
void *isa; //指向所屬類(lèi)(即block類(lèi)型)的指針,isa指針說(shuō)明block也是對(duì)象
int Flags;
int Reserved;
void *FuncPtr; //block執(zhí)行時(shí)調(diào)用的函數(shù)指針玖院,block函數(shù)的地址菠红。存儲(chǔ)著 __main_block_func_0 函數(shù)的地址
};
// main函數(shù)中第0個(gè)block,即上面代碼中的block:^{printf("這是一個(gè)block");}();
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
//結(jié)構(gòu)體的構(gòu)造函數(shù)
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block塊函數(shù)體的定義部分难菌,可以看出block的代碼塊都轉(zhuǎn)化為了普通的函數(shù)试溯,并且函數(shù)會(huì)默認(rèn)增加一個(gè)隱藏的__cself參數(shù),用來(lái)指向block對(duì)象本身郊酒。
//block代碼塊中的代碼被封裝成 __main_block_func_0 函數(shù)遇绞,F(xiàn)uncPtr則存儲(chǔ)著 __main_block_func_0 函數(shù)的地址
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("這是一個(gè)block");}
static struct __main_block_desc_0 {
size_t reserved; //保留字段
size_t Block_size; //block大小(sizeof(struct __main_block_impl_0))
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
//__main_block_impl_0的FuncPtr指向了函數(shù)__main_block_func_0
//__main_block_impl_0的Desc也指向了定義__main_block_desc_0時(shí)就創(chuàng)建的__main_block_desc_0_DATA,其中紀(jì)錄了block結(jié)構(gòu)體大小等信息
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
2燎窘、block的實(shí)際結(jié)構(gòu)
Block_private.h文件中對(duì)block的相關(guān)結(jié)構(gòu)體的真實(shí)定義:
/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src); //輔助拷貝函數(shù)摹闽,處理block范圍外的變量時(shí)使用
void (*dispose)(void *); //輔助銷(xiāo)毀函數(shù),處理block范圍外的變量時(shí)使用
};
struct Block_layout {
void *isa; //isa指針褐健,所有對(duì)象都有該指針付鹿,用于實(shí)現(xiàn)對(duì)象相關(guān)的功能。
int flags;
int reserved; //reserved,保留變量
void (*invoke)(void *, ...); //函數(shù)指針,指向具體的block實(shí)現(xiàn)的函數(shù)調(diào)用地址倘屹。block定義時(shí)內(nèi)部的執(zhí)行代碼都在這個(gè)函數(shù)中
struct Block_descriptor *descriptor; //block的詳細(xì)描述
/* Imported variables. */
};
block的結(jié)構(gòu)如下圖:
二银亲、block的類(lèi)型
block有三種類(lèi)型:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(棧)和_NSConcreteMallocBlock(堆)纽匙。其中_NSConcreteGlobalBlock和_NSConcreteStackBlock可以在代碼中創(chuàng)建务蝠。下面代碼中創(chuàng)建了一個(gè)global block和一個(gè)stack block。
typedef void(^SomeBlock)();
//global block
void (^globalBlock)(void) = ^{ printf("全局block"); };
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void (^aBlock)(void) = ^{
printf("a = %d", a);
};
//
void (^bBlock)() = ^{};
//
SomeBlock cBlock = ^{};
NSLog(@"globalBlock=%@\n,aBlock=%@\n,bBlock=%@\n,cBlock=%@\n", globalBlock,aBlock, bBlock, cBlock);
}
return 0;
}
運(yùn)行結(jié)果
2019-03-19 11:46:26.388349+0800 BlockTest[90500:9974201]
globalBlock=<__NSGlobalBlock__: 0x1000020b8>,
aBlock=<__NSStackBlock__: 0x7ffeefbff4d8>,
bBlock=<__NSGlobalBlock__: 0x100002118>,
cBlock=<__NSGlobalBlock__: 0x100002158>
Program ended with exit code: 0
如何判斷block是哪種類(lèi)型烛缔?
(1)沒(méi)有訪問(wèn)auto變量的block是NSGlobalBlock 馏段,放在數(shù)據(jù)段;
(2)訪問(wèn)了auto變量的block是NSStackBlock践瓷;
(3)[NSStackBlock copy]操作就變成了NSMallocBlock院喜。
NSConcreteMallocBlock類(lèi)型的block通常不會(huì)在源碼中直接出現(xiàn),因?yàn)槟J(rèn)它是當(dāng)一個(gè)block被copy的時(shí)候晕翠,才會(huì)將這個(gè)block復(fù)制到堆中喷舀。由于block的拷貝最終都會(huì)調(diào)用_Block_copy_internal函數(shù)(runtime.c中的_Block_copy_internal函數(shù)),所以觀察這個(gè)函數(shù)就可以知道堆中block是如何被創(chuàng)建的:
static void *_Block_copy_internal(const void *arg, const bool wantsOne) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GC) {
// GC refcounting is expensive so do most refcounting here.
if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 2)) {
// Tell collector to hang on this - it will bump the GC refcount version
_Block_setHasRefcount(aBlock, true);
}
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
// Its a stack block. Make a copy.
if (!isGC) {
// 申請(qǐng)block的堆內(nèi)存
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return NULL;
// 拷貝棧中block到剛申請(qǐng)的堆內(nèi)存中
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// 改變isa指向_NSConcreteMallocBlock淋肾,即堆block類(lèi)型
result->isa = _NSConcreteMallocBlock;
_Block_call_copy_helper(result, aBlock);
return result;
}
else {
//省略...
}
}
三硫麻、block與變量
1、block與基本數(shù)據(jù)類(lèi)型變量
void testBlockVar(void);
int c = 30; //全局變量
static int d = 40; //全局靜態(tài)變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlockVar();
}
return 0;
}
void testBlockVar() {
int a = 10; //局部變量
static int b = 20; //靜態(tài)局部變量
void (^someBlock)(void) = ^(void) {
b = 2222;
c = 33;
d = 44;
NSLog(@"a = %d, b = %d, c = %d, d = %d\n", a, b, c, d);
};
a = 11;
b = 22;
someBlock();
}
上面代碼運(yùn)行結(jié)果a = 10, b = 2222, c = 33, d = 44樊卓。編譯之后:
void testBlockVar(void);
//全局變量c和全局靜態(tài)變量d存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)拿愧,在程序結(jié)束前不會(huì)被銷(xiāo)毀,所以block直接訪問(wèn)了對(duì)應(yīng)的變量碌尔,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒(méi)有拷貝
int c = 30;
static int d = 40;
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
testBlockVar();
}
return 0;
}
//全局變量c和全局靜態(tài)變量d存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū)浇辜,在程序結(jié)束前不會(huì)被銷(xiāo)毀,所以block直接訪問(wèn)了對(duì)應(yīng)的變量唾戚,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒(méi)有拷貝
struct __testBlockVar_block_impl_0 {
struct __block_impl impl;
struct __testBlockVar_block_desc_0* Desc;
int *b; //指針傳遞柳洋。static修飾的靜態(tài)局部變量b傳遞到block內(nèi)部的是指針,在 __testBlockVar_block_func_0 函數(shù)內(nèi)部就可以拿到b的內(nèi)存地址颈走,因此就可以在block內(nèi)部修改b的值膳灶。
int a; //值傳遞
__testBlockVar_block_impl_0(void *fp, struct __testBlockVar_block_desc_0 *desc, int *_b, int _a, int flags=0) : b(_b), a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __testBlockVar_block_func_0(struct __testBlockVar_block_impl_0 *__cself) {
int *b = __cself->b; // bound by copy
int a = __cself->a; // bound by copy
(*b) = 2222;
c = 33;
d = 44;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_ccbce4_mi_0, a, (*b), c, d);
}
void testBlockVar() {
int a = 10;
static int b = 20;
void (*someBlock)(void) = ((void (*)())&__testBlockVar_block_impl_0((void *)__testBlockVar_block_func_0, &__testBlockVar_block_desc_0_DATA, &b, a));
a = 11;
b = 22;
((void (*)(__block_impl *))((__block_impl *)someBlock)->FuncPtr)((__block_impl *)someBlock);
}
查看編譯后的代碼可以得出結(jié)論:
(1)局部變量:所有在block代碼塊引用的局部變量都會(huì)成為結(jié)構(gòu)體的同名數(shù)據(jù)成員咱士,因此struct __testBlockVar_block_impl_0結(jié)構(gòu)體增加了名為int a的成員變量立由。
(2)靜態(tài)局部變量:static修飾的靜態(tài)局部變量b傳遞到block內(nèi)部的是指針,在 __testBlockVar_block_func_0 函數(shù)內(nèi)部就可以拿到b的內(nèi)存地址序厉,因此就可以在block內(nèi)部修改b的值锐膜。
(3)全局變量和全局靜態(tài)全局變量:全局變量c和全局靜態(tài)變量d存儲(chǔ)在靜態(tài)數(shù)據(jù)存儲(chǔ)區(qū),在程序結(jié)束前不會(huì)被銷(xiāo)毀弛房,所以block直接可以訪問(wèn)對(duì)應(yīng)的變量道盏,因此在結(jié)構(gòu)體__testBlockVar_block_impl_0中沒(méi)有也不需要拷貝變量作為結(jié)構(gòu)體成員變量。如下圖:
2、block與__strong和__weak變量
//main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
p.name = @"胖虎の朋友";
someBlock = ^{
NSLog(@"---block內(nèi)部:%@", p.name);
};
someBlock();
NSLog(@"======");
}
return 0;
}
//Person.m
- (void)dealloc
{
NSLog(@"Person dealloc");
}
運(yùn)行結(jié)果:
2019-03-15 16:39:18.747356+0800 BlockTest[14917:6258900] ---block內(nèi)部:胖虎の朋友
2019-03-15 16:39:18.747505+0800 BlockTest[14917:6258900] ======
Program ended with exit code: 0
大括號(hào)執(zhí)?完畢之后荷逞,p依然不不會(huì)被釋放媒咳。p為auto變量,即block有一個(gè)強(qiáng)引?指向p种远,所以block不被銷(xiāo)毀的話涩澡,p也不會(huì)銷(xiāo)毀。所以Person類(lèi)的dealloc沒(méi)有執(zhí)行坠敷。查看編譯后代碼:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__strong p; //強(qiáng)引用p
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__strong _p, int flags=0) : p(_p) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
如果為在block內(nèi)部調(diào)用的p變量添加__weak修飾符:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *p = [Person new];
p.name = @"胖虎の朋友";
__weak Person *weakP = p;
someBlock = ^{
NSLog(@"---block內(nèi)部:%@", weakP.name);
};
someBlock();
NSLog(@"======");
}
return 0;
}
運(yùn)行結(jié)果:
2019-03-15 16:41:20.836909+0800 BlockTest[14952:6266465] ---block內(nèi)部:胖虎の朋友
2019-03-15 16:41:20.837082+0800 BlockTest[14952:6266465] ======
2019-03-15 16:41:20.837098+0800 BlockTest[14952:6266465] Person dealloc
Program ended with exit code: 0
編譯之后:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
Person *__weak weakP; //weak p
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, Person *__weak _weakP, int flags=0) : weakP(_weakP) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3妙同、block與__block修飾的變量
int main(int argc, const char * argv[]) {
@autoreleasepool {
testBlock();
}
return 0;
}
void testBlock()
{
//下面分別定義各種類(lèi)型的變量
__block int b = 20; //帶__block修飾符的block普通變量
NSString *str = @"123";
__block NSString *blockStr = str; //帶__block修飾符的block OC變量
//定義一個(gè)block塊并帶一個(gè)參數(shù)
void (^someBlock)(void) = ^{
NSLog(@"b=%d, str=%@, blockStr=%@", b, str, blockStr);
};
b = 40; //修改值會(huì)影響someBlock內(nèi)的計(jì)算結(jié)果
str = @"str"; //修改值不會(huì)影響someBlock內(nèi)的計(jì)算結(jié)果
blockStr = @"blockStr"; //修改值會(huì)影響someBlock內(nèi)的計(jì)算結(jié)果
someBlock(); //執(zhí)行block代碼
}
上面代碼的運(yùn)行結(jié)果:b=40, str=123, blockStr=blockStr。由此可以看出只有帶有__block修飾符的變量b和blockStr被修改后影響了block中的值膝迎。將代碼編譯后:
//__block修飾的變量b變成了結(jié)構(gòu)體__Block_byref_b_0粥帚,內(nèi)存結(jié)構(gòu)和OC類(lèi)兼容
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;//__forwarding是指向自己在堆中的地址,訪問(wèn)時(shí)通過(guò)b->__forwarding->b保證操作的是堆中的變量b
int __flags;
int __size;
int b;//保存定義的變量b
};
//__block修飾的變量blockStr變成了結(jié)構(gòu)體__Block_byref_blockStr_1
struct __Block_byref_blockStr_1 {
void *__isa;
__Block_byref_blockStr_1 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *__strong blockStr;
};
struct __testBlock_block_impl_0 {
struct __block_impl impl;
struct __testBlock_block_desc_0* Desc;
//所有在block代碼塊引用的外部數(shù)據(jù)都會(huì)成為結(jié)構(gòu)體的同名數(shù)據(jù)成員
NSString *__strong str;
__Block_byref_b_0 *b; // by ref
__Block_byref_blockStr_1 *blockStr; // by ref
//結(jié)構(gòu)體的構(gòu)造函數(shù)
__testBlock_block_impl_0(void *fp, struct __testBlock_block_desc_0 *desc, NSString *__strong _str, __Block_byref_b_0 *_b, __Block_byref_blockStr_1 *_blockStr, int flags=0) : str(_str), b(_b->__forwarding), blockStr(_blockStr->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __testBlock_block_func_0(struct __testBlock_block_impl_0 *__cself) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
__Block_byref_blockStr_1 *blockStr = __cself->blockStr; // bound by ref
NSString *__strong str = __cself->str; // bound by copy
////b->__forwarding->b用來(lái)保證操作的始終是堆中的拷貝b限次,而不是棧中的b
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_c2a8d8_mi_1, (b->__forwarding->b), str, (blockStr->__forwarding->blockStr));
}
void testBlock()
{
//由__block修飾的變量b變成了__Block_byref_b_0對(duì)象
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 20};
NSString *str = (NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_c2a8d8_mi_0;
//由__block修飾的blockStr頁(yè)變成了__Block_byref_blockStr_1對(duì)象
__attribute__((__blocks__(byref))) __Block_byref_blockStr_1 blockStr = {(void*)0,(__Block_byref_blockStr_1 *)&blockStr, 33554432, sizeof(__Block_byref_blockStr_1), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, str};
void (*someBlock)(void) = ((void (*)())&__testBlock_block_impl_0((void *)__testBlock_block_func_0, &__testBlock_block_desc_0_DATA, str, (__Block_byref_b_0 *)&b, (__Block_byref_blockStr_1 *)&blockStr, 570425344));
}
結(jié)構(gòu)體__Block_byref_b_0中__Block_byref_b_0 *__forwarding:指向自己在堆中的地址芒涡。由此可以保證只要是使用b->_forwarding->b訪問(wèn)的都是堆中的變量b。如下圖所示:
四卖漫、block輔助函數(shù)
主要指的是copy和dispose輔助函數(shù)拖陆,負(fù)責(zé)block的拷貝和釋放。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
__block int b = 20; //block修飾的基本數(shù)據(jù)類(lèi)型
NSString *c = @"c";
someBlock = ^{
NSLog(@"a=%d, b=%d, c=%@", a, b, c);
};
someBlock();
}
return 0;
}
在捕獲變量為_(kāi)_block修飾的基本類(lèi)型懊亡,或者為對(duì)象時(shí)依啰,block才會(huì)有這兩個(gè)輔助函數(shù)。編譯之后:
//Step 3(copy操作):_Block_object_assign函數(shù)內(nèi)部會(huì)根據(jù)變量在__main_block_impl_0中的修飾符進(jìn)行引用計(jì)數(shù)器的操作店枣。如果為strong計(jì)數(shù)器+1速警,為weak則不變
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
NSString *__strong c;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, NSString *__strong _c, __Block_byref_b_0 *_b, int flags=0) : a(_a), c(_c), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
int a = __cself->a; // bound by copy
NSString *__strong c = __cself->c; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_main_4bb492_mi_1, a, (b->__forwarding->b), c);
}
//Step 2(copy操作):調(diào)用_Block_object_assign函數(shù)
//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->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_assign((void*)&dst->c, (void*)src->c, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//dispose輔助函數(shù)
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);
_Block_object_dispose((void*)src->c, 3/*BLOCK_FIELD_IS_OBJECT*/);
}
//Step 1(copy操作):調(diào)用__main_block_desc_0中的__main_block_copy_0函數(shù)
//Step 1(dispose操作):調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù);
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};
當(dāng)對(duì)block進(jìn)行copy操作時(shí)鸯两,主要執(zhí)行如下操作:
(1)調(diào)用__main_block_desc_0中的__main_block_copy_0函數(shù)闷旧;
(2)__main_block_copy_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù);
(3)_Block_object_assign函數(shù)內(nèi)部根據(jù)變量在__main_block_impl_0中的修飾符進(jìn)行引用計(jì)數(shù)器的操作钧唐,如果為strong計(jì)數(shù)器+1忙灼,為weak則不變。
當(dāng)block從堆中移除時(shí):_Block_object_dispose會(huì)斷開(kāi)對(duì)對(duì)象的引用钝侠,而對(duì)象是否被釋放取決于對(duì)象自己的引用計(jì)數(shù)该园。
(1)自動(dòng)調(diào)用__main_block_desc_0中的__main_block_dispose_0函數(shù);
(2)__main_block_dispose_0函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_dispose函數(shù)帅韧。
五里初、block與循環(huán)引用
1、場(chǎng)景一:
//main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person new];
person.name = @"啊哈哈";
person.block = ^{
NSLog(@"---%@", person.name);
};
person.block();
}
return 0;
}
//Person.h
@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) void(^block)(void);
@end
//Person.m
@implementation Person
- (void)dealloc
{
NSLog(@"Person dealloc");
}
@end
執(zhí)行結(jié)果:2019-03-15 18:27:15.358609+0800 BlockTest[16449:6652153] ---啊哈哈
person沒(méi)有被釋放忽舟,產(chǎn)生了循環(huán)引用双妨。其原因如下圖所示:
稍作修改之后:
//main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [Person new];
person.name = @"啊哈哈";
__weak Person *wp = person;
person.block = ^{
NSLog(@"---%@", wp.name);
};
person.block();
}
return 0;
}
//打印結(jié)果:
2019-03-15 18:32:37.898324+0800 BlockTest[16538:6672390] ---啊哈哈
2019-03-15 18:32:37.898488+0800 BlockTest[16538:6672390] Person dealloc
Program ended with exit code: 0
2淮阐、場(chǎng)景二:
//main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.block();
}
return 0;
}
//Person.m
- (instancetype)init
{
self = [super init];
if (self) {
self.block = ^{
NSLog(@"===%@",[self class]);
};
}
return self;
}
- (void)dealloc
{
NSLog(@"Person dealloc");
}
//運(yùn)行結(jié)果:
2019-03-15 18:38:39.059249+0800 BlockTest[16718:6698586] ===Person
Program ended with exit code: 0
編譯Person.m文件:
struct __Person__init_block_impl_0 {
struct __block_impl impl;
struct __Person__init_block_desc_0* Desc;
Person *__strong self;
__Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *__strong _self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
這?可以看到 __Person__init_block_impl_0 結(jié)構(gòu)體中創(chuàng)建了一個(gè)Person *__strong self的強(qiáng)指針指向init方法中self 指針?biāo)赶虻膒erson對(duì)象,使person引?計(jì)數(shù)+1刁品,而person對(duì)block也有?個(gè)強(qiáng)引用泣特。這?就造成了循環(huán)引?用。
原因:之前說(shuō)過(guò)block會(huì)捕獲局部變量挑随,上?的OC函數(shù)調(diào)用轉(zhuǎn)化為runtime代碼為 objc_msgSend(self, @selector(init)) 在OC的方法中有2個(gè)隱藏參數(shù)self和cmd群扶,這2個(gè)參數(shù)作為函數(shù)的形參在?法作?域中屬于局部變量, 所以在block中使用self就滿(mǎn)足之前提到的block會(huì)捕獲局部變量镀裤。解決方案如下:
//Person.m
- (instancetype)init
{
self = [super init];
if (self) {
__weak typeof(self) weakself = self;
self.block = ^{
NSLog(@"===%@\n",[weakself class]);
};
}
return self;
}
運(yùn)行結(jié)果:
2019-03-15 18:55:48.421348+0800 BlockTest[17081:6778502] ===Person
2019-03-15 18:55:48.421693+0800 BlockTest[17081:6778502] Person dealloc
Program ended with exit code: 0
編譯Person.m:
struct __Person__init_block_impl_0 {
struct __block_impl impl;
struct __Person__init_block_desc_0* Desc;
Person *__weak weakself;
__Person__init_block_impl_0(void *fp, struct __Person__init_block_desc_0 *desc, Person *__weak _weakself, int flags=0) : weakself(_weakself) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3竞阐、場(chǎng)景三:
在block中調(diào)?用super也會(huì)造成循環(huán)引用,如下:
//main.m
int main(int argc, const char * argv[]) {
@autoreleasepool {
Person *person = [[Person alloc] init];
person.block();
}
return 0;
}
//Person.m
- (instancetype)init
{
self = [super init];
if (self) {
self.block = ^{
[super init];
};
}
return self;
}
- (void)dealloc
{
NSLog(@"Person dealloc");
}
編譯后:
//當(dāng)使?[self class]時(shí)暑劝,會(huì)調(diào)用objc_msgSend函數(shù)骆莹,第一個(gè)參數(shù)receiver就是self,?第二個(gè)參數(shù)担猛,要先找到self所在的這個(gè)class的?法列表
//當(dāng)使用[super class]時(shí)幕垦,會(huì)調(diào)用objcmsgSendSuper函數(shù),此時(shí)會(huì)先構(gòu)造一個(gè) __rw_objc_super 的結(jié)構(gòu)體作為objcmsgSendSuper的第?個(gè)參數(shù)傅联。該結(jié)構(gòu)體第一個(gè)成員變量receiver仍然是self先改,而第二個(gè)成員變量super_class即是所在類(lèi)的?類(lèi)
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself)
{
((Person *(*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){
(id)self,
(id)class_getSuperclass(objc_getClass("Person"))
},
sel_registerName("init"));
}
//
struct __rw_objc_super {
struct objc_object *object;
struct objc_object *superClass;
__rw_objc_super(struct objc_object *o, struct objc_object *s) : object(o), superClass(s) {}
};
//runtime對(duì)外暴暴露露的類(lèi)型為:
//結(jié)構(gòu)體第一個(gè)成員receiver代表方法的接收者,第二個(gè)成員super_class代表方法接收者的父類(lèi)
struct objc_super {
__attribute__((objc_ownership(none))) _Nonnull id receiver;
__attribute__((objc_ownership(none))) _Nonnull Class super_class;
};
因此:
self.block = ^{
[super init];
};
轉(zhuǎn)換為源碼是:
self.block = ^{
struct objc_super superInfo = {
.receiver = self,
.super_class = class_getSuperclass(objc_getClass("Person")),
};
((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(init));
};
可以明顯看到蒸走,block強(qiáng)引用了self仇奶,而self也強(qiáng)引用了block。
解決方案:
__weak __typeof(self) weakSelf = self;
self.block = ^{
struct objc_super superInfo = {
.receiver = weakSelf,
.super_class = class_getSuperclass(NSClassFromString(@"Person")),
};
((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,@selector(class));
};
上面代碼編譯后比驻,self已經(jīng)變成了weakSelf:
static void __Person__init_block_func_0(struct __Person__init_block_impl_0 *__cself) {
Person *__weak weakSelf = __cself->weakSelf; // bound by cop
struct objc_super superInfo = {
.receiver = weakSelf,
.super_class = class_getSuperclass(NSClassFromString((NSString *)&__NSConstantStringImpl__var_folders_fb_61h7kdld7qz8hz9ph2rt22k00000gn_T_Person_d2ee36_mi_0))
};
((Class(*)(struct objc_super *, SEL))objc_msgSendSuper)(&superInfo,sel_registerName("class"));
}
參考資料:
https://opensource.apple.com/source/libclosure/libclosure-65/Block_private.h.auto.html
https://opensource.apple.com/source/libclosure/libclosure-65/runtime.c.auto.html
http://blog.devtang.com/2013/07/28/a-look-inside-blocks/