文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

NotificationCenter类的实现原理是什么

2023-07-05 18:56

关注

这篇文章主要讲解了“NotificationCenter类的实现原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“NotificationCenter类的实现原理是什么”吧!

正文

NotificationCenter是一个系统组件,它负责协调和管理事件的通知和响应。它的基本原理是基于观察者模式!而 Apple 对其是闭源的,因此无法查看 NotificationCenter 的源码,但是可以通过分析开源的 Swift 来理解 NotificationCenter 的实现,以下是一个简化的实现:

简单实现

1、首先定义一个NotificationCenter类定义

class RYNotificationCenter {    private init(){}    static let `default` = RYNotificationCenter()    private var observers: [RYNotificationObserver] = []}

定义了一个单例,用于在整个程序中共享,observers数组用来存储已经注册的所有观察者。

2、然后定义一个观察者对象

观察者对象用来封装具体的观察者的信息。

class RYNotificationObserver {    var name: String    var block: (Notification) -> Void    init(name: String, block: @escaping (Notification) -> Void) {        self.name = name        self.block = block    }}

3、在NotificationCenter中添加注册观察者的方法

func addObserver(name: String, block: @escaping (Notification) -> Void) -> RYNotificationObserver {    let observer = RYNotificationObserver(name: name, block: block)    observers.append(observer)    return observer}

addObserver方法用于注册观察者。在这个实现中,我们创建了一个新 RYNotificationObserver 对象并将其添加到 observers 数组。这个方法返回观察者对象,以便稍后从 NotificationCenter 中移除。

4、在 NotificationCenter 中添加发送通知的方法

/// 发送通知的本质是利用了观察者模式/// 让观察者数组执行闭包中的代码func post(name: String, userInfo: [AnyHashable: Any]? = nil) {    let notification = Notification(name: Notification.Name(name), userInfo: userInfo)    observers        .filter({ $0.name == name })        .forEach { $0.block(notification) }}

post 方法用来发送通知,它接受通知名以及可选的userInfo字典。同时参数都包装在Notification对象中,然后遍历 observers 数组。如果观察者的名称和通知名称匹配,我们将执行保存的block

5、在NotificationCenter中添加移除通知者的方法

func removeObserver(_ observer: RYNotificationObserver) {    if let index = observers.firstIndex(where: { $0 === observer }) {        observers.remove(at: index)    }}

removeObserver 方法用于移除观察者。它接受一个观察者对象并从 observers 数组中移除它。

NotificationCenter的源码分析

普遍来说,现在分析 NotificationCenter 的源码,一般是 github.com/gnustep/lib… ,这是在 gnustep 库的源码中,它和官方的具体实现肯定是有差异的,但是可以以它为参考的对象,在这里通知的源码使用了三个主要的类:

NSNotificationCenter 实现

用于在观察者和发送者之间发送通知,这是核心类,它的方法和Objective-C是一致的,使用 **addObserver:selector:name:object: 方法来添加观察者,但是它在内部使用了C语言实现链表的数据结构 Obs 存储观察者相关的信息:

typedef struct Obs {  id observer;       SEL selector;       struct Obs *next;   int retained;       struct NCTbl *link; } Observation;

而在 postNotificationName:object:userInfo: 方法执行的时候会通过通知名找到封装好的 Obs 观察者,然后执行相应的方法:

- (void) postNotificationName: (NSString*)name      object: (id)object    userInfo: (NSDictionary*)info{  // 先封装好notification  GSNotification *notification;  notification = (id)NSAllocateObject(concrete, 0, NSDefaultMallocZone());  notification->_name = [name copyWithZone: [self zone]];  notification->_object = [object retain];  notification->_info = [info retain];  [self _postAndRelease: notification];}// 然后调用观察者的selector方法- (void) _postAndRealse: (NSNotification*)notification {......[o->observer performSelector: o->selector withObject: notification];......}

当然,要将封装好的 notification ,作为参数传递给观察者需要执行的 selector

NSNotification 实现

那么 Notifiation 呢?它是一个包含了通知的名称、发送者对象以及用户信息字典的不可变对象。

- (id) initWithCoder: (NSCoder*)aCoder{  NSString *name;  id object;  NSDictionary *info;  id n;  [aCoder decodeValueOfObjCType: @encode(id) at: &name];  [aCoder decodeValueOfObjCType: @encode(id) at: &object];  [aCoder decodeValueOfObjCType: @encode(id) at: &info];  n = [NSNotification notificationWithName: name object: object userInfo: info];  RELEASE(name);  RELEASE(object);  RELEASE(info);  DESTROY(self);  return RETAIN(n);}

NSNotificationQueue 的实现

最后是 NSNotificationQueue 的实现,它是一个用于管理通知发送的队列,可以按照特定的发送模式(例如合并相同的通知或按发送顺序)将通知排队。

- (void) enqueueNotification: (NSNotification*)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask: (NSUInteger)coalesceMask forModes: (NSArray*)modes{    if (modes == nil)    {      modes = defaultMode;    }    if (coalesceMask != NSNotificationNoCoalescing)    {      [self dequeueNotificationsMatching: notification coalesceMask: coalesceMask];    }    switch (postingStyle) {        case NSPostNow: {            NSString *mode;            mode = [[NSRunLoop currentRunLoop] currentMode];            if (mode == nil || [modes indexOfObject: mode] != NSNotFound)            {                [_center postNotification: notification];            }        }        break;      case NSPostASAP:            add_to_queue(_asapQueue, notification, modes, _zone);            break;      case NSPostWhenIdle:            add_to_queue(_idleQueue, notification, modes, _zone);            break;    }}

当使用 NSNotificationQueue 的时候,就不需要我们手动发送 Notification 了,NSNotificationQueue 会自动帮我们发送,在上述代码中,如果是 NSPostNow,那么通知会立马被发送,否则就先加入队列中:_asapQueue 或者 _idleQueue ,然后在合适的时候执行队列中的通知,比如:

void GSPrivateNotifyIdle(NSString *mode) {    NotificationQueueList *item;    for (item = currentList(); item; item = item->next)    {        if (item->queue) {            notify(item->queue->_center,                   item->queue->_idleQueue,                   mode,                   item->queue->_zone);        }    }}

问题:如果NotificationCenter 添加的观察者是self,会造成循环引用吗?

答案是:不会!

NotificationCenter 对观察者的引用方式是弱引用(weak),而不是强持有(strong)。因此,当一个对象被销毁时,它的 deinit 方法会被调用,即使它是一个观察者。所以即使我们不在 deinit 方法中添加移除 self 的操作也是可以的,因为 NotificationCenter 并没有对观察者强持有。

问题:如果 NotificationCenter 添加的是 block ,而 block 强持有了 self ,这会造成循环引用吗?

答案是:会!

从iOS 9开始,如果使用了基于 block 的观察者,那么就需要去小心观察者的生命周期了,因为NotificationCenter 对添加的 block 是强持有的,正如上述简单实现中的那样,它对闭包中捕获的变量就也是强持有的,所以为了避免这种现象,需要确保使用 [weak self] 来捕获列表。

在实际使用的时候,由于编码惯性,可能会在 deinit 方法中移除基于 block 的观察者以解决该问题:

class ViewController: UIViewController {    private weak var observer: NSObjectProtocol!    func addObserver() {        observer = NotificationCenter.default.addObserver(forName: NSNotification.Name("test"), object: nil, queue: OperationQueue.main) { _ in            self.view.backgroundColor = UIColor.white        }    }    deinit {        NotificationCenter.default.removeObserver(observer!)    }}

但是在这种情况下, deinit 方法并不会执行! 原因就是 NotificationCenter 持有了 block, 也间接持有了 self,而 NotificationCenter 是一个单例,所以这种持有关系是一直存在的,导致了 deinit 方法并不会执行!

问题:观察者的 selector 执行的线程和发送通知的线程有关吗?

答案是:正相关!

从上文中的简单实现以及GNU的源码中基本可以看出结论了。添加观察者的线程并没有什么影响,而发送通知的线程,其实就是调用方法执行的线程,所以两者是在同一线程执行的。

func addObserver() {    NotificationCenter.default.addObserver(self, selector: #selector(click), name: NSNotification.Name.init("test"), object: nil)    DispatchQueue.global().async {        NotificationCenter.default.post(name: NSNotification.Name.init("test"), object: nil)        NSLog("curretThread1: \(Thread.current)")    }}@objc func click() {    NSLog("curretThread2: \(Thread.current)")}// curretThread2: <NSThread: 0x600001358240>{number = 6, name = (null)}// curretThread1: <NSThread: 0x600001358240>{number = 6, name = (null)}

同时还需要注意的就是通知发送,然后 selector 被执行,这个过程其实本质上是一个观察者模式的实现方式,同时,它也是同步执行的,再执行完发送消息的方法后就会去寻找对应的 Observer ,找到之后就执行相应的 selector ,执行完之后,发送消息的方法才执行完毕了。

所以发送通知和监听通知执行方法的核心是:相同线程执行 且 同步执行。

感谢各位的阅读,以上就是“NotificationCenter类的实现原理是什么”的内容了,经过本文的学习后,相信大家对NotificationCenter类的实现原理是什么这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是编程网,小编将为大家推送更多相关知识点的文章,欢迎关注!

阅读原文内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯