假期学习-- iOS 通知详解

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.rhkb.cn/news/419674.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

音视频入门基础:WAV专题(10)——FFmpeg源码中计算WAV音频文件每个packet的pts、dts的实现

一、引言 从文章《音视频入门基础&#xff1a;WAV专题&#xff08;6&#xff09;——通过FFprobe显示WAV音频文件每个数据包的信息》中我们可以知道&#xff0c;通过FFprobe命令可以打印WAV音频文件每个packet&#xff08;也称为数据包或多媒体包&#xff09;的信息&#xff0…

程序员要失业了,一行代码没写,就完成了一个个人简历网页模版的创建

今天发现了一个好用的工——Cursor, 真的一行代码都没有写&#xff0c;完成了一个个人简历网页的创建&#xff0c;快来体验一下吧&#xff01; 官网: https://www.cursor.com/ 价格&#xff08;近乎免费&#xff09; 先来看一下价格&#xff0c;cursor 非免费&#xff0c;但是…

[数据集][目标检测]肺炎检测数据集VOC+YOLO格式4983张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;4983 标注数量(xml文件个数)&#xff1a;4983 标注数量(txt文件个数)&#xff1a;4983 标注…

springboot个性化大学生线上聊天交友系统

基于springbootvue实现的个性化大学生线上聊天交友系统 &#xff08;源码L文ppt&#xff09;4-017 4系统设计 4.1 软件功能模块设计 个性化大学生线上聊天交友分为两个模块&#xff0c;分别是管理员功能模块和用户功能模块。主要功能模块包括&#xff…

如何实现输入手机号查询座位号或桌号?

如何通过关键词查询信息&#xff1f; 一、简介 在大型活动中&#xff0c;如公司年会&#xff0c;快速定位座位或桌号对于参与者来说非常重要。本文将指导您如何使用云分组小程序&#xff0c;通过输入手机号来查询座位号或桌号&#xff0c;确保每位参与者都能轻松找到自己的座位…

uniapp交互反馈

页面交互反馈可以通过:uni.showToast(object)实现,常用属性有 ioc值说明 值说明success显示成功图标&#xff0c;此时 title 文本在小程序平台最多显示 7 个汉字长度&#xff0c;App仅支持单行显示。error显示错误图标&#xff0c;此时 title 文本在小程序平台最多显示 7 个汉字…

51单片机个人学习笔记11(AT24C02-I2C总线)

前言 本篇文章属于STC89C52单片机&#xff08;以下简称单片机&#xff09;的学习笔记&#xff0c;来源于B站教学视频。下面是这位up主的视频链接。本文为个人学习笔记&#xff0c;只能做参考&#xff0c;细节方面建议观看视频&#xff0c;肯定受益匪浅。 [1-1] 课程简介_哔哩…

【Qt| 入门知识】怎样创建一个最简单 Qt 界面程序

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; ⏰发布时间⏰&#xff1a; 2024-09-09 …

开启计算机的ssh 22端口

当我们使用一台服务器的时候&#xff0c;经常需要开启ssh 服务&#xff0c;有些系统没有默认开启服务&#xff0c;需要需要我们做一些配置&#xff0c;以下是完整配置过程和错误解决方法&#xff1a; 以下过程适合于当我们购买一台云主机时候配置远程登录 开启本地计算机的22…

孕期全攻略·如何成为一位准爸爸准妈妈

【智慧孕育&#xff0c;科学护航】 步入孕期&#xff0c;每一位准妈妈、准爸爸都渴望给予宝宝最好的开始。《孕期全攻略》汇集权威专家建议&#xff0c;为您打造一站式智慧孕育指南。从孕期各阶段的身体变化解析&#xff0c;到孕期疾病的预防与应对&#xff1b;从科学胎教方法&…

【Java】解析方法的调用关系

目录 一、方法的定义二、方法的声明格式三、方法的调用四、特殊方法&#xff1a;程序入口main方法五、方法的局部变量六、类的静态变量七、类的静态方法八、方法的传参九、方法的重载 一、方法的定义 方法就是 将功能重复的代码封装成一段独立的代码&#xff0c;通过调用方法的…

【网络】UDP协议的简单使用

目录 服务器 客户端 测试 code for Udp_echo_serve Udp_dict_serve UDP是基于socket&#xff08;基于IP和port进行通信就叫做socket通信&#xff09;进行网络通信的&#xff0c;那我们这篇博客就来介绍一下基于UDP通信的基本流程&#xff0c;先让服务端和客户端进行简单的…

深入探索Unity协程:揭开CSharp迭代器背后的神秘面纱

协程是一种特殊类型的迭代器方法&#xff0c;允许你在多个帧之间分段执行代码。可以用来处理时间延迟、异步操作和顺序执行的任务&#xff0c;而不阻塞主线程。Unity协程的实现依赖于C#语言提供的迭代器相关的语言特性&#xff0c;所以想要弄清楚Unity协程的底层原理&#xff0…

单例的饿汉式,懒汉式的线程安全问题

1 单例的饿汉式 对象在类加载的时候就创建了&#xff0c;线程安全&#xff0c;速度块&#xff0c;但是浪费空间&#xff0c; public class Hungry {//唯一对象private static final Hungry HUNGRY new Hungry();byte byte1[]new byte[1024];byte byte2[]new byte[1024];byte…

Java的时间复杂度和空间复杂度和常见排序

目录 一丶时间复杂度 二丶空间复杂度 三丶Java常见排序 1. 冒泡排序&#xff08;Bubble Sort&#xff09; 2.插入排序&#xff08;Insertion Sort&#xff09; 3.希尔排序&#xff08;Shell Sort&#xff09; 4.选择排序&#xff08;Selection Sort&#xff09; 5.堆排序&am…

qmt量化交易策略小白学习笔记第61期【qmt编程之期权行情数据--get_market_data_ex函数】

qmt编程之获取期权数据 期权行情数据 qmt更加详细的教程方法&#xff0c;会持续慢慢梳理。 也可找寻博主的历史文章&#xff0c;搜索关键词查看解决方案 &#xff01; 获取期权行情数据 获取期权最新数据&#xff0c;首先需要进行数据订阅。完成合约订阅后&#xff0c;用g…

【OpenCV】灰度化处理

文章目录 1. 图像灰度化处理对比2. 代码示例3. 二值化处理 1. 图像灰度化处理对比 2. 代码示例 #include <opencv2/opencv.hpp> using namespace cv;int main() {Mat currentImage imread("path_to_image.jpg"); // 读取彩色图像Mat grayImage;// 将彩色图像…

Rust的常数、作用域与所有权

【图书介绍】《Rust编程与项目实战》-CSDN博客 《Rust编程与项目实战》(朱文伟&#xff0c;李建英)【摘要 书评 试读】- 京东图书 (jd.com) Rust到底值不值得学&#xff0c;之一 -CSDN博客 Rust到底值不值得学&#xff0c;之二-CSDN博客 Rust的数据类型-CSDN博客 3.7 常…

HTTP 二、进阶

四、安全 1、TLS是什么 &#xff08;1&#xff09;为什么要有HTTPS ​ 简单的回答是“因为 HTTP 不安全”。由于 HTTP 天生“明文”的特点&#xff0c;整个传输过程完全透明&#xff0c;任何人都能够在链路中截获、修改或者伪造请求 / 响应报文&#xff0c;数据不具有可…

阿里不认命

​ 转载&#xff1a;新熵 原创 作者丨萧维 编辑丨影蕨 国家定调了&#xff01;一系列积极信号为平台经济注入一剂强心针&#xff0c;阿里迎来新生。 最近&#xff0c;阿里捷报频传&#xff01; 先是8月28日&#xff0c;阿里巴巴完成香港双重主要上市。紧接着&#xff0c;8月…