本文將圍繞以下幾個(gè)問(wèn)題分析block:
block是不是函數(shù)指針饭望?如果不是它們有什么區(qū)別固额?
block修改局部變量為什么必須要加
__block
修飾完残?block為什么會(huì)產(chǎn)生循環(huán)引用聋袋?應(yīng)該怎么避免音比?
要完全搞清楚這幾個(gè)問(wèn)題妖滔,首先我們需要搞清block的本質(zhì)是什么隧哮?
結(jié)論:在iOS中block本質(zhì)上就是一個(gè)oc對(duì)象,內(nèi)部同其他OC對(duì)象一樣有一個(gè)isa指針铛楣。block是一個(gè)將函數(shù)定義近迁、實(shí)現(xiàn)、調(diào)用封裝在一起的OC對(duì)象簸州;
第一步:先寫(xiě)一個(gè)簡(jiǎn)單的測(cè)試block
- (void)test {
void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b){
NSLog(@"block a = %ld, b = %ld",a, b);
};
block(10, 20);
}
用命令:xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc ViewController.m
將OC文件轉(zhuǎn)換成c++代碼查看OC內(nèi)部實(shí)現(xiàn)結(jié)構(gòu)
static void _I_ViewController_test(ViewController * self, SEL _cmd) {
void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));
((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}
從上述代碼可以看到鉴竭,block的定義被轉(zhuǎn)換成了
void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));
實(shí)際上是把函數(shù)__ViewController__test_block_impl_0的函數(shù)地址賦值給了block,
應(yīng)該在這段代碼之上我們可以看到__ViewController__test_block_impl_0其實(shí)是一個(gè)結(jié)構(gòu)體搏存,結(jié)構(gòu)如下:
struct __ViewController__test_block_impl_0 {
struct __block_impl impl;
struct __ViewController__test_block_desc_0* Desc;
__ViewController__test_block_impl_0(void *fp, struct __ViewController__test_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
該結(jié)構(gòu)體內(nèi)部包含了兩個(gè)變量impl
、Desc
和一個(gè)同名構(gòu)造函數(shù)__ViewController__test_block_impl_0矢洲,全局搜索__block_impl
找到其結(jié)構(gòu)如下:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
isa:類(lèi)型指針璧眠,這里代表block的類(lèi)型
Flags:這里從構(gòu)造函數(shù)的調(diào)用看到它有一個(gè)默認(rèn)值0,并未對(duì)其賦值读虏,也許在更深層有其它的用處
Reserved:應(yīng)該同F(xiàn)lags差不多在底層有使用吧
-
FuncPtr:函數(shù)指針责静,這里指向__ViewController__test_block_func_0函數(shù)
到這里足以證明block就是一個(gè)OC對(duì)象,同樣有這一個(gè)指向它類(lèi)型的isa指針盖桥;結(jié)構(gòu)體__ViewController__test_block_impl_0的下方應(yīng)該就能看到另一個(gè)變量
Desc
其結(jié)構(gòu)如下:static struct __ViewController__test_block_desc_0 { size_t reserved; size_t Block_size; } __ViewController__test_block_desc_0_DATA = { 0, sizeof(struct __ViewController__test_block_impl_0)};
__ViewController__test_block_desc_0
包含了兩個(gè)變量灾螃,并在定義時(shí)創(chuàng)建了一個(gè)__ViewController__test_block_desc_0_DATA結(jié)構(gòu)體 reserved:字面意思
保留
,應(yīng)也是一個(gè)內(nèi)部使用字段揩徊,這里能看到為其賦值0Block_size:sizeof()腰鬼,這里代表結(jié)構(gòu)體__ViewController__test_block_impl_0的內(nèi)存占用大小
void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA));
回看我們的block定義,__ViewController__test_block_impl_0調(diào)用時(shí)還傳入了一個(gè)__ViewController__test_block_func_0函數(shù)
static void __ViewController__test_block_func_0(struct __ViewController__test_block_impl_0 *__cself, NSInteger a, NSInteger b) {
// NSLog(@"block a = %ld, b = %ld",a, b); OC block 內(nèi)部代碼
NSLog((NSString *)&__NSConstantStringImpl__var_folders_dm_tq28fyls0cq42nl6fdhyqzd40000gn_T_ViewController_939d4d_mi_0,a, b);
}
從函數(shù)結(jié)構(gòu)就能看出該函數(shù)其實(shí)就是block{}里面的實(shí)現(xiàn)塑荒,因此我們得出block就是OC對(duì)象熄赡,內(nèi)部封裝了c函數(shù)定義、實(shí)現(xiàn)齿税、和函數(shù)調(diào)用彼硫;
1.那么block是不是函數(shù)指針?如果不是它們有什么區(qū)別?
綜上所述乌助,block不是函數(shù)指針溜在。函數(shù)指針是指向一段預(yù)先定義好的可執(zhí)行代碼,且該函數(shù)地址是在編譯時(shí)就已經(jīng)確定好的他托,函數(shù)內(nèi)只能訪(fǎng)問(wèn)全局變量。block是OC對(duì)象仆葡,可以在運(yùn)行時(shí)自由創(chuàng)建赏参,不同作用域內(nèi)創(chuàng)建的block對(duì)象的生命周期也不一樣,通常動(dòng)態(tài)創(chuàng)建的block都存儲(chǔ)在棧上沿盅,可以調(diào)用[block copy]
將其拷貝到堆上延長(zhǎng)生命周期把篓,另外block對(duì)局部變量擁有讀的權(quán)限,對(duì)使用__block修飾的局部變量擁有讀寫(xiě)權(quán)限
2.block修改局部變量為什么必須要加__block
修飾腰涧?
- (void)test {
int j = 10;
int g = 10;
static int s = 10;
__block int i = 10;
void(^block)(NSInteger, NSInteger) = ^(NSInteger a, NSInteger b){
i++;
s++;
self;
// j++; 報(bào)錯(cuò):Variable is not assignable (missing __block type specifier)
NSLog(@"block a = %ld, b = %ld",a, b);
NSLog(@"block i = %d, j = %d",i, j);
};
block(10, 20);
}
在原有test
方法內(nèi)新增成員變量j韧掩、g,static變量s窖铡,__block變量i
,再次執(zhí)行命令轉(zhuǎn)換為c++疗锐,結(jié)果如下:
static void _I_ViewController_test(ViewController * self, SEL _cmd) {
int j = 10;
int g = 10;
static int s = 10;
__attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 10};
void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344));
((void (*)(__block_impl *, NSInteger, NSInteger))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 20);
}
可以看到前三個(gè)變量并沒(méi)有什么變化,只有__block 變量i
被轉(zhuǎn)換成了__Block_byref_i_0
結(jié)構(gòu)體:
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
其__isa=(void*)0
,__forwarding=(__Block_byref_i_0 *)&i
指向i自身地址的指針费彼,__flags=0
,__size=sizeof(__Block_byref_i_0)
,i=10
為原來(lái)i的值滑臊,修改后block的實(shí)現(xiàn)變成如下:
struct __ViewController__test_block_impl_0 {
struct __block_impl impl;
struct __ViewController__test_block_desc_0* Desc;
int *s;
ViewController *self;
int j;
int g;
__Block_byref_i_0 *i; // by ref
__ViewController__test_block_impl_0(void *fp, struct __ViewController__test_block_desc_0 *desc, int *_s, ViewController *_self, int _j, int _g, __Block_byref_i_0 *_i, int flags=0) : s(_s), self(_self), j(_j), g(_g), i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
仔細(xì)看會(huì)發(fā)現(xiàn)在函數(shù)__ViewController__test_block_impl_0()
后面還有這么一截: s(_s), self(_self), j(_j), g(_g), i(_i->__forwarding)
這一截都是對(duì)結(jié)構(gòu)體__ViewController__test_block_impl_0內(nèi)的自定義變量進(jìn)行復(fù)制s(_s)
等價(jià)于s = _s
其他同理,結(jié)合改變后的block定義:
void(*block)(NSInteger, NSInteger) = ((void (*)(NSInteger, NSInteger))&__ViewController__test_block_impl_0((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344));
看調(diào)用__ViewController__test_block_impl_0
函數(shù)時(shí)傳入的參數(shù)((void *)__ViewController__test_block_func_0, &__ViewController__test_block_desc_0_DATA, &s, self, j, g, (__Block_byref_i_0 *)&i, 570425344)
前兩個(gè)同修改前一樣前面有介紹箍铲,從第三個(gè)開(kāi)始分別為:靜態(tài)變量s的地址雇卷、當(dāng)前類(lèi)對(duì)象self、j和g傳入的都是int類(lèi)型數(shù)值颠猴、(__block變量轉(zhuǎn)換后)結(jié)構(gòu)體i的地址关划,最后那個(gè)數(shù)字對(duì)應(yīng)的是flags
那么,block修改局部變量為什么必須要加__block
修飾翘瓮?
因?yàn)橹郏褂胈_block修飾后的變量在block內(nèi)部會(huì)創(chuàng)建一個(gè)相應(yīng)的結(jié)構(gòu)體,該結(jié)構(gòu)體內(nèi)保留了變量的值和地址春畔,block內(nèi)部還為結(jié)構(gòu)體定義了一個(gè)指針變量脱货,內(nèi)部對(duì)原來(lái)變量的讀寫(xiě)都是通過(guò)這個(gè)結(jié)構(gòu)體指針來(lái)實(shí)現(xiàn)的,所以就完成了對(duì)__block
變量的修改律姨;更多細(xì)節(jié)
3.block為什么會(huì)產(chǎn)生循環(huán)引用振峻?應(yīng)該怎么避免?
通過(guò)以上內(nèi)容分析择份,我們知道block內(nèi)會(huì)對(duì)外部變量扣孟、對(duì)象產(chǎn)生一個(gè)引用關(guān)系,OC對(duì)象在沒(méi)有指定強(qiáng)弱引用的情況下荣赶,默認(rèn)是強(qiáng)引用凤价,那么一個(gè)對(duì)象如果間接或直接擁有block鸽斟,block內(nèi)部又擁有該對(duì)象那么就會(huì)產(chǎn)生循環(huán)引用
如圖1中object----(strong)---->object1----(strong)---->block----(strong)---->object就是一個(gè)間接性引用產(chǎn)生的循環(huán)引用
解決圖1中問(wèn)題其實(shí)只需將三條箭頭中任意一條改用弱引用就能打破循環(huán)保留問(wèn)題,但在實(shí)際開(kāi)發(fā)過(guò)程中我們調(diào)用block一般都不是及時(shí)性的利诺,所以如果object----(weak)---->object1或者object1----(weak)---->block當(dāng)我們?cè)谡{(diào)用block時(shí)可能對(duì)象已經(jīng)被釋放富蓄,代碼運(yùn)行會(huì)違背我們預(yù)期效果,即解決block循環(huán)引用問(wèn)題慢逾,我們推崇將block-->object外部改成弱引用立倍,block內(nèi)部在將弱引用對(duì)象轉(zhuǎn)強(qiáng)引用避免block內(nèi)部訪(fǎng)問(wèn)還沒(méi)結(jié)束object就被釋放問(wèn)題,如圖2: