本文重點總結(jié) OC block 的原理,并帶上一些例子,不討論 block 的寫法和應(yīng)用。
block 的本質(zhì)總結(jié)如下:
- block 在底層上是一個結(jié)構(gòu)體,內(nèi)部有一個
isa
指針萍倡,指向block
所屬的類,其父類最終指向NSObject
辟汰,所以可以把block
看做對象:
void (^ blk)(void) = ^{
NSLog(@"hello");
};
NSLog(@"%@", [blk class]); // __NSGlobalBlock__
NSLog(@"%@", [[blk class] superclass]); // __NSGlobalBlock
NSLog(@"%@", [[[blk class] superclass] superclass]); // NSBlock
NSLog(@"%@", [[[[blk class] superclass] superclass] superclass]); // NSObject
- block 有三種類型:_NSConcreteGlobalBlock(全局block), _NSConcreteStackBlock (棧block), _NSConcreteMallocBlock (堆block)列敲,分別儲存在data區(qū),棧區(qū)帖汞,堆區(qū)戴而,他們的類型由創(chuàng)建方式以及創(chuàng)建時候捕獲的變量類型決定。
- 不使用外部變量的block是全局block翩蘸,使用外部變量并且未進行copy操作的block是棧block所意,對棧block進行copy操作,就是堆block催首,而對全局block進行copy扶踊,仍是全局block。
// 不使用外部變量
NSLog(@"%@", [^{
NSLog(@"globalBlock");
} class]); // __NSGlobalBlock__
// 使用外部變量并且未進行copy
NSInteger num = 10;
NSLog(@"%@", [^{
NSLog(@"stackBlock:%zd", num);
} class]); // __NSStackBlock__
// 對全局block進行copy
void (^ globalBlock)(void) = [^{
NSLog(@"globalBlock");
} copy];
NSLog(@"%@", [globalBlock class]); // __NSGlobalBlock__
// 對棧block進行copy操作
void (^ mallocBlock)(void) = [^{
NSLog(@"stackBlock:%zd", num);
} copy];
NSLog(@"%@", [mallocBlock class]); // __NSMallocBlock__
為了解決block所在變量域結(jié)束后block仍然可用的問題郎任,需要把棧block復(fù)制到堆上秧耗。
-
ARC時,在四種情況下棧block會自動復(fù)制到堆上
- 手動copy
- block作為函數(shù)返回值
- block賦值給__strong修飾的變量或Block類型成員變量時
- 向Cocoa框架含有usingBlock的方法或者GCD的API傳遞Block參數(shù)時 )
MRC只有手動copy涝滴,才會復(fù)制到堆上
block可以捕獲他周圍的變量绣版,分為:全局變量胶台、自動變量(函數(shù)棧上的默認變量)歼疮、靜態(tài)變量(函數(shù)棧上加
static
關(guān)鍵字的變量)、__block
變量(函數(shù)棧上加__block
關(guān)鍵字的變量)全局變量诈唬,因為作用域范圍廣韩脏,所以可以在block內(nèi)改變它們的值。
block捕獲的自動變量又分為基本數(shù)據(jù)類型變量(如
int
,double
)和指針型變量(如:對象)铸磅,捕獲的是自動變量的值赡矢,不能在block內(nèi)部改變自動變量的值。block捕獲的靜態(tài)變量是變量的地址阅仔。通過使用靜態(tài)變量的指針對其進行訪問吹散,可以在block內(nèi)改變值。
block變量在底層是一個結(jié)構(gòu)體八酒,也有isa指針空民,可以看成一個對象。
block捕獲
__block
變量,捕獲的是對應(yīng)結(jié)構(gòu)體的變量的地址界轩,可以在block內(nèi)直接改變值画饥。當block復(fù)制到堆上,block使用到的__block變量也會被復(fù)制到堆上并被block持有浊猾。
__block
變量結(jié)構(gòu)體內(nèi)有__forwarding
指針抖甘,棧上的__forwarding
指針會指向堆上的__forwarding
變量,而堆上的__forwarding
指針指向其自身葫慎,所以如果對__block
的修改(棧上的還是堆上的)衔彻,實際上永遠只會修改堆上的__block
變量。-
當block復(fù)制到堆上后偷办,其對獲取的變量的關(guān)系如下:->表示持有
- 自動變量:堆Block -> 變量的值 (不能改變)
- 靜態(tài)變量:堆Block -> 變量的地址(可以改變變量)
-
__block
普通基本數(shù)據(jù)類型變量:堆Block -> 堆__block
變量 (此時變量的值被復(fù)制到了堆了米奸,可以改變變量的值) -
__block __strong
對象類型變量: 堆Block -> 堆__block
變量 -> 對象 (對象本身就是在堆上,可以改變對象的值)
堆上的block如果捕獲了強引用對象爽篷,那個對象又引用了block悴晰,會照成循環(huán)引用
可以用
__weak
修飾應(yīng)用block的對象,避免循環(huán)引用假設(shè)
self
引用了block逐工,用__weak
修飾(__weak typeof(self) weakSelf = sellf;
)避免循環(huán)引用铡溪,如果block執(zhí)行完成之前,self
被釋放了泪喊,weakSelf
也會變?yōu)?nil棕硫,可能引起奔潰,我們在block中使用__strong
修飾weakSelf
保證任何情況下袒啼,self
在超出作用域后仍能夠使用哈扮,防止self
的提前釋放:__strong typeof(weakSelf) strongSelf = weakSelf