An overview of the

NSNotification is a “synchronous” one-way and thread-safe message notification mechanism provided by Apple (and messages can carry information). By registering messages with the singleton notification center, the observer can receive messages from the specified object or any other object, and can realize the “unicast” or “broadcast” message mechanism. And the observer and receiver can be completely decoupled to achieve cross-layer messaging;

Synchronization: Message sending needs to wait for the observer to finish processing the message before continuing.

One-way: the sender only sends the message, and the receiver does not need to reply to the message.

Thread-safe: Messages are sent and received in the same line, and there is no need to deal with thread synchronization, more on this later;

use

NSNotification

NSNotification contains some information about message sending, including name message name, object message sender, and additional information carried by userInfo message sender. Its class structure is as follows:

@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 */ @endCopy the code

You can build an NSNotification object as an instance or as a class;

NSNotificationCenter

NSNotificationCenter Indicates the message notification center in global singleton mode (each process has a default notification center by default for intra-process communication). You can obtain the notification center SMS by using the following methods:

For macOS systems, each process has a default distributed NSDistributedNotificationCenter notification center, specific see NSDistributedNotificationCenter

+ (NSNotificationCenter *)defaultCenter
Copy the code

The specific method for registering notification messages is as follows:

// register observer - (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), Tvos watchos (2.0), (9.0));Copy the code

The observer registration method provides two forms: selector and block. The observer cannot be nil for adding a specified observer object. The block method executes the copy method, returns the used anonymous observer object, and specifies the observer’s operation object NSOperationQueue to process the message.

Both the specified message name and the sender object object can be empty, that is, all messages and all messages sent by sending objects can be received. If either or both are specified, the message with the specified message name and sender is received.

If the queue specified in block mode can be nil, it is processed by default on the sending thread. If the main queue is specified, that is, the main thread processing, to avoid UI operations caused by exceptions.

Note: Registering observer notification messages should avoid duplicate registration, which would result in duplicate processing of notification messages, andblockTo hold external objects, so you need to avoid causing circular reference problems;

The message sending method is as follows:

- (void)postNotification:(NSNotification *)notification; - (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject; - (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;Copy the code

Messages can be sent through the notification message object wrapped in NSNotification, or they can be sent by specifying the message name, sender and carried information respectively. In synchronous execution mode, the method will return to continue until all registered observers finish processing the notification message. The notification object is executed in the queue specified by the registration message for block mode, and is processed in the same thread for non-block mode.

Note: The message sending type must be the same as the type at the time of registration. That is, if the registered observer specifies the message name and sender at the same time, the sending message must be specified at the same time. Otherwise, the message cannot be received.

Remove the observer as follows:

- (void)removeObserver:(id)observer; - (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;Copy the code

Remove all notification messages of the specified observer, that is, the observer does not receive any messages, generally used for the observer object dealloc release call, but in ios9 and macos10.11 do not need to manually call, dealloc has been automatically processed;

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.

You can also remove single or all messages by specifying the message name or sender (remove all messages of the corresponding type by setting nil);

NSNotificationQueue

NSNotificationQueue Implements the management of notification messages, such as message sending time, message merging strategy, and manages messages in first-in, first-out mode. However, the actual message sending is still completed by the NSNotificationCenter notification center.

@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;
Copy the code

Can get the current thread binding through defaultQueue notification message queue, but can be by initWithNotificationCenter: to specify to inform management center, the news of the specific management strategies are as follows:

NSPostingStyle: Used to configure when notifications are sent

  • NSPostASAP: Notifications are sent when the current notification call or the timer ends
  • NSPostWhenIdle: Notifies when runloop is idle
  • NSPostNow: Immediately after the merger notification is completed.

NSNotificationCoalescing (note that this is an NS_OPTIONS) : Used to configure how to combine notifications

  • NSNotificationNoCoalescing: no merger notification
  • NSNotificationCoalescingOnName: according to the notification name merger notification
  • NSNotificationCoalescingOnSender: according to the incoming object merger notification

If the NSPostNow mode is not specified for the NSNotificationQueue notification queue, it can be sent asynchronously through runloop.

NSNotification and multithreading

The official documentation for NSNotification and multithreading is as follows:

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.

In other words, both the sending and receiving of NSNotification are processed in the same thread. In the case of block, the receiving is processed in the specified queue. This has been explained above.

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.

If officially stated; This processing scenario exists for notification threads that are not the main thread, such as background threads, and the official implementation scheme is also provided:

One way to implement redirection is to customize a Notification queue (not an NSNotificationQueue object, but an array) to maintain the notifications we need to redirect. We still register a Notification observer as usual. When the Notification arrives, we first check to see if the thread that posted the Notification is the one we expected. If not, we store the Notification in our queue. And sends a Mach signal to the desired thread to tell it that it needs to process a Notification. Upon receiving the signal, the specified thread removes the Notification from the queue and processes it.

Official demo is as follows:

@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
Copy the code

Notifications thread definition class MyThreadedClass contains notification message queue notifications used to record all notification messages, notifications received thread notificationThread, mutex NSLock required for multi-threaded concurrent processing, NSMachPort for notification handling of thread handling notification messages Example methods of setting thread attributes, handling Mach messages and handling notification messages are provided.

The setUpThreadSupport method is as follows:

- (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.notificationPort
            forMode:(NSString __bridge *)kCFRunLoopCommonModes];
}
Copy the code

Initialize the class properties and specify the NSMachPort proxy and add it to the runloop of the processing thread. If a Mach message arrives without the receiving thread’s Runloop running, the kernel saves the message until the next runloop runs. But can be by performSelectro: inThread: withObject: waitUtilDone: modes to realize, but needs to be enabled for the child thread runloop, otherwise this method fails, and specify the waitUtilDone parameters to NO asynchronous calls;

The NSMachPortDelegate protocol method handles the following:

- (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];
}
Copy the code

The NSMachPort protocol method is mainly to check any notification messages that need to be processed and process them iteratively (to avoid sending a large number of port messages concurrently, resulting in message loss), and remove them synchronously from the message queue after the processing is completed.

The notification processing method is as follows:

- (void)processNotification:(NSNotification *)notification {
    if ([NSThread currentThread] != notificationThread) {
        // Forward the notification to the correct thread.
        [self.notificationLock lock];
        [self.notifications addObject:notification];
        [self.notificationLock unlock];
        [self.notificationPort sendBeforeDate:[NSDate date]
                components:nil
                from:nil
                reserved:0];
    }
    else {
        // Process the notification here;
    }
}
Copy the code

In order to distinguish the internal invocation of NSMachPort protocol method from the notification processing message callback, it is necessary to determine the current processing thread to process different notification message processing modes. For the notification observation callback, add the message to the message queue and send an interthread communication Mach message; In fact, the core of this scheme is through asynchronous communication between threads NSMachPort to notify the receiving thread to process the message in the notification queue;

Call the following method to start notification message processing for the receiving thread:

[self setupThreadingSupport]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) Name :@"NotificationName"// NotificationName :nil];Copy the code

The official also gives the problems and thinking of this scheme:

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

It is better to subclass an NSNotficationCenter yourself (see GYNotificationCenter at Github) or write a separate class to handle this forwarding.

The principle of analytic

Notification open source gnustep-base-1.25.0 code to analyze the specific implementation of notification;

The data structure of _GSIMapTable mapping table is as follows:

The related data structures are as follows:

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_VALUE
    GSIMapVal	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_EXTRA
  GSI_MAP_EXTRA	extra;
#endif
};
Copy the code

GSIMapTable includes nodeChunks, a pointer array pointing to GSIMapNode, which records the number of nodes in each linked list and the address at the head of the linked list through buckets. BucketCount, nodeCount, and chunkCount record the number of node nodes, single-linked list information array, and single-linked list pointer array respectively.

The code to add/remove from the mapping table is as follows:

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;
}
Copy the code

In fact, it is a hash table structure, which can fetch the first element of each single linked list as an array or as a linked list. Arrays can easily fetch each one-way linked list, and then use the linked list structure to add and delete.

The structure of the notification global object table is as follows:

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;
Copy the code

The most important data structures are two Gsimaptables: named, Nameless and wildcard;

  • namedHolds the notification with the name of the incoming notificationhashTable;
  • namelessSave the one without an incoming notification namehashTable;
  • wildcardSave has neither notification name nor incomingobjectNotification singly linked list;

The notification table named needs to register an object. Therefore, the structure of this table is passed the name as the key, where the value is also an observer of the GSIMapTable table used to store the corresponding object.

For nameless, which only passes object object without notification name, only the corresponding relationship between object and observer needs to be saved. Therefore, object is used as key and observer as value.

The specific core function for adding observers (the block form is just a wrapper around this function) looks like this:

- (void) addObserver: (id)observer selector: (SEL)selector name: (NSString*)name object: (id)object { Observation *list; Observation *o; GSIMapTable m; GSIMapNode n; // Check exception handling... // Lock table to keep data consistency lockNCTable(table); // Create an Observation object that wraps the corresponding calling function o = obsNew(TABLE, selector, observer); If (name) {// Table n = GSIMapNodeForKey(NAMED, (GSIMapKey)(id)name); M = mapNew(TABLE); m = mapNew(TABLE); m = mapNew(TABLE); m = mapNew(TABLE); m = mapNew(TABLE); m = mapNew(TABLE); name = [name copyWithZone: NSDefaultMallocZone()]; GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m); GS_CONSUMED(name)} else {// Consumed = (GSIMapTable)n->value. N = GSIMapNodeForSimpleKey(m, (GSIMapKey)object); If (n == 0) {if (n == 0) {if (n == 0) {o->next = ENDOBS; // The end of the single-linked list observer points to ENDOBS GSIMapAddPair(m, (GSIMapKey)object, (GSIMapVal)o); List = (Observation*)n->value.ptr; list = (Observation*)n->value.ptr; o->next = list->next; list->next = o; Else if (object) {// Get table n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object); If (n == 0) {if (n == 0) {obServerGnustep-base-1.25.0o ->next = ENDOBS; GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o); List = (Observation*)n->value.ptr; list = (Observation*)n->value.ptr; o->next = list->next; list->next = o; O ->next = WILDCARD; o->next = WILDCARD; WILDCARD = o; } // unlockNCTable(TABLE); }Copy the code

For block form the code is as follows:

- (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);
    }
}
Copy the code

For block form, create GSNotificationObserver, which copies the block via Block_copy and determines the notification operation queue. In the didReceiveNotification notification, addOperation is used to process the specified action queue, otherwise block is executed;

The core function for sending notifications is roughly as follows:

- (void) _postAndRelease: (NSNotification*)notification
{
    // check the validation
    // Create an array GSIArray to store all matching notifications
   	// Lock the table to avoid data consistency problems
    // Get all WILDCARD notifications and add them to the array
    // Find the notification of the corresponding observer object specified in the NAMELESS table and add it to the array
		// Find the corresponding notification in the NAMED table and add it to the array
    / / unlock the table
    / / traverse the entire array and, in turn, calls the performSelector: withObject handle notification message is sent
    // Unlock the table and release resources
}
Copy the code

The key is to get sent above all matching notice, and through the performSelector: withObject send notification messages, so inform the sending and receiving thread is the same thread (block form specified by operation queue queue processing);

Reference

Notification Programming Topics

NotificationCenter

Foundation: NSNotificationCenter

Notification and Multithreading

NSDistributedNotificationCenter

In-depth understanding of iOS NSNotification

Understand NSNotificationCenter in depth

IOS communication mode (KVO, Notification, Delegate, Block, target-Action)