一、協(xié)議
協(xié)議主要是提供接口、或是類似C++多重繼承功能,為類提供一種修飾機制。協(xié)議不是為回調(diào)而生的,它應(yīng)該表述一組互操作約定。
優(yōu)點:
實現(xiàn)簡單,容易理解。
強類型檢查。
缺點:
類與類間建立了比較強的耦合關(guān)系
有可能需要較長期保存委托以進行回調(diào)。如果保留的委托需要有獨占性,可能會給單件模式、以及多線程帶來麻煩。
類只能通過一個方法完成一種類型的回調(diào)。代碼邏輯很容易集中到一個方法中。
大部分回調(diào)使用實際無需通過協(xié)議暴露給外部。
二、使用respondsToSelector和performSelector進行回調(diào)。
利用OBJC的運行時特性,查找對象的消息進行回調(diào)
優(yōu)點:
與OBJC代碼兼容性好。
具有延遲執(zhí)行等特性。
輕量級的回調(diào)機制。
缺點:
回調(diào)產(chǎn)生的返回值只能為id類型,int等類型會產(chǎn)生錯誤。
參數(shù)最多只能傳入兩個。但可以通過建立包含多個參數(shù)的參數(shù)類進行回避。同時返回值限制也可通過此方式解決,即建立一個輸入類和一個輸出類。NSInvocation也提供了多參數(shù)的解決方法。
如果以
[target performSelector: @selector(callback)];
方式建立回調(diào),則需要對類的回調(diào)消息名建立約定,且回調(diào)消息名具有獨占性,即一個類中只能以此消息名進行回調(diào)。
如果通過外部傳入SEL建立回調(diào)
[target performSelector: sel];
或是外部傳入字符串建立回調(diào)
[target performSelector:NSSelectorFromString(@"callback")];
使用自動引數(shù)編譯器特征(ARC)會產(chǎn)生警告“performSelector may cause a leak because its selector is unknown”
使用此種方式建立回調(diào),當傳入一個不符合約定的消息時,會產(chǎn)生副作用繼續(xù)運行,而非報錯。比如約定消息有2個參數(shù),但傳入消息只有1個參數(shù),則按照參數(shù)約定順序屏蔽掉最后傳入的參數(shù)?;蚴莻魅胂⒕哂?個參數(shù),則多余的參數(shù)值未初始化。
三、函數(shù)指針
傳統(tǒng)的C語言回調(diào)機制。
優(yōu)點:
輕量級的回調(diào)機制。
只約定返回值和參數(shù),而非函數(shù)名。無參數(shù)、返回值限制,使用靈活。
編譯器提供類型檢查。(錯誤時產(chǎn)生警告)
缺點:
與OBJC的消息機制不兼容。因為消息并非C語言中那樣,函數(shù)名對應(yīng)函數(shù)指針。即只能對C函數(shù)進行回調(diào)。
傳入不符合約定的函數(shù)指針時,產(chǎn)生副作用繼續(xù)運行,而非報錯。
四、objc_msgSend
通過導入#import <objc/message.h>獲得運行時的消息調(diào)用。
其定義為
id objc_msgSend(id theReceiver, SEL theSelector, ...)
優(yōu)點:
輕量級的回調(diào)機制。
無傳入?yún)?shù)限制。
相比performSelector,使用自動引數(shù)特征時,不產(chǎn)生警告。
同系列的方法支持double、struct等類型的返回值,但仍然不支持int型返回值(可使用NSNumber包裝以回避)。
缺點:
傳入不符合約定的消息時,產(chǎn)生副作用繼續(xù)運行,而非報錯。
五、IMP
IMP類似于OBJC提供的函數(shù)指針,它通過methodForSelector方法查詢傳入的Selector,以獲得函數(shù)的入口地址。
其定義為
id (*IMP)(id, SEL, ...)
相比普通C語言的函數(shù)指針,其定義多了id,SEL這兩個強制參數(shù)約定,其他與函數(shù)指針無異。
優(yōu)點:
輕量級的回調(diào)機制。
傳入不符合約定的消息時,報錯。
無傳入?yún)?shù)限制。返回值可通過強轉(zhuǎn)獲得,無類型限制。如:
typedef int (*CBFUNC)(id, SEL, int, int, int); // 定義函數(shù)指針類型
int ret = ((CBFUNC)callback)(self, sel, param1, param2, param3); // 強制轉(zhuǎn)換
這里的id和SEL只是OBJC系統(tǒng)約定的占位,自定義回調(diào)時無實際意義。
由于此階段實際是函數(shù)指針調(diào)用,因此最好還是typedef定義函數(shù)指針,然后對IMP強轉(zhuǎn)一下,以免出現(xiàn)錯誤,也能提供一些編譯期保護。
缺點:
依然不能提供如同協(xié)議和函數(shù)指針的編譯期類型檢查
六、NSNotificationCenter
NSNotificationCenter是OBJC提供的消息機制。它有些類似于觀察者模式,通過關(guān)注感興趣的消息,建立回調(diào)。NSNotificationCenter提供了一種低耦合的對象通訊機制,特別適合無指定對象的一對多回調(diào)。
主要方法:
1)獲取消息中心實例(系統(tǒng)已創(chuàng)建,單件模式)
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
2)發(fā)送消息。(事件發(fā)生時調(diào)用)
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc postNotificationName: NOTIFY_MSG_UC_COMMON_PLAYER_PLAY // 消息名(字符串)
object:self // 消息源
userInfo:nil]; // 用戶字典(傳遞更多自定義參數(shù))
3)注冊消息
[nc addObserver: self // 觀察者
selector: @selector(handleNotify_Play:) // 回調(diào)
name: NOTIFY_MSG_UC_COMMON_PLAYER_PLAY // 監(jiān)聽消息
object: nil]; // 消息源
```
4)注銷消息
```
[nc removeObserver: self];
```
5)回調(diào)定義
```
- (void) handleNotify_Play:(NSNotification *)note;
```
只有一個參數(shù)
NSNotification*
–name // 消息名
–object // 消息源
–userInfo // 用戶字典
優(yōu)點:
回調(diào)對象間耦合度低。相互之間可不必知道對方存在。
通過消息傳遞的信息無限制。
觀察者可選擇特定消息、特定對象,或者特定對象的特定消息進行觀察。
缺點:
缺乏時序性。當事件發(fā)生時,回調(diào)執(zhí)行的先后次序不確定。也不能等待回調(diào)完成執(zhí)行后續(xù)操作。解決:1)使用傳統(tǒng)回調(diào)機制。2)多線程時,可使用NSCondition同步線程。3)使用更多的消息。(過多使用可能導致混亂)
**七、Block**
Block是OBJC提供的一種運行時方法機制,類似于Javascript的匿名函數(shù)。它提供了一種運行時的臨時回調(diào)機制。
Block對象的聲明:
聲明一個參數(shù)為int,返回值為int的Block對象cb。
int (^cb)(int);
也可以通過typedef簡化定義。
typedef int(^BLOCK_CALLBACK_FUNC)(int);
BLOCK_CALLBACK_FUNC cb = …
回調(diào)函數(shù)定義:
```
-(int)handleBlockCallbackFunc: (BLOCK_CALLBACK_FUNC)callback
{
return callback(10);
}
```
回調(diào)函數(shù)使用:
1)賦值后使用
```
BLOCK_CALLBACK_FUNC cb =
^(int param)
{
NSLog(@"Block Msg: %d", param);
return param*2;
};
int ret = [self handleBlockCallbackFunc:cb];
```
2)使用時賦值
```
int ret = [self handleBlockCallbackFunc:
^(int param) {
NSLog(@"Block Msg: %d", param);
return param*2;
}];
```
注意:
**1)block對象使用的變量、參數(shù)在運行時被綁定,因此可以直接使用棧空間建立的變量,無需參數(shù)傳入。但block對象的創(chuàng)建依然有生命周期限制,因此傳入異步調(diào)用的block對象時,如果是??臻g創(chuàng)建的block,必須**
**使用Block_copy()將block拷出備份,然后使用Block_release()將block釋放。參見Using Blocks章,Patterns to Avoid節(jié)**
**2)對于在??臻g聲明的變量,綁定到block時被標記為const。只能讀取不能寫入。如果需要寫入,需要用__block對變量進行標記。此時block使用的是從??截惖蕉阎械膶ο?。當出block時,如果棧可用則將堆中對象自動拷貝回棧。**
優(yōu)點:
最輕量級的回調(diào)機制。
編譯器類型檢查。
如函數(shù)指針一樣,靈活定義回調(diào)函數(shù)。
缺點:
執(zhí)行效率。(影響程度不清楚)
容易導致代碼邏輯集中。
IOS4之后的特性
**總結(jié):**
OBJC還沒有太完美的輕量級回調(diào)機制,只能根據(jù)情況選擇合適的機制。
單純的回調(diào),且沒有復用的必要,也無IOS版本限制,可采用block。
單純的回調(diào),有復用要求,可使用performSelector、objc_msgSend,或是IMP的回調(diào)機制。
使用自動引數(shù)的情況下,盡量不使用performSelector回調(diào)傳入的@Selector,防止警告。
對象間有較多的互操作,對象有復用的必要,可采用協(xié)議。
無指定對象的一對多回調(diào)采用NSNotificationCenter。
有延遲調(diào)用等特殊應(yīng)用的,可以使用performSelector。