【iOS】——通知机制及底层原理

通知传值概要

  • 通知传值可以跨越多个界面进行传值,一般用于后一个界面向前一个界面传值。

  • 通知传值支持多个接收者,多个对象可以同时接收同一个通知并进行处理。这样可以实现一对多的通信,方便跨多个对象进行值传递。

在这里插入图片描述

使用步骤

1.在发送者中创建并发送通知

[[NSNotificationCenter defaultCenter] postNotificationName:@"identification" object:nil userInfo:@{@"content": self.myTextField.text}];

2.在接收者中注册观察者并接受通知

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(Notificate:) name:@"identification" object:nil];

3.在使用完或不使用时移除观察者

[[NSNotificationCenter defaultCenter] removeObserver:self];

通知机制关键类

NSNotification

用于描述通知的类,一个NSNotification对象就包含了一条通知的信息,所以当创建一个通知时通常包含如下属性:

@interface NSNotification : NSObject <NSCopying, NSCoding>
...
/* Querying a Notification Object */- (NSString*) name; // 通知的name
- (id) object; // 携带的对象
- (NSDictionary*) userInfo; // 配置信息@end

NSNotificationCenter

通知机制的核心是一个与线程关联的单例对象叫通知中心(NSNotificationCenter)。通知中心发送通知给观察者是同步的,也可以用通知队列(NSNotificationQueue)异步发送通知。

static NSNotificationCenter *default_center = nil;+ (NSNotificationCenter*) defaultCenter
{return default_center;
}

NSNotificationCenter类主要负责三件事

  1. 添加通知
  2. 发送通知
  3. 移除通知
// 添加通知
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// 发送通知
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
// 删除通知
- (void)removeObserver:(id)observer;

通知中心定义了两个结构体来存储通知信息和观察者信息:

// 根容器,NSNotificationCenter持有
typedef struct NCTbl {Observation		*wildcard;	/* 链表结构,保存既没有name也没有object的通知 */GSIMapTable		nameless;	/* 存储没有name但是有object的通知	*/GSIMapTable	 named;		/* 存储带有name的通知,不管有没有object	*/...
} NCTable;// Observation 存储观察者和响应结构体,基本的存储单元
typedef	struct	Obs {id		observer;	/* 观察者,接收通知的对象	*/SEL		selector;	/* 响应方法		*/struct Obs	*next;		/* Next item in linked list.	*/...
} Observation;
named表

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

在这里插入图片描述

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

nameless表

nameless 表没有 NotificationName ,即没有了最外边一层键值对的约束了,其中就只有 object 和 Observation 所对应的键值对结构了:

在这里插入图片描述

wildcard表

这个表既没有 NotificationName 也没有 object 了,所以他就会在 nameless基础上在脱去一层键值对,那么它就只剩下一个链表了,该练表存储了可以接收所有通知的类的信息

在这里插入图片描述

NSNotificationQueue

通知队列,用于异步发送消息,这个异步并不是开启线程,而是把通知存到双向链表实现的队列里面,等待某个时机触发时调用NSNotificationCenter的发送接口进行发送通知,这么看NSNotificationQueue最终还是调用NSNotificationCenter进行消息的分发

NSNotificationQueue是依赖runloop的,所以如果线程的runloop未开启则无效

NSNotificationQueue主要做了两件事:

  1. 添加通知到队列
  2. 删除通知
// 把通知添加到队列中,NSPostingStyle是个枚举,下面会介绍
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
// 删除通知,把满足合并条件的通知从队列中删除
- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;

把通知添加到队列等待发送,同时提供了一些附加条件供开发者选择,如:什么时候发送通知、如何合并通知等,系统给了如下定义:

// 表示通知的发送时机
typedef NS_ENUM(NSUInteger, NSPostingStyle) {NSPostWhenIdle = 1, // runloop空闲时发送通知NSPostASAP = 2, // 尽快发送,这种情况稍微复杂,这种时机是穿插在每次事件完成期间来做的NSPostNow = 3 // 立刻发送或者合并通知完成之后发送
};
// 通知合并的策略,有些时候同名通知只想存在一个,这时候就可以用到它了
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {NSNotificationNoCoalescing = 0, // 默认不合并NSNotificationCoalescingOnName = 1, // 只要name相同,就认为是相同通知NSNotificationCoalescingOnSender = 2  // object相同
};

同步通知机制实现

注册通知

使用方法addObserver:selector:name:object添加观察者

- (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对象,持有观察者和SEL,下面进行的所有逻辑就是为了存储它o = obsNew(TABLE, selector, observer);/*======= case1: 如果name存在 =======*/if (name) {//-------- NAMED是个宏,表示名为named字典。以name为key,从named表中获取对应的mapTablen = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);if (n == 0) { // 不存在,则创建 m = mapNew(TABLE); // 先取缓存,如果缓存没有则新建一个mapGSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);...}else { // 存在则把值取出来 赋值给mm = (GSIMapTable)n->value.ptr;}//-------- 以object为key,从字典m中取出对应的value,其实value被MapNode的结构包装了一层,这里不追究细节n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n == 0) {// 不存在,则创建 o->next = ENDOBS;GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);}else {list = (Observation*)n->value.ptr;o->next = list->next;list->next = o;}}
/*======= case2:如果name为空,但object不为空 =======*/else if (object) {// 以object为key,从nameless字典中取出对应的value,value是个链表结构n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);// 不存在则新建链表,并存到map中if (n == 0) { o->next = ENDOBS;GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);}else { // 存在 则把值接到链表的节点上...}}
/*======= case3:name 和 object 都为空 则存储到wildcard链表中 =======*/else {o->next = WILDCARD;WILDCARD = o;}// 解锁unlockNCTable(TABLE);
}

NCTable结构体中核心的三个变量以及功能:wildcardnamednameless,在源码中直接用宏定义表示了:WILDCARDNAMELESSNAMED

存在name(无论object是否存在)
  • 找到NCTable中的named表,这个表存储了还有name的通知

  • name作为key,找到value,这个value依然是一个map

  • map的结构以object作为key,Observation对象为value,这个Observation对象的结构是链表结构,主要存储了observer & SEL

  • 然后把刚开始创建的Observation对象o存储进去

image.png

name不存在,只存在object
  1. object为key,从nameless表中取出value,此value是个Observation类型的链表
  2. 把创建的Observation类型的对象o存储到链表中

image.png

没有name和object

直接把Observation对象存放在了Observation *wildcard 链表结构中

发送通知

使用方法postNotification:, postNotificationName:object:userInfo或者postNotificationName:object:发送通知,后者默认userInfo为nil

// 发送通知
- (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对象, GSNotification继承了NSNotificationGSNotification	*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:方法。不同的是,postNotification:方法外部直接传了一个NSNotification对象,其他两个方法都是内部进行了处理包装 成为了一个NSNotification对象,我们再看看_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);
}

上面的代码精简后如下:

//发送通知的核心函数,主要做了三件事:查找通知、发送、释放资源
- (void) _postAndRelease: (NSNotification*)notification {//step1: 从named、nameless、wildcard表中查找对应的通知...//step2:执行发送,即调用performSelector执行响应方法,从这里可以看出是同步的[o->observer performSelector: o->selectorwithObject: notification];//step3: 释放资源RELEASE(notification);
}

总共分为查找通知、发送、释放资源三个流程:

查找通知

  • 1.首先会创建一个数组 observerArray 用来保存需要通知的 observer。
  • 2.遍历 wildcard 链表,将 observer 添加到 observerArray 数组中。
  • 3.若存在 object,在 nameless table 中找到以 object 为 key 的链表,然后遍历找到的链表,将 observer 添加到 observerArray 数组中。
  • 4.若存在 NotificationName,在 named table 中以 NotificationName 为 key 找到对应的 table,然后再在找到的 table 中以 object 为 key 找到对应的链表,遍历链表,将 observer 添加到 observerArray 数组中。如果 object 不 为nil,则以 nil 为 key 找到对应的链表,遍历链表,将 observer 添加到 observerArray 数组中。
  • 5.至此所有关于当前通知的 observer(wildcard + nameless + named)都已经加入到了数组 observerArray 中。

发送通知

取出其中 的observer 节点(包含了观察者对象和 selector)通过performSelector:逐一调用sel,这是个同步操作

释放资源

释放notification对象

总结一下流程:

从三个存储容器中:namednamelesswildcard去查找对应的Observation对象,然后通过performSelector:逐一调用响应方法,这就完成了发送流程

删除通知

调用removeObserver:方法移除通知

- (void) removeObserver: (id)observer {if (observer == nil)return;[self removeObserver: observer name: nil object: nil];
}- (void) removeObserver: (id)observername: (NSString*)nameobject: (id)object {// 当其要移除的信息都为空时,直接返回if (name == nil && object == nil && observer == nil)return;lockNCTable(TABLE);// name和object都为nil,就在wildcard链表里删除对应observer的注册信息if (name == nil && object == nil) {WILDCARD = listPurge(WILDCARD, observer);}// name为空时if (name == nil) {GSIMapEnumerator_t        e0;GSIMapNode                n0;// 首先尝试删除为此object对应的所有命名项目// 在named表中e0 = GSIMapEnumeratorForMap(NAMED);n0 = GSIMapEnumeratorNextNode(&e0);while (n0 != 0) {GSIMapTable                m = (GSIMapTable)n0->value.ptr;NSString                *thisName = (NSString*)n0->key.obj;n0 = GSIMapEnumeratorNextNode(&e0);if (object == nil) { // 如果object为空,直接清除named表// 清空named表GSIMapEnumerator_t        e1 = GSIMapEnumeratorForMap(m);GSIMapNode                n1 = GSIMapEnumeratorNextNode(&e1);while (n1 != 0) {GSIMapNode        next = GSIMapEnumeratorNextNode(&e1);purgeMapNode(m, n1, observer);n1 = next;}} else {// 以object为key找到对应链表,清空该链表GSIMapNode        n1;n1 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n1 != 0) {purgeMapNode(m, n1, observer);}}if (m->nodeCount == 0) {mapFree(TABLE, m);GSIMapRemoveKey(NAMED, (GSIMapKey)(id)thisName);}}// 开始操作nameless表if (object == nil) { // object为空时// 清空nameless表e0 = GSIMapEnumeratorForMap(NAMELESS);n0 = GSIMapEnumeratorNextNode(&e0);while (n0 != 0) {GSIMapNode        next = GSIMapEnumeratorNextNode(&e0);purgeMapNode(NAMELESS, n0, observer);n0 = next;}} else { // object不为空// 找到对应的observer链表,清空该链表n0 = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n0 != 0) {purgeMapNode(NAMELESS, n0, observer);}}} else { // name不为空GSIMapTable                m;GSIMapEnumerator_t        e0;GSIMapNode                n0;n0 = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));// 如果没有和这个name相同的key,直接返回if (n0 == 0) {unlockNCTable(TABLE);return;                /* Nothing to do.        */}m = (GSIMapTable)n0->value.ptr; // 找到name作为key对应的数据信息if (object == nil) {// 如果object为nil,就清空刚才找到的name对应的数据信息e0 = GSIMapEnumeratorForMap(m);n0 = GSIMapEnumeratorNextNode(&e0);while (n0 != 0) {GSIMapNode        next = GSIMapEnumeratorNextNode(&e0);purgeMapNode(m, n0, observer);n0 = next;}} else {// 如果object不为空,清空object对应的链表n0 = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n0 != 0) {purgeMapNode(m, n0, observer);}}// 因为其中的数据清除完了,所以记得清除named表中的作为key的nameif (m->nodeCount == 0) {mapFree(TABLE, m);GSIMapRemoveKey(NAMED, (GSIMapKey)((id)name));}}unlockNCTable(TABLE);
}
name和object都不存在

清空 wildcard 链表

name不存在(不管object存不存在)

遍历 named table,若 object 为 nil,则清空 named table,若 object 不为 nil,则以 object 为 key 找到对应的链表,然后清空链表。在 nameless table 中以 object 为 key 找到对应的 observer 链表,然后清空,若 object 也为 nil,则清空 nameless table。

name存在

在 named table 中以 NotificationName 为 key 找到对应的 table,若 object 为 nil,则清空找到的 table,若 object 不为 nil,则以 object 为 key 在找到的 table 中取出对应的链表,然后清空链表。

异步通知机制实现

异步通知机制通过NSNotificationQueue的异步发送,从线程的角度看并不是真正的异步发送,或可称为延时发送,它是利用了runloop的时机来触发的

入队

/*
* 把要发送的通知添加到队列,等待发送
* NSPostingStyle 和 coalesceMask在上面的类结构中有介绍
* modes这个就和runloop有关了,指的是runloop的mode
*/ 
- (void) enqueueNotification: (NSNotification*)notificationpostingStyle: (NSPostingStyle)postingStylecoalesceMask: (NSUInteger)coalesceMaskforModes: (NSArray*)modes
{......// 判断是否需要合并通知if (coalesceMask != NSNotificationNoCoalescing) {[self dequeueNotificationsMatching: notificationcoalesceMask: coalesceMask];}switch (postingStyle) {case NSPostNow: {...// 如果是立马发送,则调用NSNotificationCenter进行发送[_center postNotification: notification];break;}case NSPostASAP:// 添加到_asapQueue队列,等待发送add_to_queue(_asapQueue, notification, modes, _zone);break;case NSPostWhenIdle:// 添加到_idleQueue队列,等待发送add_to_queue(_idleQueue, notification, modes, _zone);break;}
}
  1. 根据coalesceMask参数判断是否合并通知
  2. 接着根据postingStyle参数,判断通知发送的时机,如果不是立即发送则把通知加入到队列中:_asapQueue_idleQueue

发送通知

static void notify(NSNotificationCenter *center, NSNotificationQueueList *list,NSString *mode, NSZone *zone)
{......// 循环遍历发送通知for (pos = 0; pos < len; pos++){NSNotification	*n = (NSNotification*)ptr[pos];[center postNotification: n];RELEASE(n);}......	
}
// 发送_asapQueue中的通知
void GSPrivateNotifyASAP(NSString *mode)
{notify(item->queue->_center,item->queue->_asapQueue,mode,item->queue->_zone);
}
// 发送_idleQueue中的通知
void GSPrivateNotifyIdle(NSString *mode)
{notify(item->queue->_center,item->queue->_idleQueue,mode,item->queue->_zone);
}

runloop触发某个时机,调用GSPrivateNotifyASAP()GSPrivateNotifyIdle()方法,这两个方法最终都调用了notify()方法

notify()所做的事情就是调用NSNotificationCenterpostNotification:进行发送通知

主线程响应通知

异步线程发送通知则响应函数也是在异步线程,如果执行UI刷新相关的话就会出问题,那么如何保证在主线程响应通知呢?

其实也是比较常见的问题了,基本上解决方式如下几种:

  1. 使用addObserverForName: object: queue: usingBlock方法注册通知,指定在mainqueue上响应block
  2. 在主线程注册一个machPort,它是用来做线程通信的,当在异步线程收到通知,然后给machPort发送消息,这样肯定是在主线程处理的

总结

注册通知

  • 在同步通知中存储是以nameobject为维度的,即判定是不是同一个通知要从nameobject区分,如果他们都相同则认为是同一个通知,后面包括查找逻辑、删除逻辑都是以这两个为维度的。
  • 通过name和object将通知存储划分为三种结构:named表(name存在)、nameless表(name不存在)、wildcard链表(name和object都不存在)
  • 在通知的存储过程并没有做去重操作,这也解释了为什么同一个通知注册多次则响应多次

发送通知:

  • 首先创建一个数组来存储接收通知的观察者,接着根据name和object作区分来遍历wildcard链表、nameless表、named表
  • 遍历所有列表,意味着注册多次通知就会响应多次
  • 通过performSelector:逐一调用sel进行发送,这是个同步操作
  • 接收通知的线程,和发送通知所处的线程是同一个线程。也就是说如果要在接收通知的时候更新 UI,需要注意发送通知的线程是否为主线程。

删除通知:

  • 查找时仍然以nameobject为维度的,再加上observer做区分

  • 因为查找时做了这个链表的遍历,所以删除时会把重复的通知全都删除掉

异步通知

  • 依赖runloop,所以如果在其他子线程使用NSNotificationQueue,需要开启runloop

  • 最终还是通过NSNotificationCenter进行发送通知,所以这个角度讲它还是同步的

  • 所谓异步,指的是非实时发送而是在合适的时机发送,并没有开启异步线程

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

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/875989.shtml

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

相关文章

Latent Factor Analysis via Dynamical Systems:LFADS (Nature methods 2018)

Nature Methods&#xff1a;https://www.nature.com/articles/s41592-018-0109-9 LFADS Run Manager for Matlab&#xff1a;https://lfads.github.io/lfads-run-manager/ 目录 Single session:假设&#xff1a;方法&#xff1a;结果&#xff1a; Multi-sessions:假设&#xff…

【C++】位运算:两整数之和

1.题目 2.算法思路 本题不能只用 -&#xff0c;那大概率用到位运算符。 异或的作用是无进位相加&#xff0c;所以需要通过异或运算&#xff08;^&#xff09;来替代加法运算&#xff0c;但是我们无法确定进位的信息。所以需要与运算&#xff08;&&#xff09;来得到进位的…

【Android】Activity生命周期与五种启动模式

文章目录 生命周期返回栈Activity状态生命周期方法 启动模式standard模式singleTask模式singleTop模式singleInstance模式singleInstancePerTask模式配置方式 生命周期 返回栈 每个Activity的状态由它在Activity栈&#xff08;又叫“回退栈back stack”&#xff09;中的位置决…

MySQL高性能读写分离实战

介绍 我的上一篇文章实现了mysql的主从复制结构&#xff0c;今天这篇继续将如何实现读写分离。 读写分离是指&#xff1a;针对数据库的写操作&#xff08;插入、更新、删除等&#xff09;访问主数据库&#xff0c;读操作访问从数据库。 因为一般网站的读请求的数量是远远大于…

《数据结构:顺序实现二叉树》

文章目录 一、树1、树的结构与概念2、树相关术语 二、二叉树1、概念与结构2、满二叉树3、完全二叉树 三、顺序二叉树存储结构四、实现顺序结构二叉树1、堆的概念与结构2、堆的实现3、堆的排序 一、树 1、树的结构与概念 树是一种非线性的数据结构&#xff0c;它是由n&#xff…

【Python机器学习】决策树的构造——划分数据集

分类算法除了需要测量信息熵&#xff0c;还需要划分数据集&#xff0c;度量划分数据集的熵&#xff0c;以便判断当前是否正确划分了数据集。 我们将对每个特征划分数据集的结果计算一次信息熵&#xff0c;然后判断按照哪个特征划分数据集是最好的划分方式。 想象一个分部在二…

4-4 数值稳定性 + 模型初始化和激活函数

数值稳定性 这里的 t t t表示层&#xff0c;假设 h t − 1 h^{t-1} ht−1是第 t − 1 t-1 t−1层隐藏层的输出&#xff0c;经过一个 f t f_{t} ft​得到第 t t t层隐藏层的输出 h t h^{t} ht。 y y y表示 x x x进来&#xff0c;第一层一直到第 d d d层&#xff0c;最后到一个损…

研究生选择学习Android开发的利与弊?

在开始前刚好我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「Android的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01;产品经理可以学学Axure快…

vscode远程免密登入

1、windows (1) 点击左下角 ->将当前窗口连接到主机…->&#xff08;配置ssh文件&#xff0c;点击或者指定&#xff09;->按这个格式添加 (2) 在cmd命令窗口&#xff1a;ssh-keygen -t rsa (-C “name”) #这个name是远程linux的用户名&#xff0c;这里也可以不加-C…

H264编码器实现-帧内预测之像素值预测

前言 本文所介绍的像素值预测&#xff0c;是指在帧内预测总体流程中的预测块每个像素值的推导过程。当我们已知向量像素的重建值的时候&#xff0c;我们就可以对当前预测块进行像素值预测。该过程得到的结果将与源像素值相减得到残差&#xff0c;为后续变换量化提供数据来源。…

Godot入门 04平台设计

新建创景&#xff0c;添加AnimatableBody2D节点。 添加Sprite2D节点 拖动图片 剪裁图片&#xff0c;吸附模式&#xff1a;像素吸附 添加CollisionShape2D&#xff0c;设置实际形状为矩形 重命名AnimatableBody2D节点为Platform&#xff0c;保存场景&#xff0c;拖动platform场景…

C#初级——条件判断语句、循环语句和运算符

条件判断语句 简单的条件判断语句&#xff0c;if()里面进行条件判断&#xff0c;如果条件判断正确就执行语句块1&#xff0c;如果不符合就执行语句块2。 if (条件判断) { 语句块1 } else { 语句块2 } int age 18;if (age < 18){Console.WriteLine("未…

深入理解 Java 虚拟机第三版(周志明)

这次社招选的这本作为 JVM 资料查阅&#xff0c;记录一些重点 1. 虚拟机历史 Sun Classic VM &#xff1a;已退休 HotSpot VM&#xff1a;主流虚拟机&#xff0c;热点代码探测技术 Mobile / Embedded VM &#xff1a;移动端、嵌入式使用的虚拟机 2.2 运行时数据区域 程序计…

软件测试20个基础面试题及答案

什么是软件测试&#xff1f; 答案&#xff1a;软件测试是指在预定的环境中运行程序&#xff0c;为了发现软件存在的错误、缺陷以及其他不符合要求的行为的过程。 软件测试的目的是什么&#xff1f; 答案&#xff1a;软件测试的主要目的是保证软件的质量&#xff0c;并尽可能…

“消费增值风暴:百万业绩背后的创新电商模式“

今日&#xff0c;我怀着无比激动的心情&#xff0c;向您揭示一个激励人心的成长篇章。我们的战略伙伴在短短一个月内&#xff0c;业绩如火箭般攀升&#xff0c;成功跨越百万销售额大关&#xff0c;同时&#xff0c;其用户活跃度居高不下&#xff0c;日均在线用户数稳稳占据8至1…

[Unity] ShaderGraph实现镜头加速线/残血效果 URP

效果如下所示&#xff1a;残血状态时&#xff0c;画面会压暗角&#xff0c;并出现速度线营造紧迫感。 使用到的素材如下&#xff0c;换别的当然也可以。[这是张白色的png放射图&#xff0c;并非皇帝的新图hhh] 这个效果的实现逻辑&#xff0c;其实就是利用time向圆心做透明度的…

HAL库源码移植与使用之低功耗模式

低功耗特性对用电池供电的产品&#xff1a; 更小电池体积&#xff08;降低了大小和成本&#xff09; 延长电池寿命 电磁干扰更小&#xff0c;提高无线通信质量 电源设计更简单&#xff0c;无需过多考虑散热问题 电源供电区分为&#xff1a; 分为VDD供电区…

友思特应用 | 硅片上的光影贴合:UV-LED曝光系统在晶圆边缘曝光中的高效应用

导读 晶圆边缘曝光是帮助减少晶圆涂布过程中多余的光刻胶对电子器件影响的重要步骤。友思特 ALE/1 和 ALE/3 UV-LED 高性能点光源&#xff0c;作为唯一可用于宽带晶圆边缘曝光的 i、h 和 g 线的 LED 解决方案&#xff0c;可高效实现WEE系统设计和曝光需求。 晶圆边缘曝光及处…

分布式相关理论详解

目录 1.绪论 2.什么是分布式系统&#xff0c;和集群的区别 3.CAP理论 3.1 什么是CAP理论 3.2 一致性 3.2.1 计算机的一致性说明 1.事务中的一致性 2.并发场景下的一致性 3.分布式场景下的一致性 3.2.2 一致性分类 3.2.3 强一致性 1.线性一致性 a) 定义 a) Raft算法…

通过ATS软件抓取ios手机日志方法记录

1.ios手机下载描述符文件&#xff0c;用于过检测 下载网址&#xff1a;https://developer.apple.com/bug-reporting/profiles-and-logs/?nameB 点击这个下载&#xff0c;之后在手机通用-VPN与设备管理里面找到刚才下载的描述文件然后安装&#xff1b; 2024.6月后注意会提示描…