【iOS】通知原理
我们可以通过看通知的实现机制来了解通知中心是怎么实现对观察者的引用的。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:GNUStep源码GitHub下载地址, 具体源码可以进行查看。
通知的主要流程
- 通知全局对象是一个名为
NCTbl
的结构体,里头有三个重要的成员变量,分别是是两张GSIMapTable
表:named
、nameless
,及单链表wildcard
。named
是存放传入了通知名称的通知的hash
表。nameless
是存放没有传入通知名称但是传入了消息发送者object
的通知的hash
表。wildcard
是存既没有传入通知名称,也没有传入消息发送者的通知的链表。
- 我们每次注册一个通知的时候,所注册的那个通知就会按照这三种类型来对号入座放入相应的
NCTbl
的结构体中的GSIMapTable
或wildcard
。 - 接着我们每次发送通知(发送消息)的时候,就是先创建存储所有匹配通知的数组
GSIArray
,按照以下流程将符合条件的通知添加到数组GSIArray
中。- 获取所有
wildcard
中符合条件的通知并添加到数组GSIArray
。 - 在
nameless
表中查找符合条件的通知并添加到数组GSIArray
。 - 在
named
表中查找符合条件的通知并添加到数组GSIArray
。
- 获取所有
- 最后待所有符合条件的通知都添加好之后,就遍历整个
GSIArray
数组并依次调用performSelector:withObject
处理通知消息发送。
通知原理
数据结构
_GSIMapTable
映射表数据结构图如下:
相关数据结构:
_GSIMapTable
映射表包含了nodeChunks
、bukets
、buketCount
、chunkCount
。nodeChunks
:nodeChunks
是一个指向GSIMapNode
指针数组的指针。它用于管理动态分配的内存块,这些内存块用于存储哈希表的节点(GSIMapNode
)。bukets
:记录单链表节点指针数组的各个链表的节点数量及链表首部地址。bucketCount
:记录了node
节点的数目。chunkCount
:记录单链表节点指针数组的数目。nodeCount
:哈希表中当前已使用的节点数量。
定义源代码:
typedef struct _GSIMapBucket GSIMapBucket_t;
typedef struct _GSIMapNode GSIMapNode_t;typedef GSIMapBucket_t *GSIMapBucket;
typedef GSIMapNode_t *GSIMapNode;typedef struct _GSIMapTable GSIMapTable_t;
typedef GSIMapTable_t *GSIMapTable;struct _GSIMapNode {GSIMapNode nextInBucket; /* Linked list of bucket. */GSIMapKey key;
#if GSI_MAP_HAS_VALUEGSIMapVal value;
#endif
};struct _GSIMapBucket {uintptr_t nodeCount; /* Number of nodes in bucket. */GSIMapNode firstNode; /* The linked list of nodes. */
};struct _GSIMapTable {NSZone *zone;uintptr_t nodeCount; /* Number of used nodes in map. */uintptr_t bucketCount; /* Number of buckets in map. */GSIMapBucket buckets; /* Array of buckets. */GSIMapNode freeNodes; /* List of unused nodes. */uintptr_t chunkCount; /* Number of chunks in array. */GSIMapNode *nodeChunks; /* Chunks of allocated memory. */uintptr_t increment;
#ifdef GSI_MAP_EXTRAGSI_MAP_EXTRA extra;
#endif
};
具体的从映射表中添加/删除的代码如下:
GS_STATIC_INLINE GSIMapBucket
GSIMapPickBucket(unsigned hash, GSIMapBucket buckets, uintptr_t bucketCount)
{return buckets + hash % bucketCount;
}GS_STATIC_INLINE GSIMapBucket
GSIMapBucketForKey(GSIMapTable map, GSIMapKey key)
{return GSIMapPickBucket(GSI_MAP_HASH(map, key),map->buckets, map->bucketCount);
}GS_STATIC_INLINE void
GSIMapLinkNodeIntoBucket(GSIMapBucket bucket, GSIMapNode node)
{node->nextInBucket = bucket->firstNode;bucket->firstNode = node;
}GS_STATIC_INLINE void
GSIMapUnlinkNodeFromBucket(GSIMapBucket bucket, GSIMapNode node)
{if (node == bucket->firstNode){bucket->firstNode = node->nextInBucket;}else{GSIMapNode tmp = bucket->firstNode;while (tmp->nextInBucket != node){tmp = tmp->nextInBucket;}tmp->nextInBucket = node->nextInBucket;}node->nextInBucket = 0;
}
其实就是一个hash表结构,可以以数组的形式取到每个单链表首元素,再利用链表结构增删。
通知全局对象表结构如下:
typedef struct NCTbl {Observation *wildcard; /* Get ALL messages*///获取所有消息GSIMapTable nameless; /* Get messages for any name.*///获取任何名称的消息GSIMapTable named; /* Getting named messages only.*///仅获取命名消息unsigned lockCount; /* Count recursive operations. *///递归运算计数NSRecursiveLock *_lock; /* Lock out other threads. *///锁定其他线程Observation *freeList;Observation **chunks;unsigned numChunks;GSIMapTable cache[CACHESIZE];unsigned short chunkIndex;unsigned short cacheIndex;
} NCTable;
其中数据结构中重要的是两张GSIMapTable
表:named
、nameless
,及单链表wildcard
named
,保存着传入通知名称的通知hash
表nameless
,保存没有传入通知名称但传入了消息发送者object
的hash
表wildcard
,保存既没有通知名称又没有传入object
的通知的单链表
保存含有通知名称的通知表named
需要注册object
对象,因此该表结构体通过传入的name
作为key
。而它的value
也是一个GSIMapTable
表,这个表用于存储对应的object
对象的observer
对象;
对没有传入通知名称只传入object
对象的通知表nameless
而言,只需要保存object
与observer
的对应关系,因此object
作为key
用observer
作为value
。
具体的添加观察者的核心函数(block形式只是该函数的包装)大致代码如下:
- (void) addObserver: (id)observerselector: (SEL)selectorname: (NSString*)nameobject: (id)object
{Observation *list;Observation *o;GSIMapTable m;GSIMapNode n;//入参检查异常处理...//table加锁保持数据一致性lockNCTable(TABLE);//创建Observation对象包装相应的调用函数o = obsNew(TABLE, selector, observer);//处理存在通知名称的情况if (name){//table表中获取相应name的节点n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name);if (n == 0){//未找到相应的节点,则创建内部GSIMapTable表,以name作为key添加到talbe中m = mapNew(TABLE);name = [name copyWithZone: NSDefaultMallocZone()];GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m);GS_CONSUMED(name)}else{//找到则直接获取相应的内部tablem = (GSIMapTable)n->value.ptr;}//内部table表中获取相应object对象作为key的节点n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);if (n == 0){//不存在此节点,则直接添加observer对象到table中o->next = ENDOBS;//单链表observer末尾指向ENDOBSGSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o);}else{//存在此节点,则获取并将obsever添加到单链表observer中list = (Observation*)n->value.ptr;o->next = list->next;list->next = o;}}//只有观察者对象情况else if (object){//获取对应object的tablen = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);if (n == 0){//未找到对应object key的节点,则直接添加observergnustep-base-1.25.0o->next = ENDOBS;GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o);}else{//找到相应的节点则直接添加到链表中list = (Observation*)n->value.ptr;o->next = list->next;list->next = o;}}//处理即没有通知名称也没有观察者对象的情况else{//添加到单链表中o->next = WILDCARD;WILDCARD = o;}//解锁unlockNCTable(TABLE);
}
对于block形式代码如下:
- (id) addObserverForName: (NSString *)name object: (id)object queue: (NSOperationQueue *)queue usingBlock: (GSNotificationBlock)block
{GSNotificationObserver *observer = [[GSNotificationObserver alloc] initWithQueue: queue block: block];[self addObserver: observer selector: @selector(didReceiveNotification:) name: name object: object];return observer;
}- (id) initWithQueue: (NSOperationQueue *)queue block: (GSNotificationBlock)block
{self = [super init];if (self == nil)return nil;ASSIGN(_queue, queue); _block = Block_copy(block);return self;
}- (void) didReceiveNotification: (NSNotification *)notif
{if (_queue != nil){GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] initWithNotification: notif block: _block];[_queue addOperation: op];}else{CALL_BLOCK(_block, notif);}
}
对于block
形式通过创建GSNotificationObserver
对象,该对象会通过Block_copy
拷贝block
,并确定通知操作队列,通知的接收处理函数didReceiveNotification
中是通过addOperation
来实现指定操作队列处理,否则直接执行block
。
发送通知的核心函数大致逻辑如下:
- (void) _postAndRelease: (NSNotification*)notification
{//入参检查校验//创建存储所有匹配通知的数组GSIArray//加锁table避免数据一致性问题//获取所有WILDCARD中的通知并添加到数组中//查找NAMELESS表中指定对应消息发送者对象object的通知并添加到数组中//查找NAMED表中相应的通知并添加到数组中//解锁table//遍历整个数组并依次调用performSelector:withObject处理通知消息发送//解锁table并释放资源
}
上面发送的重点就是获取所有匹配的通知,并通过performSelector:withObject
发送通知消息,因此通知发送和接收通知的线程是同一个线程(block
形式通过操作队列来指定队列处理)。
通知中的各个部分
NSNotification
NSNotification
包含了消息发送的一些信息,包括name
消息名称、object
消息发送者、userinfo
消息发送者携带的额外信息,其类结构如下:
@interface NSNotification : NSObject <NSCopying, NSCoding>@property (readonly, copy) NSNotificationName name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0)) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)coder NS_DESIGNATED_INITIALIZER;@end@interface NSNotification (NSNotificationCreation)+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;- (instancetype)init /*API_UNAVAILABLE(macos, ios, watchos, tvos)*/; /* do not invoke; not a valid initializer for this class */@end
可以通过实例方式构建NSNotification对象,也可以通过类方式构建。(- (instancetype)init
这个方法是初始化方法,但在 NSNotification
类中,它被标记为 API_UNAVAILABLE
,表示不可用。因此,不能直接使用此方法来初始化 NSNotification
对象。)
NSNotificationCenter
NSNotificationCenter
消息通知中心,全局单例模式(每个进程都默认有一个默认的通知中心,用于进程内通信),通过如下方法获取通知中心:
+ (NSNotificationCenter *)defaultCenter
对于macOS系统,每个进程都有一个默认的分布式通知中心NSDistributedNotificationCenter,具体可参见:NSDistributedNotificationCenter。
具体的注册通知消息方法如下:
//注册观察者
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block API_AVAILABLE(macos(10.6), ios(4.0), watchos(2.0), tvos(9.0));
注册观察者方法提供了两种形式:selector
及block
,对于添加指定观察者对象的方式,observer
不能为nil
;block
方式会执行copy
方法,返回的是使用的匿名观察者对象,且指定观察者处理消息的操作对象NSOperationQueue
。
对于指定的消息名称name
及发送者对象object
都可以为空,即接收所有消息及所有发送对象发送的消息;若指定其中之一或者都指定,则表示接收指定消息名称及发送者的消息
对于block
方式指定的queue
队列可为nil
,则默认在发送消息线程处理;若指定主队列,即主线程处理,避免执行UI操作导致异常
注意:注册观察者通知消息应避免重复注册,会导致重复处理通知消息,且block
对持有外部对象,因此需要避免引发循环引用问题。
消息发送方法如下:
//发送消息
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
可以通过NSNotification
包装的通知消息对象发送消息,也可以分别指定消息名称、发送者及携带的信息来发送,且为同步执行模式,需要等待所有注册的观察者处理完成该通知消息,方法才会返回继续往下执行,且对于block形式处理通知对象是在注册消息指定的队列中执行,对于非block
方式是在同一线程处理
注意:消息发送类型需要与注册时类型一致,即若注册观察者同时指定了消息名称及发送者,则发送消息也需要同时指定消息名称及发送者,否则无法接收到消息
移除观察者方法如下:
//移除观察者
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
可移除指定的观察者所有通知消息,即该观察者不再接收任何消息,一般用于观察者对象dealloc
释放后调用,但在iOS9
及macos10.11
之后不需要手动调用,dealloc
已经自动处理。
If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. Otherwise, you should call this method or removeObserver:name:object: before observer or any object specified in addObserverForName:object:queue:usingBlock: or addObserver:selector:name:object:is deallocated.
翻译如下:
如果你的应用目标是iOS 9.0及更高版本或macOS 10.11及更高版本,你不需要在dealloc方法中注销观察者。否则,您应该调用此方法或在取消分配addObserverForName:object:queue:usingBlock:或addObserver:selector:name:object:中指定的任何对象之前删除观察者:name:object:方法。
NSNotificationQueue
NSNotificationQueue
通知队列实现了通知消息的管理,如消息发送时机、消息合并策略,并且为先入先出方式管理消息,但实际消息发送仍然是通过NSNotificationCenter
通知中心完成。
@interface NSNotificationQueue : NSObject
@property (class, readonly, strong) NSNotificationQueue *defaultQueue;- (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER;- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;- (void)dequeueNotificationsMatching:(NSNotification *)notification coalesceMask:(NSUInteger)coalesceMask;
可以通过defaultQueue
获取当前线程绑定的通知消息队列,也可以通过initWithNotificationCenter:
来指定通知管理中心,具体的消息管理策略如下:
NSPostingStyle
:用于配置通知什么时候发送
NSPostASAP
:在当前通知调用或者计时器结束发出通知NSPostWhenIdle
:当runloop处于空闲时发出通知NSPostNow
:在合并通知完成之后立即发出通知
NSNotificationCoalescing
(注意这是一个NS_OPTIONS
):用于配置如何合并通知:
NSNotificationNoCoalescing
:不合并通知NSNotificationCoalescingOnName
:按照通知名字合并通知NSNotificationCoalescingOnSender
:按照传入的object合并通知
对于NSNotificationQueue
通知队列若不是指定NSPostNow
立即发送模式,则可以通过runloop
实现异步发送
NSNotification与多线程
对于NSNotification
与多线程官方文档说明如下:
In a multithreaded application, notifications are always delivered in the thread in which the notification was posted, which may not be the same thread in which an observer registered itself.
翻译如下:
在多线程应用程序中,通知始终在发布通知的线程中传递,该线程可能与观察者注册自身的线程不同。
即是NSNotification的发送与接收处理都是在同一个线程中,对于block形式则是接收处理在指定的队列中处理,上面已说明这点,这里重点说明下如何接收处理在其他线程处理。
For example, if an object running in a background thread is listening for notifications from the user interface, such as a window closing, you would like to receive the notifications in the background thread instead of the main thread. In these cases, you must capture the notifications as they are delivered on the default thread and redirect them to the appropriate thread.
翻译如下:
例如,如果在后台线程中运行的对象正在侦听来自用户界面的通知,例如窗口关闭,则您希望在后台线程而不是主线程中接收通知。在这些情况下,您必须在通知在默认线程上传递时捕获通知,并将其重定向到适当的线程。
如官方说明;对于处理通知线程不是主线程的,如后台线程,存在此处理场景,并且官方也提供了具体的实施方案:
一种重定向的实现思路是自定义一个通知队列(注意,不是NSNotificationQueue对象,而是一个数组),让这个队列去维护那些我们需要重定向的Notification。我们仍然是像平常一样去注册一个通知的观察者,当Notification来了时,先看看post这个Notification的线程是不是我们所期望的线程,如果不是,则将这个Notification存储到我们的队列中,并发送一个mach信号到期望的线程中,来告诉这个线程需要处理一个Notification。指定的线程在收到信号后,将Notification从队列中移除,并进行处理。
官方demo如下:
@interface MyThreadedClass: NSObject
/* Threaded notification support. */
@property NSMutableArray *notifications;
@property NSThread *notificationThread;
@property NSLock *notificationLock;
@property NSMachPort *notificationPort;- (void) setUpThreadingSupport;
- (void) handleMachMessage:(void *)msg;
- (void) processNotification:(NSNotification *)notification;
@end
通知线程定义类MyThreadedClass
包含了用于记录所有通知消息的通知消息队列notifications
,记录当前通知接收线程notificationThread
,多线程并发处理需要的互斥锁NSLock
,用于线程间通信通知处理线程处理通知消息的NSMachPort
;并提供了设置线程属性、处理mach
消息及处理通知消息的实例方法
对于setUpThreadSuppor
t方法如下:
- (void) setUpThreadingSupport {if (self.notifications) {return;}self.notifications = [[NSMutableArray alloc] init];self.notificationLock = [[NSLock alloc] init];self.notificationThread = [NSThread currentThread];self.notificationPort = [[NSMachPort alloc] init];[self.notificationPort setDelegate:self];[[NSRunLoop currentRunLoop] addPort:self.notificationPortforMode:(NSString __bridge *)kCFRunLoopCommonModes];
}
主要是初始化类属性,并指定NSMachPort
代理及添加至处理线程的runloop
中;若mach
消息到达而接收线程的runloop
没有运行时,内核会保存此消息,直到下一次runloop
运行;也可以通过performSelectro:inThread:withObject:waitUtilDone:modes
实现,不过对于子线程需要开启runloop
,否则该方法失效,且需指定waitUtilDone
参数为NO
异步调用。
NSMachPortDelegate
协议方法处理如下:
- (void) handleMachMessage:(void *)msg {[self.notificationLock lock];while ([self.notifications count]) {NSNotification *notification = [self.notifications objectAtIndex:0];[self.notifications removeObjectAtIndex:0];[self.notificationLock unlock];[self processNotification:notification];[self.notificationLock lock];};[self.notificationLock unlock];
}
NSMachPor
t协议方法主要是检查需要处理的任何通知消息并迭代处理(防止并发发送大量端口消息,导致消息丢失),处理完成后同步从消息队列中移除;
通知处理方法如下:
- (void)processNotification:(NSNotification *)notification {if ([NSThread currentThread] != notificationThread) {// 将通知转发到正确的线程。[self.notificationLock lock];[self.notifications addObject:notification];[self.notificationLock unlock];[self.notificationPort sendBeforeDate:[NSDate date]components:nilfrom:nilreserved:0];}else {// Process the notification here;}
}
为区分NSMachPort
协议方法内部调用及通知处理消息回调,需要通过判定当前处理线程来处理不同的通知消息处理方式;对于通知观察回调,将消息添加至消息队列并发送线程间通信mach
消息;其实本方案的核心就是通过线程间异步通信NSMachPort
来通知接收线程处理通知队列中的消息;
对于接收线程需要调用如下方法启动通知消息处理:
[self setupThreadingSupport];
[[NSNotificationCenter defaultCenter]addObserver:selfselector:@selector(processNotification:)name:@"NotificationName"//通知消息名称,可自定义object:nil];
官方也给出了此方案的问题及思考:
First, all threaded notifications processed by this object must pass through the same method (processNotification:). Second, each object must provide its own implementation and communication port. A better, but more complex, implementation would generalize the behavior into either a subclass of NSNotificationCenter or a separate class that would have one notification queue for each thread and be able to deliver notifications to multiple observer objects and methods
翻译如下:
首先,此对象处理的所有线程通知必须通过相同的方法(processNotification:)。第二,每个对象必须提供自己的实现和通信端口。更好但更复杂的实现将行为概括为NSNotificationCenter的一个子类或一个单独的类,每个线程有一个通知队列,并且能够向多个观察者对象和方法传递通知
其中指出更好地方式是自己去子类化一个NSNotficationCenter
(github上有大佬实现了此方案,可参考GYNotificationCenter)或者单独写一个类处理这种转发。
相关文章:

【iOS】通知原理
我们可以通过看通知的实现机制来了解通知中心是怎么实现对观察者的引用的。由于苹果对Foundation源码是不开源的,我们具体就参考一下GNUStep的源码实现。GNUStep的源码地址为:GNUStep源码GitHub下载地址, 具体源码可以进行查看。 通知的主要流程 通知全…...
创建邮件服务器(小微企业)
这里写自定义目录标题 目的硬件选型:软件选型:coremail (商业版本)postfixumail免费开源版本新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适…...

android app控制ros机器人四(调整界面布局)
半吊子改安卓,记录页面布局调整: 在ros-mobile基础上顶端增加一行,用于显示app名称和logo图像;修改标签页。 添加文字简单,但是替换图标长知识了,开始只是简单的把mipmap各个文件夹下的图片进行替换&…...

稍微深度踩坑haystack + whoosh + jieba
说到django的全文检索,网上基本推荐的都是 haystack whoosh jieba 的方案。 由于我的需求对搜索时间敏感度较低,但是要求不能有数据的错漏。 但是没有调试的情况下,搜索质量真的很差,搞得我都想直接用Like搜索数据库算了。 但是…...

微信小程序(van-tabs) 去除横向滚动条样式(附加源码解决方案+报错图)
问题描述 今天第一次接触vant组件库。 ant官网地址适用于Vue3 支持Vue2、Vue3、微信小程序等 我在使用van-tabs组件时遇到了一个问题,如下图所示: 从图片上可以看到有个灰色的横向滚动条,一开始领导给我说这个问题,我反反复复都…...

激光切割机所发出的辐射是否会对人体产生危害呢
激光切割设备所发出的激光作为一种特殊的能量光源,在一定程度上是存在辐射的。由于光纤激光器的功率通常大于半导体激光器,因此其辐射安全性也受到我们的关注。那么这种辐射的危害究竟有多大呢? 第一级:在正常操作下,不会发出对人…...

Redis 高可用:主从复制、哨兵模式、集群模式
文章目录 一、redis高可用性概述二、主从复制2.1 主从复制2.2 数据同步的方式2.2.1 全量数据同步2.2.2 增量数据同步 2.3 实现原理2.3.1 服务器 RUN ID2.3.2 复制偏移量 offset2.3.3 环形缓冲区 三、哨兵模式3.1 原理3.2 配置3.3 流程3.4 使用3.5 缺点 四、cluster集群4.1 原理…...

在GitHub上管理和协作的完全指南
介绍 GitHub 是一个强大的版本控制和协作平台,它不仅可以帮助你管理和跟踪项目的变化,还可以与他人进行协作。本文将详细介绍如何使用 GitHub 的各种功能来管理和协作项目。 目录 注册GitHub账号创建和管理仓库 创建仓库添加和管理文件分支管理合并请…...

git管理工具学习(图解使用git工作流程)
目录 GIT 简介一个最简单的GIT操作流程git的工作流程&命令 GIT 简介 git是什么,在维基百科上是这么介绍的:git是一个分布式的版本控制软件 分布式是相对于集中式而言的,分布式即每一个git库都是一个完整的库。 每个库的地位都是平等的&am…...

单例模式(Singleton)
单例模式保证一个类仅有一个实例,并提供一个全局访问点来访问它,这个类称为单例类。可见,在实现单例模式时,除了保证一个类只能创建一个实例外,还需提供一个全局访问点。 Singleton is a creational design pattern t…...

2023-08-02 LeetCode每日一题(翻转卡片游戏)
2023-08-02每日一题 一、题目编号 822. 翻转卡片游戏二、题目链接 点击跳转到题目位置 三、题目描述 在桌子上有 N 张卡片,每张卡片的正面和背面都写着一个正数(正面与背面上的数有可能不一样)。 我们可以先翻转任意张卡片,…...

JAVAWEB项目--POST完整交互(servlet,axios,JavaScript)
post交互 js: axios.post("/mycsdn/blog/pageSer", {currentPage:currentPage,}).then(function (response) {window.location.href url;}).catch(function (error) {console.error("分页未遂", error);}); 后端servlet: public…...

统一观测|借助 Prometheus 监控 ClickHouse 数据库
引言 ClickHouse 作为用于联机分析(OLAP)的列式数据库管理系统(DBMS), 最核心的特点是极致压缩率和极速查询性能。同时,ClickHouse 支持 SQL 查询,在基于大宽表的聚合分析查询场景下展现出优异的性能。因此,获得了广泛的应用。本文旨在分享阿…...

【Golang】基于录制,自动生成go test接口自动化用例
目录 背景 框架 ginkgo初始化 抓包&运行脚本 目录说明 ∮./business ∮./conf ∮./utils ∮./testcase testcase 用例目录结构规则 示例 实现思路 解析Har数据 定义结构体 解析到json 转换请求数据 转换请求 转换请求参数 写业务请求数据 写gotest测试…...

使用快捷键在Unity中快速锁定和解锁Inspector右上角的锁功能
使用快捷键在Unity中快速锁定和解锁Inspector右上角的锁功能 在Unity中,Inspector窗口是一个非常重要的工具,它允许我们查看和编辑选定对象的属性。有时候,我们可能希望锁定Inspector窗口,以防止意外更改对象的属性。幸运的是&am…...

服务器硬件、部署LNMP动态网站、部署wordpress、配置web与数据库服务分离、配置额外的web服务器
day01 day01项目实战目标单机安装基于LNMP结构的WordPress网站基本环境准备配置nginx配置数据库服务部署wordpressweb与数据库服务分离准备数据库服务器迁移数据库配置额外的web服务器 项目实战目标 主机名IP地址client01192.168.88.10/24web1192.168.88.11/24web2192.168.88…...

面试总被问高并发负载测试,你真的会么?
本文将介绍使用50K并发用户测试轻松运行负载测试所需的步骤(以及最多200万用户的更大测试)。 ❶ 写你的剧本 ❷ 使用JMeter在本地测试 ❸ BlazeMeter SandBox测试 ❹ 使用一个控制台和一个引擎设置每引擎用户数量 ❺ 设置和测试群集(一个…...

ARP协议请求
文章目录 作用请求与应答流程数据包ARP协议以太网帧协议具体应用 作用 通过 IP地址 查找 MAC地址。 请求与应答流程 A:数据发送主机 B:目标主机 目前只知道目标主机IP地址,想把数据发送过去,需要查询到目标主机的MAC地址&#x…...

前端小练-仿掘金导航栏
文章目录 前言项目结构导航实现创作中心移动小球消息提示 完整代码 前言 闲的,你信嘛,还得开发一个基本的门户社区网站,来给到Hlang,不然我怕说工作量不够。那么这个的话,其实也很好办,主要是这个门户网站的UI写起来麻…...
PDF.js实现搜索关键词高亮显示效果
在static\PDF\web\viewer.js找到定义setInitialView方法 大约是在1202行,不同的pdf.js版本不同 在方法体最后面添加如下代码: // 高亮显示关键词---------------------------------------- var keyword new URL(decodeURIComponent(location)).searchP…...

Linux服务器安装JDK20
一、下载安装包 访问官网,找到JDK20,复制下载链接 我复制的链接是:JDK20 二、Linux服务器操作 1.服务器根目录下创建一个新的文件夹 cd /mkdir jdkscd /jdks2.将下载好的jdk-20上传到jdks下 3.解压缩 tar -zxvf jdk-20_linux-x64_bin.tar…...

vue强制刷新的方法
前言 在开发过程中,有时候会遇到这么一种情况: 1.切换页面页面没有更新 2.通过动态的赋值,但是dom没有及时更新,能够获取到动态赋的值,但是无法获取到双向绑定的dom节点, 这就需要我们手动进行强制刷新组件,下面这篇文章主要给大家介绍了关于vue组件强制刷新的方案…...

Linux下TCP网络服务器与客户端通信程序入门
文章目录 目标服务器与客户端通信流程TCP服务器代码TCP客户端代码 目标 实现客户端连接服务器,通过终端窗口发送信息给服务器端,服务器接收到信息后对信息数据进行回传,客户端读取回传信息并返回。 服务器与客户端通信流程 TCP服务器代码 …...

第九章:SSM整合
第九章:SSM整合 9.1:ContextLoaderListener Spring提供了监听器ContextLoaderListener,实现ServletContextListener接口,可监听ServletContext的状态,在web服务器的启动,读取Spring的配置文件…...

shell脚本部署springboot
#!/bin/bashecho "$1 jar包名称,$2 运行环境 " echo "reload jar: $1 env: $2 " if [ -z $1 ];thenecho "请输入jar包名称......." elseecho "停止开始......."IDps -ef | grep "$1" | grep -v "grep"…...

每日一道面试题之Iterator 和 ListIterator 有什么区别?
Iterator 和 ListIterator 都是 Java 集合框架中用于遍历集合元素的接口,但它们有一些区别: 使用的范围:Iterator可以迭代所有集合,而ListIterator 只能用于List及其子类。 继承关系:ListIterator 继承 Iterator,并且ListIterat…...

基于图像形态学处理的停车位检测matlab仿真
目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 4.1. 图像预处理 4.2. 车辆定位 4.3. 停车位检测 5.算法完整程序工程 1.算法运行效果图预览 2.算法运行软件版本 MATLAB2022a 3.部分核心程序 ......................................…...

【网络编程】同步IO/异步IO
同步IO的特点: 同步IO指的是用户进程触发I/O操作并等待或者轮询的去查看I/O操作是否就绪。 同步IO的执行者是IO操作的发起者。 同步IO需要发起者进行内核态到用户态的数据拷贝过程,所以这里必须阻塞 异步IO的特点: 异步IO是指用户进程触发I/O…...

五分钟理解NIO与BIO
java NIO与BIO的区别? BIO -- Blocking IO 即阻塞式 IO。NIO -- Non-Blocking IO, 即非阻塞式 IO 或异步 IO。 BIO 基于字节流和字符流进行操作,数据的读取写入必须阻塞在一个线程内等待其完成。 NIO 主要有三大核心部分: Channel (通道)…...

Python数据可视化工具——Pyecharts
目录 1 简介绘图前先导包 2 折线图3 饼图4 柱状图/条形图5 散点图6 箱线图7 热力图8 漏斗图9 3D柱状图10 其他:配置项 1 简介 Pyecharts是一款将python与echarts结合的强大的数据可视化工具 Pyecharts是一个用于生成echarts图表的类库。echarts是百度开源的一个数据…...