iOS中消息回調Apple提供了以下幾種方法:node
delegate
delegate屬於一對一的回調。這種方式在實際的開發中應用的最多。可是缺點是沒法實現一對多的回調。objective-c
NSNotification
NSNotification屬於全局廣播。但沒法指定回調方法,並且在實際的開發中應該儘可能少用通知,由於這種方式很難管理。數組
KVO
屬於一對多的回調。可是僅僅適用於監聽屬性變動方面。async
Block 也算是一種回調方式,可是若是使用不當可能會引發循環引用問題。而且跟
delegate
同樣,一樣不具有一對多的功能,優勢在於用起來方便。atom
在實際的開發過程當中,咱們可能須要即須要相似delegate
那樣的回調方式,又想要相似KVO
那樣的一對多的功能。這種需求在IM類應用
中很廣泛,甚至能夠說這樣的回調方式是IM類應用
的核心。spa
這裏介紹一種使用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
加入數組中。code
上面的代碼中核心代碼就是消息轉發那塊代碼。也就是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
。這樣既解決了循環引用的問題,又能在指定的隊列上執行回調。