一些關(guān)鍵點(diǎn):
block 是在棧上創(chuàng)建的
block 可以復(fù)制到堆上
Block會(huì)捕獲棧上的變量(或指針),將其復(fù)制為自己私有的const(變量)。
(如果在Block中修改Block塊外的)棧上的變量和指針,那么這些變量和指針必須用__block關(guān)鍵字申明(譯者注:否則就會(huì)跟上面的情況一樣只是捕獲他們的瞬時(shí)值)。
如果 block 沒(méi)有在其他地方被保持,那么它會(huì)隨著棧生存并且當(dāng)棧幀(stack frame)返回的時(shí)候消失。僅存在于棧上時(shí),block對(duì)對(duì)象訪問(wèn)的內(nèi)存管理和生命周期沒(méi)有任何影響。
如果 block 需要在棧幀返回的時(shí)候存在,它們需要明確地被復(fù)制到堆上,這樣,block 會(huì)像其他 Cocoa 對(duì)象一樣增加引用計(jì)數(shù)。當(dāng)它們被復(fù)制的時(shí)候,它會(huì)帶著它們的捕獲作用域一起,retain 他們所有引用的對(duì)象。
如果一個(gè) block引用了一個(gè)棧變量或指針,那么這個(gè)block初始化的時(shí)候會(huì)擁有這個(gè)變量或指針的const副本,所以(被捕獲之后再在棧中改變這個(gè)變量或指針的值)是不起作用的。(譯者注:所以這時(shí)候我們?cè)赽lock中對(duì)這種變量進(jìn)行賦值會(huì)編譯報(bào)錯(cuò):Variable is not assignable(missing __block type specifier),因?yàn)樗麄兪歉北径沂莄onst的.具體見(jiàn)下面的例程)。
當(dāng)一個(gè) block 被復(fù)制后,__block 聲明的棧變量的引用被復(fù)制到了堆里,復(fù)制完成之后,無(wú)論是棧上的block還是剛剛產(chǎn)生在堆上的block(棧上block的副本)都會(huì)引用該變量在堆上的副本。
...
CGFloat blockInt = 10;
void (^playblock)(void) = ^{
NSLog(@"blockInt = %zd", blockInt);
};
blockInt ++;
playblock();
...
//結(jié)果為:blockInt = 10
最重要的事情是 __block 聲明的變量和指針在 block 里面是作為顯示操作真實(shí)值/對(duì)象的結(jié)構(gòu)來(lái)對(duì)待的。
block 在 Objective-C 的 runtime(運(yùn)行時(shí)) 里面被當(dāng)作一等公民對(duì)待:他們有一個(gè) isa 指針,一個(gè)類(lèi)也是用 isa 指針在Objective-C 運(yùn)行時(shí)來(lái)訪問(wèn)方法和存儲(chǔ)數(shù)據(jù)的。在非 ARC 環(huán)境肯定會(huì)把它搞得很糟糕,并且懸掛指針會(huì)導(dǎo)致 crash。__block 僅僅對(duì) block 內(nèi)的變量起作用,它只是簡(jiǎn)單地告訴 block:
嗨,這個(gè)指針或者原始的類(lèi)型依賴(lài)它們?cè)诘臈?。?qǐng)用一個(gè)棧上的新變量來(lái)引用它。我是說(shuō),請(qǐng)對(duì)它進(jìn)行雙重解引用,不要 retain 它。 謝謝,哥們。
如果在定義之后但是 block 沒(méi)有被調(diào)用前,對(duì)象被釋放了,那么 block 的執(zhí)行會(huì)導(dǎo)致 crash。 __block 變量不會(huì)在 block 中被持有,最后... 指針、引用、解引用以及引用計(jì)數(shù)變得一團(tuán)糟。
self 的循環(huán)引用
當(dāng)使用代碼塊和異步分發(fā)的時(shí)候,要注意避免引用循環(huán)。 總是使用 weak 來(lái)引用對(duì)象,避免引用循環(huán)。(譯者注:這里更為優(yōu)雅的方式是采用影子變量@weakify/@strongify 這里有更為詳細(xì)的說(shuō)明) 此外,把持有 block 的屬性設(shè)置為 nil (比如 self.completionBlock = nil) 是一個(gè)好的實(shí)踐。它會(huì)打破 block 捕獲的作用域帶來(lái)的引用循環(huán)。
例子:
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
}];
不要這樣:
[self executeBlock:^(NSData *data, NSError *error) {
[self doSomethingWithData:data];
}];
多個(gè)語(yǔ)句的例子:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
__strong __typeof(weakSelf) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomethingWithData:data];
[strongSelf doSomethingWithData:data];
}
}];
不要這樣:
__weak __typeof(self)weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
[weakSelf doSomethingWithData:data];
}];
你應(yīng)該把這兩行代碼作為 snippet 加到 Xcode 里面并且總是這樣使用它們。
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
這里我們來(lái)討論下 block 里面的 self 的 __weak 和 __strong 限定詞的一些微妙的地方。簡(jiǎn)而言之,我們可以參考 self 在 block 里面的三種不同情況。
直接在 block 里面使用關(guān)鍵詞 self
在 block 外定義一個(gè) __weak 的 引用到 self,并且在 block 里面使用這個(gè)弱引用
在 block 外定義一個(gè) __weak 的 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè) __strong 的引用。
方案 1. 直接在 block 里面使用關(guān)鍵詞 self
如果我們直接在 block 里面用 self 關(guān)鍵字,對(duì)象會(huì)在 block 的定義時(shí)候被 retain,(實(shí)際上 block 是 copied 但是為了簡(jiǎn)單我們可以忽略這個(gè))。一個(gè) const 的對(duì) self 的引用在 block 里面有自己的位置并且它會(huì)影響對(duì)象的引用計(jì)數(shù)。如果這個(gè)block被其他的類(lèi)使用并且(或者)彼此間傳來(lái)傳去,我們可能想要在 block 中保留 self,就像其他在 block 中使用的對(duì)象一樣. 因?yàn)樗麄兪莃lock執(zhí)行所需要的.
dispatch_block_t completionBlock = ^{
NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:completionHandler];
沒(méi)啥大不了。但是如果通過(guò)一個(gè)屬性中的 self 保留 了這個(gè) block(就像下面的例程一樣),對(duì)象( self )保留了 block 會(huì)怎么樣呢?
self.completionHandler = ^{
NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:self.completionHandler];
這就是有名的 retain cycle, 并且我們通常應(yīng)該避免它。這種情況下我們收到 CLANG 的警告:
Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 里面發(fā)現(xiàn)了 self 的強(qiáng)引用,可能會(huì)導(dǎo)致循環(huán)引用)
所以 __weak 就有用武之地了。
方案 2. 在 block 外定義一個(gè) __weak 的 引用到 self,并且在 block 里面使用這個(gè)弱引用
這樣會(huì)避免循壞引用,也是通常情況下我們的block作為類(lèi)的屬性被self retain 的時(shí)候會(huì)做的。
__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
NSLog(@"%@", weakSelf);
};
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:self.completionHandler];
這個(gè)情況下 block 沒(méi)有 retain 對(duì)象并且對(duì)象在屬性里面 retain 了 block 。所以這樣我們能保證了安全的訪問(wèn) self。 不過(guò)糟糕的是,它可能被設(shè)置成 nil 的。問(wèn)題是:如何讓 self 在 block 里面安全地被銷(xiāo)毀。
考慮這么個(gè)情況:block 作為屬性(property)賦值的結(jié)果,從一個(gè)對(duì)象被復(fù)制到另一個(gè)對(duì)象(如 myController),在這個(gè)復(fù)制的 block 執(zhí)行之前,前者(即之前的那個(gè)對(duì)象)已經(jīng)被解除分配。
下面的更有意思。
方案 3. 在 block 外定義一個(gè) __weak 的 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè) __strong 的引用
你可能會(huì)想,首先,這是避免 retain cycle 警告的一個(gè)技巧。
這不是重點(diǎn),這個(gè) self 的強(qiáng)引用是在block 執(zhí)行時(shí) 被創(chuàng)建的,但是否使用 self 在 block 定義時(shí)就已經(jīng)定下來(lái)了, 因此self (在block執(zhí)行時(shí)) 會(huì)被 retain.
Apple 文檔 中表示 "為了 non-trivial cycles ,你應(yīng)該這樣" :
MyViewController *myController = [[MyViewController alloc] init...];
// ...
MyViewController * __weak weakMyController = myController;
myController.completionHandler = ^(NSInteger result) {
MyViewController *strongMyController = weakMyController;
if (strongMyController) {
// ...
[strongMyController dismissViewControllerAnimated:YES completion:nil];
// ...
}
else {
// Probably nothing...
}
};
首先,我覺(jué)得這個(gè)例子看起來(lái)是錯(cuò)誤的。如果 block 本身在 completionHandler 屬性中被 retain 了,那么 self 如何被 delloc 和在 block 之外賦值為 nil 呢? completionHandler 屬性可以被聲明為 assign 或者 unsafe_unretained 的,來(lái)允許對(duì)象在 block 被傳遞之后被銷(xiāo)毀。
我不能理解這樣做的理由,如果其他對(duì)象需要這個(gè)對(duì)象(self),block 被傳遞的時(shí)候應(yīng)該 retain 對(duì)象,所以 block 應(yīng)該不被作為屬性存儲(chǔ)。這種情況下不應(yīng)該用 __weak/__strong
總之,其他情況下,希望 weakSelf 變成 nil 的話(huà),就像第二種情況解釋那么寫(xiě)(在 block 之外定義一個(gè)弱應(yīng)用并且在 block 里面使用)。
還有,Apple的 "trivial block" 是什么呢。我們的理解是 trivial block 是一個(gè)不被傳送的 block ,它在一個(gè)良好定義和控制的作用域里面,weak 修飾只是為了避免循環(huán)引用。
雖然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 討論的 一 些 的 在線 參考, Matt Galloway 的 (Effective Objective-C 2.0 和 Pro Multithreading and Memory Management for iOS and OS X ,大多數(shù)開(kāi)發(fā)者始終沒(méi)有弄清楚概念。
在 block 內(nèi)用強(qiáng)引用的優(yōu)點(diǎn)是,搶占執(zhí)行的時(shí)候的魯棒性。在 block 執(zhí)行的時(shí)候, 再次溫故下上面的三個(gè)例子:
方案 1. 直接在 block 里面使用關(guān)鍵詞 self
如果 block 被屬性 retain,self 和 block 之間會(huì)有一個(gè)循環(huán)引用并且它們不會(huì)再被釋放。如果 block 被傳送并且被其他的對(duì)象 copy 了,self 在每一個(gè) copy 里面被 retain
方案 2. 在 block 外定義一個(gè) __weak 的 引用到 self,并且在 block 里面使用這個(gè)弱引用
不管 block 是否通過(guò)屬性被 retain ,這里都不會(huì)發(fā)生循環(huán)引用。如果 block 被傳遞或者 copy 了,在執(zhí)行的時(shí)候,weakSelf 可能已經(jīng)變成 nil。
block 的執(zhí)行可以搶占,而且對(duì) weakSelf 指針的調(diào)用時(shí)序不同可以導(dǎo)致不同的結(jié)果(如:在一個(gè)特定的時(shí)序下 weakSelf 可能會(huì)變成nil)。
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption, weakSelf turned nil
[weakSelf doSomethingElse]; // weakSelf == nil
};
方案 3. 在 block 外定義一個(gè) __weak 的 引用到 self,并在在 block 內(nèi)部通過(guò)這個(gè)弱引用定義一個(gè) __strong 的引用。
不管 block 是否通過(guò)屬性被 retain ,這里也不會(huì)發(fā)生循環(huán)引用。如果 block 被傳遞到其他對(duì)象并且被復(fù)制了,執(zhí)行的時(shí)候,weakSelf 可能被nil,因?yàn)閺?qiáng)引用被賦值并且不會(huì)變成nil的時(shí)候,我們確保對(duì)象 在 block 調(diào)用的完整周期里面被 retain了,如果搶占發(fā)生了,隨后的對(duì) strongSelf 的執(zhí)行會(huì)繼續(xù)并且會(huì)產(chǎn)生一樣的值。如果 strongSelf 的執(zhí)行到 nil,那么在 block 不能正確執(zhí)行前已經(jīng)返回了。
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomething]; // strongSelf != nil
// preemption, strongSelf still not nil(搶占的時(shí)候,strongSelf 還是非 nil 的)
[strongSelf doSomethingElse]; // strongSelf != nil
}
else {
// Probably nothing...
return;
}
};
在ARC條件中,如果嘗試用 -> 符號(hào)訪問(wèn)一個(gè)實(shí)例變量,編譯器會(huì)給出非常清晰的錯(cuò)誤信息:
Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (對(duì)一個(gè) __weak 指針的解引用不允許的,因?yàn)榭赡茉诟?jìng)態(tài)條件里面變成 null, 所以先把他定義成 strong 的屬性)
可以用下面的代碼展示
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
id localVal = weakSelf->someIVar;
};
在最后
方案 1: 只能在 block 不是作為一個(gè) property 的時(shí)候使用,否則會(huì)導(dǎo)致 retain cycle。
方案 2: 當(dāng) block 被聲明為一個(gè) property 的時(shí)候使用。
方案 3: 和并發(fā)執(zhí)行有關(guān)。當(dāng)涉及異步的服務(wù)的時(shí)候,block 可以在之后被執(zhí)行,并且不會(huì)發(fā)生關(guān)于 self 是否存在的問(wèn)題。