假期学习-- iOS 通知详解

news/2024/9/17 20:06:08/ 标签: ios, 学习, cocoa

iOS 通知详解

数据结构

从我们之前使用通知的流程和代码来看,通知其实就是一个单例,方便随时访问。

NSNotificationCenter:消息中心

这个单例类中主要定义了两个表,一个存储所有注册通知信息的表的结构体,一个保存单个注册信息的节点结构体。

typedef struct NCTbl {Observation       *wildcard;  // 添加观察者时既没有传入 NotificationName ,又没有传入object,就会加在这个链表上,它里边的观察者可以接收所有的系统通知GSIMapTable       nameless;   // 添加观察者时没有传入 NotificationName 的表GSIMapTable       named;      // 添加观察者时传入了 NotificationName 的表
} NCTable

观察者信息的结构:

typedef struct  Obs {id        observer;   // 观察者对象SEL       selector;   // 方法信息struct Obs    *next;      // 指向下一个节点int       retained;   /* Retain count for structure.  */struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;
named表

在 named 表中,NotifcationName 作为表的 key,因为我们在注册观察者的时候是可以传入一个参数 object 用于只监听指定该对象发出的通知,并且一个通知可以添加多个观察者,所以还需要一张表来保存 object 和 Observer 的对应关系。这张表的是 key、Value 分别是以 object 为 Key,Observer 为 value。用了链表这种数据结构实现保存多个观察者的情况。

534534534

在实际开发过程中 object 参数我们经常传 nil,这时候系统会根据 nil 自动生成一个 key,相当于这个 key 对应的 value(链表)保存的就是当前通知传入了 NotificationName 没有传入 object 的所有观察者。

同理nameless表和wildcard表如下:

5345345

4234234

添加观察者

使用方法addObserver:selector:name:object添加观察者,根据 GNUStep 的源码分析:

- (void) addObserver: (id)observerselector: (SEL)selectorname: (NSString*)nameobject: (id)object
{Observation        *list;Observation        *o;GSIMapTable        m;GSIMapNode        n;
// observer为空时的报错if (observer == nil)[NSException raise: NSInvalidArgumentExceptionformat: @"Nil observer passed to addObserver ..."];
// selector为空时的报错if (selector == 0)[NSException raise: NSInvalidArgumentExceptionformat: @"Null selector passed to addObserver ..."];
// observer不能响应selector时的报错if ([observer respondsToSelector: selector] == NO){[NSException raise: NSInvalidArgumentExceptionformat: @"[%@-%@] Observer '%@' does not respond to selector '%@'",NSStringFromClass([self class]), NSStringFromSelector(_cmd),observer, NSStringFromSelector(selector)];}
// 给表上锁lockNCTable(TABLE);
// 建立一个新Observation,存储这次注册的信息o = obsNew(TABLE, selector, observer);// 如果有nameif (name) {// 在named表中 以name为key寻找valuen = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);// named表中没有找到对应的valueif (n == 0) {// 新建一个表m = mapNew(TABLE);// 由于这是对给定名称的首次观察,因此我们对该名称进行了复制,以便在map中无法对其进行更改(来自GNUStep的注释)name = [name copyWithZone: NSDefaultMallocZone()];// 新建表作为name的value添加在named表中GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);GS_CONSUMED(name)} else { //named表中有对应的value// 取出对应的valuem = (GSIMapTable)n->value.ptr;}// 将observation添加到正确object的列表中// 获取添加完后name对应的value的object对应的链表n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);// n是object的valueif (n == 0) { // 如果object对应value没有数据o->next = ENDOBS;// 将o作为object的value链表的头结点插入GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);} else { // 如果有object对应的value那么就直接添加到原练表的尾部// 在链表尾部加入olist = (Observation*)n->value.ptr;o->next = list->next;list->next = o;}// 这个else if 就是没有name有object的Observation,对object进行的操作相同,} else if (object) {// 直接获取object对应的value链表n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n == 0) { // 这个对应链表如果没有数据o->next = ENDOBS;// 将该observation作为头节点插入GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);} else { // 有数据,将obsevation直接插在原链表的后面list = (Observation*)n->value.ptr;o->next = list->next;list->next = o;}} else {// 既没有name又没有object,就加在WILDCARD链表中o->next = WILDCARD;WILDCARD = o;}// 解锁unlockNCTable(TABLE);
}
流程

1.首先检查添加观察者方法的参数是否正确 ;

2.创建新的Observation存储这次注册的信息

3.先判断name是否存在,存在就把name作为key去named table查找value;没找到就新建表和对应的name作为key-value存入named table中;找到就获取value(object-observation的表) 然后这时在这个表中查找object ;没找到新建object-observation插入表中;找到先取出对应的observation链表,在链表尾部插入 ;

4.然后判断object是否存在,其他都和上面差不多,只不过这次是从nameless table中开始 ;

5.若既没有 NotificationName 也没有 object,那么就加在 WILDCARD 链表中。

发送通知

使用方法postNotification:postNotificationName:object:userInfo或者postNotificationName:object:发送通知,后者默认userInfo为nil,同样使用GNUStep源码进行分析:

- (void) postNotification: (NSNotification*)notification {if (notification == nil) {[NSException raise: NSInvalidArgumentExceptionformat: @"Tried to post a nil notification."];}[self _postAndRelease: RETAIN(notification)];
}- (void) postNotificationName: (NSString*)nameobject: (id)object {[self postNotificationName: name object: object userInfo: nil];
}- (void) postNotificationName: (NSString*)nameobject: (id)objectuserInfo: (NSDictionary*)info {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];
}

最终都只会调用_postAndRelease:方法。

- (void) _postAndRelease: (NSNotification*)notification {Observation        *o;unsigned        count;NSString        *name = [notification name];id                object;GSIMapNode        n;GSIMapTable        m;GSIArrayItem        i[64];GSIArray_t        b;GSIArray        a = &b;// name为空的报错,注册时可以注册无名,注册无名就等于说是所有的通知都能接收,但是发送通知时不可以if (name == nil) {RELEASE(notification);[NSException raise: NSInvalidArgumentExceptionformat: @"Tried to post a notification with no name."];}object = [notification object];GSIArrayInitWithZoneAndStaticCapacity(a, _zone, 64, i);lockNCTable(TABLE);// 查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中for (o = WILDCARD = purgeCollected(WILDCARD); o != ENDOBS; o = o->next){GSIArrayAddItem(a, (GSIArrayItem)o);}// 查找与通知的object相同但是没有name的观察者,加在a数组中if (object) {// 在nameless中找object对应的数据节点n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n != 0) { // 将其加入到新建链表中o = purgeCollectedFromMapNode(NAMELESS, n);while (o != ENDOBS) {GSIArrayAddItem(a, (GSIArrayItem)o);o = o->next;}}}// 查找name的观察者,但观察者的非零对象与通知的object不匹配时除外,加在a数组中if (name) {// 先匹配namen = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));if (n) { // m指向name匹配到的数据m = (GSIMapTable)n->value.ptr;} else {m = 0;}if (m != 0) { // 如果上述name查找到了数据// 首先,查找与通知的object相同的观察者n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n != 0) { // 找到了与通知的object相同的观察者,就加入到新建链表中o = purgeCollectedFromMapNode(m, n);while (o != ENDOBS) {GSIArrayAddItem(a, (GSIArrayItem)o);o = o->next;}}if (object != nil) {// 接着是没有object的观察者,都加在新建链表中n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);if (n != 0) { // 如果没有object并且有数据,就把其中的数据加到新建链表中o = purgeCollectedFromMapNode(m, n);while (o != ENDOBS) {GSIArrayAddItem(a, (GSIArrayItem)o);o = o->next;}}}}}unlockNCTable(TABLE);// 发送通知,给之前新建链表中的所有数据count = GSIArrayCount(a);while (count-- > 0) {o = GSIArrayItemAtIndex(a, count).ext;if (o->next != 0) {NS_DURING {// 给observer发送selector,让其处理[o->observer performSelector: o->selectorwithObject: notification];}NS_HANDLER {BOOL        logged;// 尝试将通知与异常一起报告,但是如果通知本身有问题,我们只记录异常。NS_DURINGNSLog(@"Problem posting %@: %@", notification, localException);logged = YES;NS_HANDLERlogged = NO;NS_ENDHANDLERif (NO == logged){ NSLog(@"Problem posting notification: %@", localException);}  }NS_ENDHANDLER}}lockNCTable(TABLE);GSIArrayEmpty(a);unlockNCTable(TABLE);RELEASE(notification);
}
流程

简单的说就是查找到对应的根据通知的参数查找到对应的observation(不需要添加插入),然后按顺序存到链表中,找完之后按顺序遍历执行 ;

1.首先检查参数中的name是否存在 ,不存在报错 ;

2.首先把wildcard中可以接收所有的observation存入链表中 (查找所有未指定name或object的观察者,加在a数组中,即将wildcard表中的数据都加在新建链表中)

3.然后在nameless table中查找与参数object(先判断obect为nil的情况,然后在判断object为nil的情况,两种情况用于查找的key不同)对应的observation链表,把其中的元素也遍历插入执行链表中 (查找与通知的object相同但是没有name的观察者,加在a数组中)

4.最后在name table中查找,同理,先找name,然后再从中找对应object的obsevation链表,把其中的元素也遍历插入执行链表中 (先匹配name,首先,查找与通知的object相同的观察者,接着是没有object的观察者,都加在新建链表中)

5.最后遍历执行链表中的observation,给observer发送selector,让其处理

  • 注意:关于能不能查找到的问题,我们只需要知道它是从表外往表里找的就行了,下面会提到一个例子 ;

移除通知

不给出源码了:

流程

1.若 NotificationName 和 object 都为 nil,则清空 wildcard 链表。
2.若 NotificationName 为 nil,遍历 named table,若 object 为 nil,则清空 named table,若 object 不为 nil,则以 object 为 key 找到对应的链表,然后清空链表。在 nameless table 中以 object 为 key 找到对应的 observer 链表,然后清空,若 object 也为 nil,则清空 nameless table。
3.若 NotificationName 不为nil,在 named table 中以 NotificationName 为 key 找到对应的 table,若 object 为 nil,则清空找到的 table,若 object 不为 nil,则以 object 为 key 在找到的 table 中取出对应的链表,然后清空链表。

一些问题

下面的方法不会接收到通知?
// 添加观察
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1];
// 通知发送
[NSNotificationCenter.defaultCenter postNotificationName:@"TestNotification" object:nil];

这里我理解的就两点:

  • 注册是无论如何都可以注册的,而且也是从外往里注册的
  • 通知发送(查找)是先从wildcard到nameless table到name table的顺序查找的,而且都是从外往里找的;这样也能知道通知执行的顺序 ;
  • 或者是只要发送通知的参数和观察者的关系是前者包含后者,就可以找到
通知的发送时同步的,还是异步的?发送消息与接收消息的线程是同一个线程么?

通知中心发送通知给观察者是同步的,也可以用通知队列(NSNotificationQueue)异步发送通知。

而且要注意**接收通知的线程,和发送通知所处的线程是同一个线程。**也就是说如果要在接收通知的时候更新 UI,需要注意发送通知的线程是否为主线程。

如何使用异步发送通知?

1.让通知事件处理方法的内部实现再次说明在子线程实现 :

- (void)viewDidLoad {[super viewDidLoad];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {NSLog(@"--current thread: %@", [NSThread currentThread]);NSLog(@"Begin post notification");[[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];NSLog(@"End");
}//- (void)test {
//   NSLog(@"--current thread: %@", [NSThread currentThread]);
//    NSLog(@"Handle notification and sleep 3s");
//    sleep(3);
//}- (void)test {dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{    // 异步执行 + 串行队列NSLog(@"--current thread: %@", [NSThread currentThread]);NSLog(@"Handle notification and sleep 3s");sleep(3);});
}

2.可以通过 NSNotificationQueue 的 enqueueNotification: postingStyle: 和 enqueueNotification: postingStyle: coalesceMask: forModes: 方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。

这里的异步可能有点奇怪,我的理解是:这里的异步是指和发送通知这个任务异步,而不是队列中的通知异步发送 ;

比如:

- (void)viewDidLoad {[super viewDidLoad];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {dispatch_queue_t queue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL);dispatch_async(queue, ^{    // 异步执行 + 串行队列NSLog(@"--current thread: %@", [NSThread currentThread]);NSLog(@"Begin post notification");[[NSNotificationCenter defaultCenter] postNotificationName:@"NotificationName" object:nil];NSLog(@"End");});
}- (void)test {NSLog(@"--current thread: %@", [NSThread currentThread]);NSLog(@"Handle notification and sleep 3s");sleep(3);
}

这里[[NSNotificationCenter defaultCenter] postNotificationName:@“NotificationName” object:nil];在队列中执行时,会添加test的同步执行任务进队列,这样导致了要先等待test执行完毕才会解除阻塞完成[[NSNotificationCenter defaultCenter] postNotificationName:@“NotificationName” object:nil];的任务,最后才能执行NSLog(@“End”);

但如果使用通知队列:

- (void)viewDidLoad {[super viewDidLoad];[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(test) name:@"NotificationName" object:nil];
}- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {NSLog(@"--current thread: %@", [NSThread currentThread]);NSLog(@"Begin post notification");NSNotification *notification = [NSNotification notificationWithName:@"NotificationName" object:nil];[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];NSLog(@"End");
}- (void)test {NSLog(@"--current thread: %@", [NSThread currentThread]);NSLog(@"Handle notification and sleep 3s");sleep(3);
}

这时执行[[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP];时,会把通知的任务加入通知队列,这时发送消息的任务已经完成了,于是就可以向主队列中同步添加NSLog(@“End”);,而通知队列中的通知时异步添加到串行队列队列中的,虽然没有创建新的线程,任务的执行也是顺序执行的,但这也意味着这里的通知执行没有阻塞发送消息的任务,所以这里的通知执行和发送消息的任务是异步的 ;

页面销毁时不移除通知会崩溃吗?

在观察者对象释放之前,需要调用 removeOberver 方法将观察者从通知中心移除,否则程序可能会出现崩溃。**(因为这个时候可能出现野指针)**但从 iOS9 开始,即使不移除观察者对象,程序也不会出现异常。

这是因为在 iOS9 以后,通知中心持有的观察者由 unsafe_unretained 引用变为weak引用。即使不对观察者手动移除,持有的观察者的引用也会在观察者被回收后自动置空。但是通过 addObserverForName:object: queue:usingBlock: 方法注册的观察者需要手动释放,因为通知中心持有的是它们的强引用。

多次添加同一个通知会是什么结果?多次移除通知呢?
  • 多次添加同一个通知,观察者方法会调用多次(可以看之前的源码,在注册观察者时不会对链表里面原来的判断,而是直接加入链表末尾,等到发送通知在表中查找观察者时,只要找到就会执行) ;
  • 多次移除,没关系。
为什么注册通知时可以空名注册,但是发送通知时却不可以?

具体的原因不好说,但实际出现这种现象的原因是因为在发送通知的方法里面最开始就有判段是否空名,注册就没有 ;

object是干嘛的?是不是可以用来传值?

object是用来过滤Notification的,只接收指定的sender所发的Notification,传值请用userInfo,而不是object。


http://www.ppmy.cn/news/1525196.html

相关文章

模版方法模式template method

学习笔记&#xff0c;原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架&#xff0c; 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

神经网络学习笔记——如何设计、实现并训练一个标准的前馈神经网络

1.从零设计并训练一个神经网络https://www.bilibili.com/video/BV134421U77t/?spm_id_from333.337.search-card.all.click&vd_source0b1f472915ac9cb9cdccb8658d6c2e69 一、如何设计、实现并训练一个标准的前馈神经网络&#xff0c;用于手写数字图像的分类&#xff0c;重…

【SpringBoot3】面向切面 AspectJ AOP 使用详解

文章目录 一、AspectJ介绍二、简单使用步骤1、引入依赖2、定义一个Aspect3、开启AOP支持 三、AOP 核心概念四、切点&#xff08;Pointcut&#xff09;1. execution2. within3. this & target4. args & args5. within & target & annotation 五、通知&#xff0…

Unix时间戳与C语言的time.h库函数

目录 Unix时间戳介绍 UTC/GMT 时间与秒计数器转换代码 time.h库函数 Unix时间戳介绍 Unix 时间戳&#xff08;Unix Timestamp&#xff09;定义为从UTC/GMT的1970年1月1日0时0分0秒开始所经过的秒数&#xff0c;不考虑闰秒 时间戳存储在一个秒计数器中&#xff0c;秒计数器…

15.5 创建监控控制平面的service

本节重点介绍 : k8s中service的作用和类型创建k8s控制平面的service 给prometheus采集用&#xff0c; 类型clusterIp kube-schedulerkube-controller-managerkube-etcd service的作用 Kubernetes Service定义了这样一种抽象&#xff1a; Service是一种可以访问 Pod逻辑分组…

MATLAB求解0-1线性规划问题的详细分析

引言 0-1线性规划是整数规划中的一种特殊形式&#xff0c;它广泛应用于资源分配、工厂选址、投资组合优化、物流运输等多个领域。0-1线性规划的特点是&#xff0c;决策变量只能取0或1的离散值&#xff0c;通常用于描述“是-否”决策问题。随着计算机技术的发展&#xff0c;数学…

《仙境传说RO:新启航》游戏攻略:VMOS云手机提升装备获取辅助!自由交易,辅助挂机操作!

在《仙境传说RO&#xff1a;新启航》中&#xff0c;想要快速提升战斗力并获取顶级装备&#xff0c;玩家需要熟练掌握多种获取资源与提升角色的途径。为了让玩家更加轻松地享受游戏&#xff0c;VMOS云手机推出了专属定制版云手机&#xff0c;内置游戏安装包&#xff0c;不需要重…

【c++实现】统计上升四元组

&#x1f308;个人主页&#xff1a;Yui_ &#x1f308;Linux专栏&#xff1a;Linux &#x1f308;C语言笔记专栏&#xff1a;C语言笔记 &#x1f308;数据结构专栏&#xff1a;数据结构 &#x1f308;C专栏&#xff1a;C 文章目录 1. 题目描述2. 解释3. DP前缀和枚举 1. 题目描…

使用 nvm 管理 node 版本:如何在 macOS 和 Windows 上安装使用nvm

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、nvm的安装与基本使用2.1 macOS安装nvm2.1.1 使用 curl 安装2.1.2 使用 Homebrew 安装 2.2 Windows安装nvm2.2.1 下载 nvm-windows2.2.2 安装 nvm-windows 2.3 安装node2.4 切换node版本 三、常见问题及解决方案…

graphQL 参数使用报错问题

query{getMembers(sid:0,nodeId:"ns6;i7896"){methods} } //报错 "message": "Field \"methods\" of type \"[UaMethod]\" must have a selection of subfields. Did you mean \"methods { ... }\"?",这个错误信…

观众登记2025中国(深圳)国际智能手机供应链展览会

时间&#xff1a;2024年4月9-11日 地点&#xff1a;深圳会展中心 ◆展会背景background&#xff1a; 近年来&#xff0c;国内手机品牌在全球市场上的影响力不断增强&#xff0c;华为、OPPO、VIVO和小米等…

实战案例(2)防火墙+二交换机VLAN组网

案例二&#xff1a;防火墙充当三层交换机与路由器角色功能进行组网 拿到这样的拓扑后&#xff0c;首先要了解好客户的需求&#xff0c;然后根据需求进行划分 比如客户那边有监控跟办公网络&#xff0c;可以通过VLAN划分不同的区域&#xff0c;然后二层交换机对接终端的口划入到…

Spring Boot属性注入的多种方式!

Spring Boot的一个问题&#xff0c;证明你是不是真正的 "会用" Spring boot ?Spring Boot的一个问题&#xff0c;直接暴露你是不是真正使用Spring Boothttps://mp.weixin.qq.com/s?__bizMzkzMTY0Mjc0Ng&mid2247484040&idx1&sn64ad15d95e44c874cc890973…

国产服务器CPU发展分析

CPU行业概览&#xff1a;信创带动服务器CPU国产化 目前CPU行业由两大生态体系主导&#xff1a;一是基于X86指令系统和Windows操作系统的Wintel体系&#xff0c;主要用于服务器与电脑等&#xff1b;二是基于ARM指令系统和Android操作系统的AA体系&#xff0c;主要用于移动设备…

机器学习和深度学习存在显著区别

机器学习和深度学习在多个方面存在显著的区别&#xff0c;以下是对这些区别的详细阐述&#xff1a; 定义与起源 机器学习&#xff1a;是人工智能的一个分支领域&#xff0c;它使计算机能够从数据中学习并改进其性能&#xff0c;而无需进行显式编程。机器学习起源于20世纪50年代…

认识原码反码补码

目录 一.何为原码反码和补码? (1)原码 (2)反码 (3)补码 (4)总结 二.原反补之间的简单计算 (1)补码加法 (2) 补码减法 (3) 溢出问题 一.何为原码反码和补码? (1)原码 原码:直接将数值按照正负数的形式翻译成⼆进制得到的就是原码。 符号位&#xff1a;最高位&#xf…

uniapp数据缓存和发起网络请求

数据缓存 uni.onStorageSync同步的方式将数据存储到本地缓存 <template><button click"onStorageSync()">存储数据</button> </template><script setup>const onStorageSync () > {// 存储数据uni.setStorageSync(username, 张三)…

Python——爬虫(2)

要使用Python爬取B站热门视频&#xff0c;可以使用第三方库requests和BeautifulSoup来实现。 首先&#xff0c;你需要安装这两个库。你可以使用以下命令在终端或命令提示符中安装它们&#xff1a; pip install requests beautifulsoup4接下来&#xff0c;你可以使用以下代码来…

使用Astra DB和LangChain构建高效的RAG系统:从入门到实践

使用Astra DB和LangChain构建高效的RAG系统&#xff1a;从入门到实践 1. 引言 检索增强生成&#xff08;Retrieval-Augmented Generation&#xff0c;简称RAG&#xff09;是一种结合了信息检索和文本生成的AI技术&#xff0c;能够显著提升大语言模型的表现。本文将介绍如何使…

React Native 0.76版本发布

关于 React Native 的 New Architecture 概念&#xff0c;最早应该是从 2018 年 RN 团队决定重写大量底层实现开始&#xff0c;因为那时候 React Native 面临各种结构问题和性能瓶颈&#xff0c;最终迫使 RN 团队开始进行重构。 而从 React Native 0.68 开始&#xff0c;New A…