iOS:利用消息轉發機制實現多播委託

iOS中消息回調Apple提供了以下幾種方法:node

  1. delegate

    delegate屬於一對一的回調。這種方式在實際的開發中應用的最多。可是缺點是沒法實現一對多的回調。objective-c

  2. NSNotification

    NSNotification屬於全局廣播。但沒法指定回調方法,並且在實際的開發中應該儘可能少用通知,由於這種方式很難管理。數組

  3. KVO

    KVO屬於一對多的回調。可是僅僅適用於監聽屬性變動方面。async

  4. Block

    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

上面的代碼中核心代碼就是消息轉發那塊代碼。也就是methodSignatureForSelectorforwardInvocation兩個方法。當咱們對一個對象調用了不存在的方法的時候就會觸發消息轉發機制,當消息轉發進入到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的初始化過程當中你應該注意到了,變量mutiDelegateid類型,而不是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。這樣既解決了循環引用的問題,又能在指定的隊列上執行回調。

相關文章
相關標籤/搜索