Blocks 是 Objective-C 版本的 lambda 或者 closure(閉包)。
使用 block 定義異步接口:
~~~
- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion;
~~~
當你定義一個類似上面的接口的時候,盡量使用一個單獨的 block 作為接口的最后一個參數。把需要提供的數據和錯誤信息整合到一個單獨 block 中,比分別提供成功和失敗的 block 要好。
以下是你應該這樣做的原因:
- 通常這成功處理和失敗處理會共享一些代碼(比如讓一個進度條或者提示消失);
- Apple 也是這樣做的,與平臺一致能夠帶來一些潛在的好處;
- block 通常會有多行代碼,如果不是在最后一個參數的話會打破調用點;
- 使用多個 block 作為參數可能會讓調用看起來顯得很笨拙,并且增加了復雜性。
看上面的方法,完成處理的 block 的參數很常見:第一個參數是調用者希望獲取的數據,第二個是錯誤相關的信息。這里需要遵循以下兩點:
- 若 `objects` 不為 nil,則 `error` 必須為 nil
- 若 `objects` 為 nil,則 `error` 必須不為 nil
因為調用者更關心的是實際的數據,就像這樣:
~~~
- (void)downloadObjectsAtPath:(NSString *)path
completion:(void(^)(NSArray *objects, NSError *error))completion {
if (objects) {
// do something with the data
}
else {
// some error occurred, 'error' variable should not be nil by contract
}
}
~~~
此外,Apple 提供的一些同步接口在成功狀態下向 error 參數(如果非 NULL) 寫入了垃圾值,所以檢查 error 的值可能出現問題。
## 深入 Blocks
一些關鍵點:
- block 是在棧上創建的
- block 可以復制到堆上
- block 有自己的私有的棧變量(以及指針)的常量復制
- 可變的棧上的變量和指針必須用 __block 關鍵字聲明
如果 block 沒有在其他地方被保持,那么它會隨著棧生存并且當棧幀(stack frame)返回的時候消失。當在棧上的時候,一個 block 對訪問的任何內容不會有影響。如果 block 需要在棧幀返回的時候存在,它們需要明確地被復制到堆上,這樣,block 會像其他 Cocoa 對象一樣增加引用計數。當它們被復制的時候,它會帶著它們的捕獲作用域一起,retain 他們所有引用的對象。如果一個 block指向一個棧變量或者指針,那么這個block初始化的時候它會有一份聲明為 const 的副本,所以對它們賦值是沒用的。當一個 block 被復制后,`__block` 聲明的棧變量的引用被復制到了堆里,復制之后棧上的以及產生的堆上的 block 都會引用這個堆上的變量。
用 LLDB 來展示 block 是這樣子的:

最重要的事情是 `__block` 聲明的變量和指針在 block 里面是作為顯示操作真實值/對象的結構來對待的。
block 在 Objective-C 里面被當作一等公民對待:他們有一個 `isa` 指針,一個類也是用 `isa` 指針來訪問 Objective-C 運行時來訪問方法和存儲數據的。在非 ARC 環境肯定會把它搞得很糟糕,并且懸掛指針會導致 Crash。`__block` 僅僅對 block 內的變量起作用,它只是簡單地告訴 block:
> 嗨,這個指針或者原始的類型依賴它們在的棧。請用一個棧上的新變量來引用它。我是說,請對它進行雙重解引用,不要 retain 它。謝謝,哥們。
如果在定義之后但是 block 沒有被調用前,對象被釋放了,那么 block 的執行會導致 Crash。 `__block` 變量不會在 block 中被持有,最后... 指針、引用、解引用以及引用計數變得一團糟。
## self 的循環引用
當使用代碼塊和異步分發的時候,要注意避免引用循環。 總是使用 `weak` 引用會導致引用循環。 此外,把持有 blocks 的屬性設置為 nil (比如 `self.completionBlock = nil`) 是一個好的實踐。它會打破 blocks 捕獲的作用域帶來的引用循環。
**例子:**
~~~
__weak __typeof(self) weakSelf = self;
[self executeBlock:^(NSData *data, NSError *error) {
[weakSelf doSomethingWithData:data];
}];
~~~
**不要這樣做:**
~~~
[self executeBlock:^(NSData *data, NSError *error) {
[self doSomethingWithData:data];
}];
~~~
**多個語句的例子:**
~~~
__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];
}];
~~~
你應該把這兩行代碼作為 snippet 加到 Xcode 里面并且總是這樣使用它們。
~~~
__weak __typeof(self)weakSelf = self;
__strong __typeof(weakSelf)strongSelf = weakSelf;
~~~
這里我們來討論下 block 里面的 self 的 `__weak` 和 `__strong` 限定詞的一些微妙的地方。簡而言之,我們可以參考 self 在 block 里面的三種不同情況。
1. 直接在 block 里面使用關鍵詞 self
1. 在 block 外定義一個 `__weak` 的 引用到 self,并且在 block 里面使用這個弱引用
1. 在 block 外定義一個 `__weak` 的 引用到 self,并在在 block 內部通過這個弱引用定義一個 `__strong` 的引用。
**1. 直接在 block 里面使用關鍵詞 `self`**
如果我們直接在 block 里面用 self 關鍵字,對象會在 block 的定義時候被 retain,(實際上 block 是 [copied](https://developer.apple.com/library/ios/documentation/cocoa/conceptual/Blocks/Articles/bxVariables.html#//apple_ref/doc/uid/TP40007502-CH6-SW4) 但是為了簡單我們可以忽略這個)。一個 const 的對 self 的引用在 block 里面有自己的位置并且它會影響對象的引用計數。如果 block 被其他 class 或者/并且傳送過去了,我們可能想要 retain self 就像其他被 block 使用的對象,從他們需要被block執行
~~~
dispatch_block_t completionBlock = ^{
NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:completionHandler];
~~~
不是很麻煩的事情。但是, 當 block 被 self 在一個屬性 retain(就像下面的例子)呢
~~~
self.completionHandler = ^{
NSLog(@"%@", self);
}
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:self.completionHandler];
~~~
這就是有名的 retain cycle, 并且我們通常應該避免它。這種情況下我們收到 CLANG 的警告:
~~~
Capturing 'self' strongly in this block is likely to lead to a retain cycle (在 block 里面發現了 `self` 的強引用,可能會導致循環引用)
~~~
所以可以用 `weak` 修飾
**2. 在 block 外定義一個 `__weak` 的 引用到 self,并且在 block 里面使用這個弱引用**
這樣會避免循環引用,也是我們通常在 block 已經被 self 的 property 屬性里面 retain 的時候會做的。
~~~
__weak typeof(self) weakSelf = self;
self.completionHandler = ^{
NSLog(@"%@", weakSelf);
};
MyViewController *myController = [[MyViewController alloc] init...];
[self presentViewController:myController
animated:YES
completion:self.completionHandler];
~~~
這個情況下 block 沒有 retain 對象并且對象在屬性里面 retain 了 block 。所以這樣我們能保證了安全的訪問 self。 不過糟糕的是,它可能被設置成 nil 的。問題是:如果和讓 self 在 block 里面安全地被銷毀。
舉個例子, block 被一個對象復制到了另外一個(比如 myControler)作為屬性賦值的結果。之前的對象在可能在被復制的 block 有機會執行被銷毀。
下面的更有意思。
**3. 在 block 外定義一個 `__weak` 的 引用到 self,并在在 block 內部通過這個弱引用定義一個 `__strong` 的引用**
你可能會想,首先,這是避免 retain cycle 警告的一個技巧。然而不是,這個到 self 的強引用在 _block 的執行時間_ 被創建。當 block 在定義的時候, block 如果使用 self 的時候,就會 retain 了 self 對象。
[Apple 文檔](http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html) 中表示 "為了 non-trivial cycles ,你應該這樣" :
~~~
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...
}
};
~~~
首先,我覺得這個例子看起來是錯誤的。如果 block 本身被 completionHandler 屬性里面 retain 了,那么 self 如何被 delloc 和在 block 之外賦值為 nil 呢? completionHandler 屬性可以被聲明為 `assign` 或者 `unsafe_unretained` 的,來允許對象在 block 被傳遞之后被銷毀。
我不能理解這樣做的理由,如果其他對象需要這個對象(self),block 被傳遞的時候應該 retain 對象,所以 block 應該不被作為屬性存儲。這種情況下不應該用 `__weak`/`__strong`
總之,其他情況下,希望 weakSelf 變成 nil 的話,就像第二種情況解釋那么寫(在 block 之外定義一個弱應用并且在 block 里面使用)。
還有,Apple的 "trivial block" 是什么呢。我們的理解是 trivial block 是一個不被傳送的 block ,它在一個良好定義和控制的作用域里面,weak 修飾只是為了避免循環引用。
雖然有 Kazuki Sakamoto 和 Tomohiko Furumoto) 討論的 [一](http://dhoerl.wordpress.com/2013/04/23/i-finally-figured-out-weakself-and-strongself/)[些](http://blog.random-ideas.net/?p=160)[的](http://stackoverflow.com/questions/7904568/disappearing-reference-to-self-in-a-block-under-arc)[在線](http://stackoverflow.com/questions/12218767/objective-c-blocks-and-memory-management)[參考](https://github.com/AFNetworking/AFNetworking/issues/807), [Matt Galloway](https://twitter.com/mattjgalloway) 的 ([Effective Objective-C 2.0](http://www.effectiveobjectivec.com/) 和 [Pro Multithreading and Memory Management for iOS and OS X](http://www.amazon.it/Pro-Multithreading-Memory-Management-Ios/dp/1430241160) ,大多數開發者始終沒有弄清楚概念。
在 block 內用強引用的優點是,搶占執行的時候的魯棒性。看上面的三個例子,在 block 執行的時候
**1. 直接在 block 里面使用關鍵詞 `self`**
如果 block 被屬性 retain,self 和 block 之間會有一個循環引用并且它們不會再被釋放。如果 block 被傳送并且被其他的對象 copy 了,self 在每一個 copy 里面被 retain
**2. 在 block 外定義一個 `__weak` 的 引用到 self,并且在 block 里面使用這個弱引用**
沒有循環引用的時候,block 是否被 retain 或者是一個屬性都沒關系。如果 block 被傳遞或者 copy 了,在執行的時候,weakSelf 可能會變成 nil。
block 的執行可以搶占,并且后來的對 weakSelf 的不同調用可以導致不同的值(比如,在 一個特定的執行 weakSelf 可能賦值為 nil )
~~~
__weak typeof(self) weakSelf = self;
dispatch_block_t block = ^{
[weakSelf doSomething]; // weakSelf != nil
// preemption, weakSelf turned nil
[weakSelf doSomethingElse]; // weakSelf == nil
};
~~~
** 3. 在 block 外定義一個 `__weak` 的 引用到 self,并在在 block 內部通過這個弱引用定義一個 `__strong` 的引用。**
不論管 block 是否被 retain 或者是一個屬性,這樣也不會有循環引用。如果 block 被傳遞到其他對象并且被復制了,執行的時候,weakSelf 可能被nil,因為強引用被復制并且不會變成nil的時候,我們確保對象 在 block 調用的完整周期里面被 retain了,如果搶占發生了,隨后的對 strongSelf 的執行會繼續并且會產生一樣的值。如果 strongSelf 的執行到 nil,那么在 block 不能正確執行前已經返回了。
~~~
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
[strongSelf doSomething]; // strongSelf != nil
// preemption, strongSelf still not nil(搶占的時候,strongSelf 還是非 nil 的)
[strongSelf doSomethingElse]; // strongSelf != nil
}
else {
// Probably nothing...
return;
}
};
~~~
在一個 ARC 的環境中,如果嘗試用 `->`符號來表示,編譯器會警告一個錯誤:
~~~
Dereferencing a __weak pointer is not allowed due to possible null value caused by race condition, assign it to a strong variable first. (對一個 __weak 指針的解引用不允許的,因為可能在競態條件里面變成 null, 所以先把他定義成 strong 的屬性)
~~~
可以用下面的代碼展示
~~~
__weak typeof(self) weakSelf = self;
myObj.myBlock = ^{
id localVal = weakSelf->someIVar;
};
~~~
在最后【疑問】
-
**1**: 只能在 block 不是作為一個 property 的時候使用,否則會導致 retain cycle。
-
**2**: 當 block 被聲明為一個 property 的時候使用。
-
**Case 3**: 和并發執行有關。當涉及異步的服務的時候,block 可以在之后被執行,并且不會發生關于 self 是否存在的問題。