iis服务器助手广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >NotificationCenter类实现原理
  • 595
分享到

NotificationCenter类实现原理

NotificationCenter 原理NotificationCenter 源码解析 2023-05-13 14:05:41 595人浏览 泡泡鱼
摘要

目录正文1、首先定义一个NotificationCenter类定义2、然后定义一个观察者对象3、在NotificationCenter中添加注册观察者的方法4、在 Notificat

正文

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 库的源码中,它和官方的具体实现肯定是有差异的,但是可以以它为参考的对象,在这里通知的源码使用了三个主要的类:

  • NSNotification
  • NSNotificationCenter
  • NSNotificationQueue

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 原理的资料请关注编程网其它相关文章!

--结束END--

本文标题: NotificationCenter类实现原理

本文链接: https://www.lsjlt.com/news/202142.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • NotificationCenter类实现原理
    目录正文1、首先定义一个NotificationCenter类定义2、然后定义一个观察者对象3、在NotificationCenter中添加注册观察者的方法4、在 Notificat...
    99+
    2023-05-13
    NotificationCenter 原理 NotificationCenter 源码解析
  • NotificationCenter类的实现原理是什么
    这篇文章主要讲解了“NotificationCenter类的实现原理是什么”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“NotificationCenter类的实现原理是什么”吧!正文Not...
    99+
    2023-07-05
  • java原子类实现的原理是什么
    Java原子类的实现原理是利用了底层的CAS(Compare and Swap)操作。CAS是一种乐观锁机制,它包含三个参数:内存位...
    99+
    2023-10-18
    java
  • 详解Redis数据类型实现原理
    目录1. 对象的类型与编码① type属性② encoding 属性和 *prt 指针2. 字符串对象① 编码② 编码的转换3. 列表对象① 编码② 编码转换4. 哈希对象① 编码②...
    99+
    2024-04-02
  • Redis源码类型的实现原理是什么
    这期内容当中小编将会给大家带来有关Redis源码类型的实现原理是什么,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。  Redis源码类型的实现原理有什么  Redis内部...
    99+
    2024-04-02
  • golang 函数类型转换的原理和实现
    go语言中函数类型转换通过创建一个新的函数实现,将函数类型实参转换为目标类型,而无需重新实现。原理如下:源函数和目标函数必须具有相同的入参数量和类型,以及相同的返回值类型(或兼容类型)。...
    99+
    2024-05-03
    golang 类型转换 go语言
  • Typescript类型检查原理之Override如何实现
    这篇文章主要介绍了Typescript类型检查原理之Override如何实现的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Typescript类型检查原理之Override如何实现文章都会有所收获,下面我们一起...
    99+
    2023-06-05
  • Session实现原理
    HTTP协议 ( http://www.w3.org/Protocols/ )是“一次性单向”协议。 服务端不能主动连接客户端,只能被动等待并答复客户端请求。客户端连接服务端,发出一个HTTP Request,服务端处理请求,并且返回一个H...
    99+
    2023-06-03
  • MySQL中索引类型的实现原理是什么
    本篇文章给大家分享的是有关MySQL中索引类型的实现原理是什么,小编觉得挺实用的,因此分享给大家学习,希望大家阅读完这篇文章后可以有所收获,话不多说,跟着小编一起来看看吧。  一、按表列属性分类:  1.单...
    99+
    2024-04-02
  • Go iota关键字与枚举类型实现原理
    目录1. iota关键字2. 枚举类型1. iota关键字 iota是go语言的常量计数器,只能在常量的表达式中使用,其值从0开始,在const中每新增一行iota自己增长1,其值一...
    99+
    2024-04-02
  • Java虚拟机类装载:原理、实现与应用
    Java虚拟机类装载:原理、实现与应用[@more@]  一、引言   Java虚拟机(JVM)的类装载就是指将包含在类文件中的字节码装载到JVM中, 并使其成为JVM一部分的过程。JVM的类动态装载技术能够在运行时刻动态地加载或者替换系统...
    99+
    2023-06-03
  • golang map 实现原理
    在学习 Golang 的过程中,map 是我们经常使用的一种数据结构,可以用来存储 key-value 对。但是,你是否想过 map 的实现原理是什么呢?在本文中,我们将探究 Golang 中 map 的实现原理。map 实现原理简介map...
    99+
    2023-05-15
  • redis锁实现原理
    redis锁是一种分布式锁机制,通过以下步骤实现:1. 获取锁(setnx);2. 释放锁(del);3. 设置过期时间(expire);4. 锁竞争。它优势在于分布式、简单、高效、可扩...
    99+
    2024-04-19
    redis 并发访问
  • Cookie 实现的原理
    基于响应头set-cookie和请求头cookie实现 第一次访问服务器,响应头会返回 set-Cookie 第二次访问,会带着上次请求返回的 Cookie 访问 也需要注意,...
    99+
    2024-04-02
  • C++多态的实现与原理及抽象类实例分析
    这篇文章主要讲解了“C++多态的实现与原理及抽象类实例分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“C++多态的实现与原理及抽象类实例分析”吧!多态的概念多态: 从字面意思来看,就是事物...
    99+
    2023-06-29
  • Java线程的并发工具类实现原理解析
    目录一、fork/join1.Fork-Join原理2.工作窃取3.代码实现二、CountDownLatch三、CyclicBarrier四、Semaphore五、Exchange六...
    99+
    2024-04-02
  • redis缓存实现原理
    redis 缓存机制通过键值对存储、内存存储、过期策略、数据结构、复制和持久化来实现。它遵循获取数据、缓存命中、缓存不命中、写入缓存、更新缓存的步骤,提供快速的数据访问和高性能的缓存服务...
    99+
    2024-04-19
    redis 数据访问 键值对
  • VueRouter深扒实现原理
    目录回顾Vue Router的核心代码代码实现创建Vue-Router插件构造函数完整代码Vue Router官网 前置知识:插件、slot插槽、mixins混入、render函数、...
    99+
    2024-04-02
  • PHP中上传文件类型的实现原理是什么
    PHP中上传文件类型的实现原理是什么,相信很多没有经验的人对此束手无策,为此本文总结了问题出现的原因和解决方法,通过这篇文章希望你能解决这个问题。实现上传,首先要在HTML上加入上传文件的“<FORM>”表格。<form meth...
    99+
    2023-06-17
  • Ajax原生如何实现MIME类型
    小编给大家分享一下Ajax原生如何实现MIME类型,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!问题描述下面的例子是一个Ajax的post请求的代码,这段代码在测...
    99+
    2023-06-08
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作