iOS中訊息回撥Apple提供瞭如下幾種方法:
delegate
delegate屬於一對一的回撥。這種方式在實際的開發中應用的最多。但是缺點是無法實現一對多的回撥。
NSNotification
NSNotification屬於全域性廣播。但無法指定回撥方法,而且在實際的開發中應該儘量少用通知,因為這種方式很難管理。
- KVO
KVO
屬於一對多的回撥。但是僅僅適用於監聽屬性變更方面。 - Block
Block 也算是一種回撥方式,但是如果使用不當可能會引起迴圈引用問題。並且跟
delegate
一樣,同樣不具備一對多的功能,優點在於用起來方便。
在實際的開發過程中,我們可能需要即需要類似delegate
那樣的回撥方式,又想要類似KVO
那樣的一對多的功能。這種需求在IM類應用
中很普遍,甚至可以說這樣的回撥方式是IM類應用
的核心。
這裡介紹一種使用OC的訊息轉發機制來實現多播委託功能的方法。這裡先直接貼出實現程式碼再一一解釋。
@interface MulticastDelegate : NSObject
-(void)regisetDelegate:(id)delegate;
@end
@implementation MulticastDelegate{
// delegate陣列
NSMutableArray *delegates;
}
-(id)init{
self = [super init];
delegates = [NSMutableArray array];
return self;
}
// 註冊delegate
-(void)regisetDelegate:(id)delegate{
// 其實就是把delegate存入陣列
[delegates addObject:delegate];
}
// 方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 利用訊息轉發機制對delegate陣列進行回撥
-(void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = invocation.selector;
[delegates enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if([obj respondsToSelector:sel]){
[invocation invokeWithTarget:obj];
}
}];
}
@end
複製程式碼
這裡定義了一個叫做MulticastDelegate
的類,這個類專門用於管理多播委託的,並且對外只提供了一個regisetDelegate:
方法。而這個方法也很簡單,就是將delegate
加入陣列中。
上面的程式碼中核心程式碼就是訊息轉發那塊程式碼。也就是methodSignatureForSelector
和forwardInvocation
兩個方法。當我們對一個物件呼叫了不存在的方法的時候就會觸發訊息轉發
機制,當訊息轉發進入到forwardInvocation
的時候說明已經進入到最後一步,並且系統已經把方法的效用資訊全部封裝進了NSInvocation
中了。這時候只需要將delegate陣列
中的delegate
遍歷執行即可。
整體的實現程式碼可以說很簡單。下面是使用方式程式碼,既然叫做MulticastDelegate
,那麼用的時候肯定是要先定義個protocol
了。那麼第一步就是定義protocol
@protocol TestDelegate
-(void)print;
@end
複製程式碼
然後就是註冊回撥
id mutiDelegate; // 注意是id型別。
mutiDelegate =[[MulticastDelegate alloc] init];
[mutiDelegate regisetDelegate:self];
複製程式碼
最後就是呼叫。這一步的呼叫跟原來的一樣,直接對mutiDelegate
呼叫方法即可。
[mutiDelegate print];
複製程式碼
在上面的MulticastDelegate
的初始化過程中你應該注意到了,變數mutiDelegate
是id
型別,而不是MulticastDelegate
。之所以這樣做,是因為只有id型別的變數才能呼叫任意方法而Xcode不會警告,否則XCode都不能編譯。
其實看了上面的程式碼,你會發現一個,我們平常設定delegate
的時候都是weak
屬性,但是上面的程式碼中存入陣列中的delegate
是strong的,這不是形成迴圈引用了嗎?
另外,在實際的開發過程中,甚至對回撥執行緒也有要求,比如在註冊回撥的時候指定回撥佇列。這樣就需要修改回撥陣列的存放的內容了。
// 定義一個存放delete 和 dispatch_queue_t 的class
@interface MulticastDelegateNode : NSObject
@property (nonatomic,weak,readonly)id delegate;
@property (nonatomic,readonly)dispatch_queue_t queue;
@end
@implementation MulticastDelegateNode
-(id)initWithDelegate:(id)delegate delegateQueue:(dispatch_queue_t)queue{
self = [super init];
_delegate = delegate;
_queue = queue;
return self;
}
@end
@implementation MulticastDelegate{
// delegate陣列
NSMutableArray<MulticastDelegateNode *> *delegates;
}
-(id)init{
self = [super init];
delegates = [NSMutableArray array];
return self;
}
// 註冊delegate
-(void)regisetDelegate:(id)delegate delegateQueue:(dispatch_queue_t)queue{
// 其實就是把delegate存入陣列
MulticastDelegateNode *node = [[MulticastDelegateNode alloc] initWithDelegate:delegate delegateQueue:queue?:dispatch_get_main_queue()];
[delegates addObject:node];
}
// 方法簽名
-(NSMethodSignature *)methodSignatureForSelector:(SEL)selector{
return [NSMethodSignature signatureWithObjCTypes:"v@:"];
}
// 利用訊息轉發機制對delegate陣列進行回撥
-(void)forwardInvocation:(NSInvocation *)invocation{
SEL sel = invocation.selector;
[delegates enumerateObjectsUsingBlock:^(MulticastDelegateNode * _Nonnull node, NSUInteger idx, BOOL * _Nonnull stop) {
if([node.delegate respondsToSelector:sel]){
dispatch_async(node.queue, ^{
[invocation invokeWithTarget:node.delegate];
});
}
}];
}
@end
複製程式碼
上面的程式碼中額外定義了一個MulticastDelegateNode
的class,主要是以weak
的方式儲存delegate
,並且儲存dispatch_queue_t
。這樣既解決了迴圈引用的問題,又能在指定的佇列上執行回撥。