@(iOS 知行路)[源码阅读 | iOS 开发 | NSNotification|iOS 印象]
https://github.com/Glow-Inc/GLPubSub 是
NSNotificationCenter
的封装, 目标是简化 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 来管理第三方依赖, 你也可以直接导入源文件.
下载最新代码 https://github.com/Glow-Inc/GLPubSub/archive/master.zip 并解压
导入
NSObject+GLPubSub.h
和
NSObject+GLPubSub.m
到你的工程, 记得在导入时勾选 "Copy items if needed"
因为 GLPubSub 是基于
NSNotificationCenter
并注册在
[NSNotificationCenter defaultCenter]
的, 所以 GLPubSub 也支持大部分系统通知, 例如
- UIApplicationDidEnterBackgroundNotification
- ,
- UIApplicationDidBecomeActiveNotification
- ,
- UITextFieldTextDidChangeNotification
等等, 但是转发的过程中会丢弃系统通知的 userInfo 字段.
设置 PubSub 的队列
GLPubSub 主要基于
NSNotificationCenter
的
-addObserverForName:object:queue:usingBlock:
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/#//apple_ref/occ/instm/NSNotificationCenter/addObserverForName:object:queue:usingBlock: 方法. 你可以调用 NSObject 的 +setPubSubQueue: 方法来设置传入该方法的 queue.
默认传入的 queue 为 nil, 这意味着所有事件会在发布通知的线程中被执行. 你可以手动设置为
[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 https://github.com/jspahrsummers/libextobjc/blob/master/extobjc/EXTScope.h 来做 weakify/strongify:
- @weakify(self);
- [self subscribe:UIApplicationDidEnterBackgroundNotification handler:^(GLEvent *event) {
- @strongify(self);
- [self appDidEnterBackground];
- }];
复制代码
来源: https://juejin.im/post/5b65aa25e51d453467553744