【iOS印象】GLPubSub 原始碼閱讀筆記

Binboy_王興彬發表於2019-03-01

@(iOS知行路)[原始碼閱讀|iOS開發|NSNotification|iOS印象]

GLPubSubNSNotificationCenter 的封裝,目標是簡化 iOS 開發中的釋出訂閱模式。該庫通過新增 NSObject 的 Category,使得所有物件都可以方便地使用其訂閱或釋出通知。

原始碼閱讀


  • GLEvent 類,傳遞的事件物件,包含事件名稱事件釋出者事件附帶的額外資料
/**
 `GLEvent` is the event object passed into handler block.
 */
@interface GLEvent : NSObject

/**
 *  Name of the event
 */
@property (nonatomic, copy) NSString *name;

/**
 *  The object triggering the event
 */
@property (nonatomic, retain) id obj;

/**
 *  Additional data of the event
 */
@property (nonatomic, retain) id data;

/**
 *  Init `GLEvent` with `name`, `obj` and `data`.
 *
 *  @param name Name of the event
 *  @param obj  The object triggering the event
 *  @param data Additional data of the event
 *
 *  @return Initialized `GLEvent`
 */
- (id)initWithName:(NSString *)name obj:(id)obj data:(id)data;

@end
複製程式碼
  • 通過為 NSObject 新增 Category,增加訂閱/取消訂閱釋出事件等方法,以及設定事件處理的處理佇列
@interface NSObject (GLPubSub)

#pragma mark - Class Methods
/**
 *  Set the queue to handle events. If not set or set to `nil, events will be handled on the queue where the notification is posted. Otherwise, all the events will be handled on the set queue.
 *
 *  @param queue The queue on which to handle events.
 */
+ (void)setPubSubQueue:(NSOperationQueue *)queue;

#pragma mark - Publish Methods
/**
 *  Publish an event without additional data.
 *
 *  @param name Event name to publish.
 *
 *  @see -publish:data:
 */
- (void)publish:(NSString *)name;

/**
 *  Publish an event with additional data.
 *
 *  @param name Event name to publish.
 *  @param data Additional data.
 *
 *  @see -publish:
 */
- (void)publish:(NSString *)name data:(id)data;

#pragma mark - Subscribe Methods with Selector
/**
 *  Subscribe an event with selector.
 *
 *  @param eventName Event name to subscribe.
 *  @param selector  Selector to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:obj:selector:
 *  @see -subscribeOnce:selector:
 *  @see -subscribeOnce:obj:selector:
 */
- (id)subscribe:(NSString *)eventName selector:(SEL)selector;

/**
 *  Subscribe an event from a specified object with selector.
 *
 *  @param eventName Event name to subscribe.
 *  @param object    Publisher to subscribe from.
 *  @param selector  Selector to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:selector:
 *  @see -subscribeOnce:selector:
 *  @see -subscribeOnce:obj:selector:
 */
- (id)subscribe:(NSString *)eventName obj:(id)obj selector:(SEL)selector;

/**
 *  Subscribe an event only once with selector.
 *
 *  @param eventName Event name to subscribe.
 *  @param selector  Selector to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:selector:
 *  @see -subscribe:obj:selector:
 *  @see -subscribeOnce:obj:selector:
 */
- (id)subscribeOnce:(NSString *)eventName selector:(SEL)selector;

/**
 *  Subscribe an event from a specified object only once with selector.
 *
 *  @param eventName Event name to subscribe.
 *  @param object    Publisher to subscribe from.
 *  @param selector  Selector to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:selector:
 *  @see -subscribe:obj:selector:
 *  @see -subscribeOnce:selector:
 */
- (id)subscribeOnce:(NSString *)eventName obj:(id)obj selector:(SEL)selector;

#pragma mark - Subscribe Methods with Handler Block
/**
 *  Subscribe an event with handler block.
 *
 *  @param eventName Event name to subscribe.
 *  @param handler   Handler block to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:obj:handler:
 *  @see -subscribeOnce:handler:
 *  @see -subscribeOnce:obj:handler:
 */
- (id)subscribe:(NSString *)eventName handler:(GLEventHandler)handler;

/**
 *  Subscribe an event from a specified object with handler block.
 *
 *  @param eventName Event name to subscribe.
 *  @param obj       Publisher to subscribe from.
 *  @param handler   Handler block to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:handler:
 *  @see -subscribeOnce:handler:
 *  @see -subscribeOnce:obj:handler:
 */
- (id)subscribe:(NSString *)eventName obj:(id)obj handler:(GLEventHandler)handler;

/**
 *  Subscribe an event only once with handler block.
 *
 *  @param eventName Event name to subscribe.
 *  @param handler   Handler block to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:handler:
 *  @see -subscribe:obj:handler:
 *  @see -subscribeOnce:obj:handler:
 */
- (id)subscribeOnce:(NSString *)eventName handler:(GLEventHandler)handler;

/**
 *  Subscribe an event from a specified object only once with handler block.
 *
 *  @param eventName Event name to subscribe.
 *  @param obj       Publisher to subscribe from.
 *  @param handler   Handler block to handle the event.
 *
 *  @return An opaque object to act as the observer.
 *
 *  @see -subscribe:handler:
 *  @see -subscribe:obj:handler:
 *  @see -subscribeOnce:handler:
 */
- (id)subscribeOnce:(NSString *)eventName obj:(id)obj handler:(GLEventHandler)handler;

#pragma mark - Unsubscribe Methods
/**
 *  Unsubscribe speficied event for current instance.
 *
 *  @param eventName Name of event to unsubscribe.
 *
 *  @see -unsubscribeAll
 */
- (void)unsubscribe:(NSString *)eventName;

/**
 *  Unsubscribe all events for current instance.
 *
 *  @see -unsubscribe:
 */
- (void)unsubscribeAll;

@end
複製程式碼
  • 釋出事件的實現
- (void)publish:(NSString *)name data:(id)data {
    // 通知中心發出通知,並將事件物件放入`userInfo`
    NSDictionary *userInfo = nil;
    if (data != nil) {
        userInfo = @{kGLPubSubDataKey: data};
    }
    [[NSNotificationCenter defaultCenter] postNotificationName:name object:self userInfo:userInfo];
}
複製程式碼
  • 訂閱事件的實現
- (id)subscribe:(NSString *)eventName obj:(id)obj selector:(SEL)selector {
    // 用於判斷`selector`的引數個數
    NSMethodSignature *sig = [self methodSignatureForSelector:selector];
    //  The hidden arguments self and _cmd (of type SEL) are at indices 0 and 1; method-specific arguments begin at index 2
    BOOL passEventObj = ([sig numberOfArguments] == 3);
    __weak __typeof__(self) weakSelf = self;
    return [self subscribe:eventName obj:obj handler:^(GLEvent *event) {
        __strong __typeof__(weakSelf) strongSelf = weakSelf;
        if (passEventObj) {
            [strongSelf performSelector:selector withObject:event];
        } else {
            [strongSelf performSelector:selector];
        }
    }];
}

- (id)subscribe:(NSString *)eventName obj:(id)obj handler:(GLEventHandler)handler {
    // 通知中心通過新增觀察者,並在block中生成事件進行轉發
    id observer =  [[NSNotificationCenter defaultCenter] addObserverForName:eventName object:obj queue:_pubSubQueue usingBlock:^(NSNotification *note) {
        GLEvent *event = [[GLEvent alloc] initWithName:eventName obj:note.object data:[note.userInfo objectForKey:kGLPubSubDataKey]];
        handler(event);
    }];
    // 新增觀察者物件至該事件的觀察者陣列,用於取消訂閱事件
    NSMutableDictionary *subscriptions = (NSMutableDictionary *)objc_getAssociatedObject(self, &kGLPubSubSubscriptionsKey);
    if (!subscriptions) {
        subscriptions = [[NSMutableDictionary alloc] init];
        objc_setAssociatedObject(self, &kGLPubSubSubscriptionsKey, subscriptions, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    NSMutableSet *observers = [subscriptions objectForKey:eventName];
    if (!observers) {
        observers = [[NSMutableSet alloc] init];
        [subscriptions setObject:observers forKey:eventName];
    }
    [observers addObject:observer];
    return observer;
}
複製程式碼
  • 取消事件的實現
- (void)unsubscribe:(NSString *)eventName {
    NSMutableDictionary *subscriptions = (NSMutableDictionary *)objc_getAssociatedObject(self, &kGLPubSubSubscriptionsKey);
    if (!subscriptions)
        return;
    NSMutableSet *observers = [subscriptions objectForKey:eventName];
    if (observers) {
        // 通知中心移除所有該事件的觀察者
        for (id observer in observers) {
            [[NSNotificationCenter defaultCenter] removeObserver:observer];
        }
        // 訂閱陣列中移除該事件觀察者陣列物件
        [subscriptions removeObjectForKey:eventName];
    }
}
複製程式碼

附:安裝與使用


CocoaPods

  • Podfile 裡新增以下依賴:
pod "GLPubSub", "~> 1.0"
複製程式碼
  • 執行 pod install 來安裝 GLPubSub

原始檔

如果你的專案沒有用 CocoaPods 來管理第三方依賴,你也可以直接匯入原始檔。

  • 下載最新程式碼並解壓
  • 匯入 NSObject+GLPubSub.hNSObject+GLPubSub.m 到你的工程,記得在匯入時勾選 “Copy items if needed”

因為 GLPubSub 是基於 NSNotificationCenter 並註冊在 [NSNotificationCenter defaultCenter] 的,所以 GLPubSub 也支援大部分系統通知,例如 UIApplicationDidEnterBackgroundNotificationUIApplicationDidBecomeActiveNotificationUITextFieldTextDidChangeNotification 等等,但是轉發的過程中會丟棄系統通知的 userInfo 欄位。

設定 PubSub 的佇列

GLPubSub 主要基於 NSNotificationCenter-addObserverForName:object:queue:usingBlock: 方法。你可以呼叫 NSObject+setPubSubQueue: 方法來設定傳入該方法的 queue

預設傳入的 queuenil,這意味著所有事件會在釋出通知的執行緒中被執行。你可以手動設定為 [NSOperationQueue maniQueue] 使得所有事件在主執行緒被觸發:

[NSObject setPubSubQueue:[NSOperationQueue mainQueue]];
複製程式碼

通過 Selector 訂閱事件

大部分時候,我們用 self 作為訂閱者:

[self subscribe:@"YourEventName" selector:@selector(yourEventHandler)];
複製程式碼

你也可以指定事件的釋出者:

[self subscribe:@"YourEventName" obj:somePublisher selector:@selector(yourEventHandler)];
複製程式碼

如果你希望你的方法只觸發一次,你可以用:

[self subscribeOnce:@"YourEventName" selector:@selector(yourEventHandler)];
複製程式碼

這樣當該事件被觸發後,就會自動取消訂閱。

你的方法可以接受一個 GLEvent 引數,該引數包含了被觸發事件的相關資訊。

@interface GLEvent : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, retain) id obj;
@property (nonatomic, retain) id data;
複製程式碼

name 是事件名,obj 是釋出者,data 是附加資訊。

通過 Block 訂閱事件

方法與上面通過 selector 訂閱的方法類似:

GLEventHandler 定義如下:

typedef void (^GLEventHandler)(GLEvent *event);
複製程式碼

所以你可以如下用 block 訂閱一個事件:

__weak __typeof__(self) weakSelf = self;
[self subscribe:UIApplicationDidEnterBackgroundNotification handler:^(GLEvent *event) {
    __strong __typeof__(weakSelf) strongSelf = weakSelf;
    [strongSelf appDidEnterBackground];
}];
複製程式碼

這裡的 weak 化是為了避免迴圈引用。對應於前面 selector 的方法,用 block 也有 4 種呼叫方法:

- (id)subscribe:(NSString *)eventName handler:(GLEventHandler)handler;
- (id)subscribe:(NSString *)eventName obj:(id)obj handler:(GLEventHandler)handler;
- (id)subscribeOnce:(NSString *)eventName handler:(GLEventHandler)handler;
- (id)subscribeOnce:(NSString *)eventName obj:(id)obj handler:(GLEventHandler)handler;
複製程式碼

取消訂閱

取消訂閱某個事件:

- (void)unsubscribe:(NSString *)eventName;
複製程式碼

取消訂閱所有事件:

- (void)unsubscribeAll;
複製程式碼

雖然當例項被銷燬時,存在 associated object 中的觀察者也都會被銷燬,但還是建議手動取消訂閱,如根據不同需求,在 -dealloc-viewDidDisappear 方法中取消訂閱。

- (void)dealloc
{
    [self unsubscribeAll];
}
複製程式碼

釋出事件

你可以簡單地釋出一個事件:

[self publish:@"YourEventName"];
複製程式碼

也可以附帶一些資料,很多時候我們會傳入一個 NSDictionary 來附帶更多結構化的資料:

[self publish:@"YourEventName" data:@{@"key": value}]
複製程式碼

迴圈引用

因為所有生成的觀察者都會被 self 引用,所以當你的 block 引用 self 的時候就會形成迴圈引用導致例項無法被釋放,所以你必須 weakify self。強烈推薦用 libextobjc 中的 EXTScope 來做 weakify/strongify:

@weakify(self);
[self subscribe:UIApplicationDidEnterBackgroundNotification handler:^(GLEvent *event) {
    @strongify(self);
    [self appDidEnterBackground];
}];
複製程式碼

相關文章