GLPubSub - Glow iOS 中的订阅发布模式

前言

我们计划通过 blog 和开源的方式,分享一些我们在开发 GlowNurture 的过程中积累的开发经验。GLPubSub 作为开源计划的第一步,是一个代码量较少,但很实用的 Category。

GitHub: https://github.com/Glow-Inc/GLPubSub
Documentation: http://cocoadocs.org/docsets/GLPubSub

Notification 作为发布订阅模式(观察者模式)的一种,在 iOS App 的开发过程中很常见,GLPubSub 是 NSNotificationCenter 的封装,目标是简化 iOS 开发中的发布订阅模式。因为是 NSObject 的 Category,所以可以在任意 NSObject 的子类上使用。

安装

CocoaPods

如果通过 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];
}];

License

GLPubSub 基于 MIT 协议开源,详见 LICENSE 文件。