面試題
block的原理是怎樣的?本質是什么?
__block的作用是什么?有什么使用注意點?
block的屬性修飾詞為什么是copy?使用block有哪些使用注意?
block在修改NSMutableArray,需不需要添加__block?
首先對block有一個基本的認識
block本質上也是一個oc對象,他內(nèi)部也有一個isa指針。block是封裝了函數(shù)調用以及函數(shù)調用環(huán)境的OC對象。
block中各個結構體之間的關系如下圖:

block底層數(shù)據(jù)結構如下圖:

為了保證block內(nèi)部能夠正常訪問外部的變量,block有一個變量捕獲機制:
局部變量都會被block捕獲,自動變量是值捕獲,靜態(tài)變量為地址捕獲。全局變量則不會被block捕獲;
block最終都是繼承自NSBlock類型,而NSBlock繼承于NSObjcet。那么block其中的isa指針其實是來自NSObject中的。這也更加印證了block的本質其實就是OC對象。
block調用copy會改變block類型嗎?

所以在平時開發(fā)過程中MRC環(huán)境下經(jīng)常需要使用copy來保存block,將棧上的block拷貝到堆中,即使棧上的block被銷毀,堆上的block也不會被銷毀,需要我們自己調用release操作來銷毀。而在ARC環(huán)境下回系統(tǒng)會自動copy,是block不會被銷毀。
什么情況下?ARC會對block做自動copy操作
block作為返回值時,比如使用“=”;
將block賦值給__strong指針時;
block作為Cocoa API中方法名含有usingBlock的方法參數(shù)時;
block作為GCD API的方法參數(shù)時;
總結
一旦block中捕獲的變量為對象類型,block結構體中的__main_block_desc_0會出兩個參數(shù)copy和dispose。因為訪問的是個對象,block希望擁有這個對象,就需要對對象進行引用,也就是進行內(nèi)存管理的操作。比如說對對象進行retarn操作,因此一旦block捕獲的變量是對象類型就會會自動生成copy和dispose來對內(nèi)部引用的對象進行內(nèi)存管理。
當block內(nèi)部訪問了對象類型的auto變量時,如果block是在棧上,block內(nèi)部不會對person產(chǎn)生強引用。不論block結構體內(nèi)部的變量是__strong修飾還是__weak修飾,都不會對變量產(chǎn)生強引用。
如果block被拷貝到堆上。copy函數(shù)會調用_Block_object_assign函數(shù),根據(jù)auto變量的修飾符(__strong,__weak,unsafe_unretained)做出相應的操作,形成強引用或者弱引用
如果block從堆中移除,dispose函數(shù)會調用_Block_object_dispose函數(shù),自動釋放引用的auto變量
- 下列代碼person在何時銷毀 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",person);
});
NSLog(@"touchBegin----------End");
}
分析:在block執(zhí)行完畢后被釋放;在ARC環(huán)境中,block作為GCD API的方法參數(shù)時會自動進行copy操作,因此block在堆空間,并且使用強引用訪問person對象,因此block內(nèi)部copy函數(shù)會對person進行強引用。當block執(zhí)行完畢需要被銷毀時,調用dispose函數(shù)釋放對person對象的引用,person沒有強指針指向時才會被銷毀。
2、下列代碼person在何時銷毀 ?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
Person *person = [[Person alloc] init];
__weak Person *waekP = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",waekP);
});
NSLog(@"touchBegin----------End");
}
分析:person先銷毀,才執(zhí)行block,打印為空;block中對waekP為__weak弱引用,因此block內(nèi)部copy函數(shù)會對person同樣進行弱引用,當大括號執(zhí)行完畢時,person對象沒有強指針引用就會被釋放。因此block塊執(zhí)行的時候打印null。
block中修改變量的值:
方式一:age使用static修飾。
前文提到過static修飾的age變量傳遞到block內(nèi)部的是指針,在__main_block_func_0函數(shù)內(nèi)部就可以拿到age變量的內(nèi)存地址,因此就可以在block內(nèi)部修改age的值。
方式二:__block
__block用于解決block內(nèi)部不能修改auto變量值的問題,__block不能修飾靜態(tài)變量(static) 和全局變量
__block為什么能修改變量的值?
__block將變量包裝成對象,然后在把age封裝在結構體里面,block內(nèi)部存儲的變量為結構體指針,也就可以通過指針找到內(nèi)存地址進而修改變量的值。
3、以下代碼是否可以正確執(zhí)行
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
Block block = ^{
[array addObject: @"5"];
[array addObject: @"5"];
NSLog(@"%@",array);
};
block();
}
return 0;
}
分析:可以正確執(zhí)行,因為在block塊中僅僅是使用了array的內(nèi)存地址,往內(nèi)存地址中添加內(nèi)容,并沒有修改arry的內(nèi)存地址,因此array不需要使用__block修飾也可以正確編譯。因此當僅僅是使用局部變量的內(nèi)存地址,而不是修改的時候,盡量不要添加__block,通過上述分析我們知道一旦添加了__block修飾符,系統(tǒng)會自動創(chuàng)建相應的結構體,占用不必要的內(nèi)存空間。
解決循環(huán)引用問題 - ARC
使用__weak 和 __unsafe_unretained修飾符可以解決循環(huán)引用的問題;
__weak不會產(chǎn)生強引用,指向的對象銷毀時,會自動將指針置為nil。因此一般通過__weak來解決問題。
__unsafe_unretained不會產(chǎn)生前引用,不安全,指向的對象銷毀時,指針存儲的地址值不變。
解決循環(huán)引用問題 - MRC
使用__unsafe_unretained解決;