【iOS印象】GLPubSub 源码阅读笔记

栏目: IOS · 发布时间: 6年前

内容简介:@(iOS知行路)[源码阅读|iOS开发|NSNotification|iOS印象]如果你的项目没有用 CocoaPods 来管理第三方依赖,你也可以直接导入源文件。

@(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}]
复制代码

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Python Web开发:测试驱动方法

Python Web开发:测试驱动方法

Harry J.W. Percival / 安道 / 人民邮电出版社 / 2015-10 / 99

本书从最基础的知识开始,讲解Web开发的整个流程,展示如何使用Python做测试驱动开发。本书由三个部分组成。第一部分介绍了测试驱动开发和Django的基础知识。第二部分讨论了Web开发要素,探讨了Web开发过程中不可避免的问题,及如何通过测试解决这些问题。第三部分探讨了一些高级话题,如模拟技术、集成第三方插件、Ajax、测试固件、持续集成等。本书适合Web开发人员阅读。一起来看看 《Python Web开发:测试驱动方法》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具