【iOS】—— 通知模式和代理模式

通知

最熟悉的观察者模式

  • 观察者和被观察者都无需知晓对方,只需要通过标记在NSNotificationCenter中找到监听该通知所对应的类,从而调用该类的方法。
  • 并且在NSNotificationCenter中,观察者可以只订阅某一特定的通知,并对其做出相应,而不用对某一个类发的所有通知都进行更新操作。
  • NSNotificationCenter对观察者的调用不是随机的,而是遵循注册顺序一一执行的,并且在该线程内是同步的。

通知的具体使用步骤:

  • 创建通知对象
NSNotification *notice = [NSNotification notificationWithName:@"send" object:self userInfo:@{@"name":_renameTextField.text,@"pass":_repassTextField.text}];
  • 通知中心发送通知
[[NSNotificationCenter defaultCenter] postNotification:notice];
  • 注册通知 添加观察者来指定一个方法、名称和对象,接受到通知时执行这个指定的方法。
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(recive:) name:@"send" object:nil];
  • 接受通知后调用的方法
- (void)recive:(NSNotification *)notice {
    NSDictionary *dictionary = notice.userInfo;
    _nameTextField.text = dictionary[@"name"];
    _passTextField.text = dictionary[@"pass"];
}
总结:
  • 接收通知的类注册监听者并实现接收通知的事件函数
  • 触发通知的类在适当的时候发送通知

通知底层实现原理

iOS9.0之前,通知中心对观察者对象进行unsafe_unretained引用,当被引用的对象释放时不会自动置为nil,指针仍指向该内存空间,造成了野指针,引起EXC_BAD_ACCESS崩溃。

iOS9.0之后,不对观察对象进行移除也不会造成崩溃,这是因为通知中心对观察者做了弱引用(weak引用),对象销毁时会对对象的指针置空。在代码编写过程中,基于严谨性,最好在注册了通知之后,也要removeObserver

NSNotification

NSNotification包含了消息发送的一些信息,包括name消息名称、object消息发送者、userinfo消息发送者携带的额外信息,其类结构如下:

@interface NSNotification : NSObject <NSCopying, NSCoding>

@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;

- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;

@end

@interface NSNotification (NSNotificationCreation)

+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

- (instancetype)init /*API_UNAVAILABLE(macos, ios, watchos, tvos)*/;	/* do not invoke; not a valid initializer for this class */

@end

可以通过实例方式构建NSNotification对象,也可以通过类方式构建;

NSNotificationCenter

NSNotificationCenter消息通知中心,全局单例模式(每个进程都默认有一个默认的通知中心,用于进程内通信),通过如下方法获取通知中心:

+ (NSNotificationCenter *)defaultCenter

对于macOS系统,每个进程都有一个默认的分布式通知中心NSDistributedNotificationCenter

具体的注册通知消息方法如下:

//注册观察者
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));

注册观察者方法提供了两种形式:selectorblock,对于添加指定观察者对象的方式,observer不能为nilblock方式会执行copy方法,返回的是使用的匿名观察者对象,且指定观察者处理消息的操作对象NSOperationQueue

对于指定的消息名称name及发送者对象object都可以为空,即接收所有消息及所有发送对象发送的消息;若指定其中之一或者都指定,则表示接收指定消息名称及发送者的消息

对于block方式指定的queue队列可为nil,则默认在发送消息线程处理;若指定主队列,即主线程处理,避免执行UI操作导致异常

注意:注册观察者通知消息应避免重复注册,会导致重复处理通知消息,且block对持有外部对象,因此需要避免引发循环引用问题

消息发送方法如下:
//发送消息
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

可以通过NSNotification包装的通知消息对象发送消息,也可以分别指定消息名称、发送者及携带的信息来发送,且为同步执行模式,需要等待所有注册的观察者处理完成该通知消息,方法才会返回继续往下执行,且对于block形式处理通知对象是在注册消息指定的队列中执行,对于非block方式是在同一线程处理

注意:消息发送类型需要与注册时类型一致,即若注册观察者同时指定了消息名称及发送者,则发送消息也需要同时指定消息名称及发送者,否则无法接收到消息

移除观察者方法如下:
//移除观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

可移除指定的观察者所有通知消息,即该观察者不再接收任何消息,一般用于观察者对象dealloc释放后调用,但在iOS9macos10.11之后不需要手动调用,dealloc已经自动处理

NSNotificationQueue

NSNotificationQueue通知队列实现了通知消息的管理,如消息发送时机、消息合并策略,并且为先入先出方式管理消息,但实际消息发送仍然是通过NSNotificationCenter通知中心完成;

@interface NSNotificationQueue : NSObject
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;

- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;

- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;

可以通过defaultQueue获取当前线程绑定的通知消息队列,也可以通过initWithNotificationCenter:来指定通知管理中心,具体的消息管理策略如下:

NSPostingStyle:用于配置通知什么时候发送

  • NSPostASAP:在当前通知调用或者计时器结束发出通知
  • NSPostWhenIdle:当runloop处于空闲时发出通知
  • NSPostNow:在合并通知完成之后立即发出通知

NSNotificationCoalescing(注意这是一个NS_OPTIONS):用于配置如何合并通知:

  • NSNotificationNoCoalescing:不合并通知
  • NSNotificationCoalescingOnName:按照通知名字合并通知
  • NSNotificationCoalescingOnSender:按照传入的object合并通知

对于NSNotificationQueue通知队列若不是指定NSPostNow立即发送模式,则可以通过runloop实现异步发送

NSNotification与多线程

对于NSNotification与多线程官方文档说明如下:

In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.
翻译如下:
在多线程应用程序中,通知始终在发布通知的线程中传递,该线程可能与观察者注册自身的线程不同。

即是NSNotification的发送与接收处理都是在同一个线程中,对于block形式则是接收处理在指定的队列中处理,上面已说明这点,这里重点说明下如何接收处理在其他线程处理。

For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
翻译如下:
例如,如果在后台线程中运行的对象正在侦听来自用户界面的通知,例如窗口关闭,则您希望在后台线程而不是主线程中接收通知。在这些情况下,您必须在通知在默认线程上传递时捕获通知,并将其重定向到适当的线程。

如官方说明;对于处理通知线程不是主线程的,如后台线程,存在此处理场景,并且官方也提供了具体的实施方案:

一种重定向的实现思路是自定义一个通知队列(注意,不是NSNotificationQueue对象,而是一个数组),让这个队列去维护那些我们需要重定向的Notification。我们仍然是像平常一样去注册一个通知的观察者,当Notification来了时,先看看post这个Notification的线程是不是我们所期望的线程,如果不是,则将这个Notification存储到我们的队列中,并发送一个mach信号到期望的线程中,来告诉这个线程需要处理一个Notification。指定的线程在收到信号后,将Notification从队列中移除,并进行处理。

官方demo如下:
@interface MyThreadedClass: NSObject
/* Threaded notification support. */
@property NSMutableArray *notifications;
@property NSThread *notificationThread;
@property NSLock *notificationLock;
@property NSMachPort *notificationPort;
 
- (void) setUpThreadingSupport;
- (void) handleMachMessage:(void *)msg;
- (void) processNotification:(NSNotification *)notification;
@end

通知线程定义类MyThreadedClass包含了用于记录所有通知消息的通知消息队列notifications,记录当前通知接收线程notificationThread,多线程并发处理需要的互斥锁NSLock,用于线程间通信通知处理线程处理通知消息的NSMachPort;并提供了设置线程属性、处理mach消息及处理通知消息的实例方法

对于setUpThreadSupport方法如下:

- (void) setUpThreadingSupport {
    if (self.notifications) {
        return;
    }
    self.notifications      = [[NSMutableArray alloc] init];
    self.notificationLock   = [[NSLock alloc] init];
    self.notificationThread = [NSThread currentThread];
 
    self.notificationPort = [[NSMachPort alloc] init];
    [self.notificationPort setDelegate:self];
    [[NSRunLoop currentRunLoop] addPort:self.notificationPort
            forMode:(NSString __bridge *)kCFRunLoopCommonModes];
}

主要是初始化类属性,并指定NSMachPort代理及添加至处理线程的runloop中;若mach消息到达而接收线程的runloop没有运行时,内核会保存此消息,直到下一次runloop运行;也可以通过performSelectro:inThread:withObject:waitUtilDone:modes实现,不过对于子线程需要开启runloop,否则该方法失效,且需指定waitUtilDone参数为NO异步调用

NSMachPortDelegate协议方法处理如下:

- (void) handleMachMessage:(void *)msg {
    [self.notificationLock lock];
 
    while ([self.notifications count]) {
        NSNotification *notification = [self.notifications objectAtIndex:0];
        [self.notifications removeObjectAtIndex:0];
        [self.notificationLock unlock];
        [self processNotification:notification];
        [self.notificationLock lock];
    };
 
    [self.notificationLock unlock];
}

NSMachPort协议方法主要是检查需要处理的任何通知消息并迭代处理(防止并发发送大量端口消息,导致消息丢失),处理完成后同步从消息队列中移除;

通知处理方法如下:
- (void)processNotification:(NSNotification *)notification {
    if ([NSThread currentThread] != notificationThread) {
        // Forward the notification to the correct thread.
        [self.notificationLock lock];
        [self.notifications addObject:notification];
        [self.notificationLock unlock];
        [self.notificationPort sendBeforeDate:[NSDate date]
                components:nil
                from:nil
                reserved:0];
    }
    else {
        // Process the notification here;
    }
}

为区分NSMachPort协议方法内部调用及通知处理消息回调,需要通过判定当前处理线程来处理不同的通知消息处理方式;对于通知观察回调,将消息添加至消息队列并发送线程间通信mach消息;其实本方案的核心就是通过线程间异步通信NSMachPort来通知接收线程处理通知队列中的消息;

对于接收线程需要调用如下方法启动通知消息处理:

[self setupThreadingSupport];
[[NSNotificationCenter defaultCenter]
        addObserver:self
        selector:@selector(processNotification:)
        name:@"NotificationName"//通知消息名称,可自定义
        object:nil];

官方也给出了此方案的问题及思考:
其中指出更好地方式是自己去子类化一个NSNotficationCenter或者单独写一个类类处理这种转发。

关于这块的源码理解的不是很透彻,就先没写上,可以看看大佬文章:iOS NSNotification使用及原理实现

代理

又称委托代理,是iOS中常用的一种设计模式

协议,是多个类共享的一个方法列表,在协议中所列出的方法没有相应的实现,由其它类来实现。

委托是指给一个对象提供机会对另一对象中的变化做出反应或者响应另一个对象的行为。其基本思想是协同解决问题

从方法的定义我们不难看出委托模式能够起到两方面的作用

1.代理协助对象主体完成某项操作,将需要定制化的操作通过代理来自定义实现,达到和子类化对象主体同样的作用。
2.事件监听,代理对象监听对象主体的某些重要事件,对事件做出具体响应或通知事件交给需要作出响应的对象来处理。

KVO\KVC\通知\代理\Block 总结

  • KVO/通知 -------> 观察者模式
  • KVC --------> KVC模式
  • 单例模式
  • 代理模式

1. 代理和通知的区别:

  • 效率:代理比通知高;
  • 关联:delegate是强关联,委托和代理双方互相知道。通知是弱关联,不需要知道是谁发,也不需要知道是谁接收。
  • 代理是一对一的关系,通知是一对多的关系。delegate一般是行为需要别人来完成。通知是全局通知。
  • 代理要实现对多个类发出消息可以通过将代理者添加入集合类后遍历,或通过消息转发来实现。

2. KVO和通知的区别:

  • 相同: 都是一对多的关系;
  • 不同: 通知是需要被观察者先主动发出通知,观察者注册监听再响应,比KVO多了发送通知这一步。
  • 监听范围:KVO是监听一个值的变化。通知不局限于监听属性的变化,还可以对多种多样的状态变化进行监听,监听范围广,使用更灵活。
  • 使用场景:KVO的一般使用场景是监听数据变化,通知是全局通知。

3. block和代理的区别:

  • 相同点: block和代理都是回调的方式。使用场景相同。
  • 不同点:
    • block集中代码块,而代理分散代码块。所以block更适用于轻便、简单的回调,如网络传输。 代理适用于公共接口较多的情况,这样做也更易于解耦代码架构。
    • block运行成本高。block出栈时,需要将使用的数据从栈内存拷贝到堆内存。当然如果是对象就是加引用计数,使用完或block置为nil后才消除。 代理只是保存了一个对象指针,直接回调,并没有额外消耗。相对C的函数指针,只是多做了一个查表动作。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值