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

weixin_34239169發表於2019-01-16

iOS中訊息回撥Apple提供瞭如下幾種方法:

  1. delegate

    delegate屬於一對一的回撥。這種方式在實際的開發中應用的最多。但是缺點是無法實現一對多的回撥。

  2. NSNotification

    NSNotification屬於全域性廣播。但無法指定回撥方法,而且在實際的開發中應該儘量少用通知,因為這種方式很難管理。

  3. KVO

    KVO屬於一對多的回撥。但是僅僅適用於監聽屬性變更方面。

  4. 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加入陣列中。

上面的程式碼中核心程式碼就是訊息轉發那塊程式碼。也就是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。這樣既解決了迴圈引用的問題,又能在指定的佇列上執行回撥。

相關文章