I. Model structure

1.NSNotification

@interface NSNotification: NSObject <NSCopying, NSCoding> // Name of the notification @property (readonly, copy) NSNotificationName name; @property (nullable, readOnly, retain) ID object; @property (nullable, readonly, copy) NSDictionary *userInfo; // Initialize a notification object - (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) // Create a notification + (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject; // create a notification + (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

This object is returned when notifications are received

2.NSNotificationCenter

@interface NSNotificationCenter : NSObject { @package void *_impl; void *_callback; void *_pad[11]; } // The default notification center, globally unique @property (class, readOnly, strong) NSNotificationCenter *defaultCenter; // Register an observer in the notification center - (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; - (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject; // Register an observer in the notification center, Block callbacks take the form of selector - (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)); // The return value is retained by the system, and should be held onto by the caller in // order to remove the observer with removeObserver: later, to stop observation. @endCopy the code

Is a singleton class that manages the creation and delivery of notifications and does three main things

  • registration
  • Send a notification
  • Remove the notification

There are currently two ways to register notifications:selectorblock

// 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

Observer cannot be nil for the way you add 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;

Send a notification

- (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 processed in the queue specified by the registration message for block mode, and 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, the sending message must be specified at the same time. Otherwise, the message cannot be received

Remove the notification

- (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;

3.NSNotificationQueue

@interface NSNotificationQueue : NSObject { @private id _notificationCenter; id _asapQueue; id _asapObs; id _idleQueue; id _idleObs; } @property (class, readonly, strong) NSNotificationQueue *defaultQueue; - (instancetype)initWithNotificationCenter:(NSNotificationCenter *)notificationCenter NS_DESIGNATED_INITIALIZER; // add notifications to the queue - (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle; - (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes; // Delete notification, Combine satisfy the conditions to inform removed from the queue - (void) dequeueNotificationsMatching notification: (NSNotification *) coalesceMask:(NSUInteger)coalesceMask; @endCopy the code

Can get the current thread binding through defaultQueue notification message queue, but can be by initWithNotificationCenter: to specify inform management center

This object does two main things

  1. Add notifications to the queue
  2. Delete the notice

Notification Sending Time

Typedef NS_ENUM(NSUInteger, NSPostingStyle) {NSPostWhenIdle = 1, // NSPostASAP = 2, NSPostNow = 3 // Send immediately or after the merge notification is complete};Copy the code

Notification Merge strategy

// Notification merge strategy, sometimes only one notification of the same name exists, Typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {NSNotificationNoCoalescing = 0, / / the default not merge NSNotificationCoalescingOnName = 1, / / in accordance with the notice name merge notice NSNotificationCoalescingOnSender = 2 / / in accordance with the incoming object merge notice};Copy the code

Use of asynchronous notifications

NSNotification *noti = [NSNotification notificationWithName:@"111" object:nil];
[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
Copy the code

Notify the use of merge

NSNotification *noti = [NSNotification notificationWithName:@"NSNotification" object:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationNoCoalescing forModes:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle coalesceMask:NSNotificationNoCoalescing forModes:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow coalesceMask:NSNotificationNoCoalescing forModes:nil]; // Register a notification named NSNotification [[NSNotificationCenter defaultCenter] addObserver:self selector:*@selector(noti: name:@"NSNotification" object:nil]; - (void) noti:(NSNotification *) noti { NSLog(@"noti"); }Copy the code

When set to NSNotificationNoCoalescing coalesceMask result printed three noti when coalesceMask set to print a noti NSNotificationCoalescingOnName results

4.GSNotificationObserver

@implementation GSNotificationObserver { NSOperationQueue *_queue; // Save the incoming queue GSNotificationBlock _block; // Save the incoming block} - (id) initWithQueue: (NSOperationQueue *)queue block: (GSNotificationBlock)block {...... Initialization operation} - (void) dealloc {.... } // Respond to the method of receiving notifications and execute block - (void) didReceiveNotification: (NSNotification *)notif {if (_queue! = nil){ GSNotificationBlockOperation *op = [[GSNotificationBlockOperation alloc] initWithNotification: notif block: _block]; [_queue addOperation: op]; } else { CALL_BLOCK(_block, notif); } } @endCopy the code

This class is defined in the GNUStep source code. It acts as a proxy observer and implements the interface: addObserverForName: Object: Queue: GSNotificationObserver holds queue and block information and registers with the notification center as an observer. When receiving the notification, the response method is triggered. In the response method, the block is thrown to the specified queue

Ii. Underlying analysis

1._GSIMapTable

The underlying layer of notification is dependent on this mapping table

* A rough picture is include below: * * * This is the map C - array of the buckets * +---------------+ +---------------+ * | _GSIMapTable | /----->| nodeCount | * |---------------| / | firstNode ----+--\ * | buckets ---+----/ | .......... | | * | bucketCount =| size of --> | nodeCount | | * | nodeChunks ---+--\ | firstNode | | * | chunkCount =-+\ | | . | | * |... || | | . | | * +---------------+| | | nodeCount | | * | | | fistNode | | * / | +---------------+ | * ---------- v v * / +----------+ +---------------------------+ * | | * ------+----->| Node1 | Node2 | Node3 ... | a chunk * chunkCount | * ------+--\ +---------------------------+ * is size of = | . | \ +-------------------------------+ * | . | ->| Node n | Node n + 1 | ... | another * +----------+ +-------------------------------+ * array pointing * to the chunksCopy the code

Structure definition

#if ! defined(GSI_MAP_TABLE_T) typedef struct _GSIMapBucket GSIMapBucket_t; typedef struct _GSIMapNode GSIMapNode_t; typedef GSIMapBucket_t *GSIMapBucket; typedef GSIMapNode_t *GSIMapNode; #endif 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. */ }; #if defined(GSI_MAP_TABLE_T) typedef GSI_MAP_TABLE_T *GSIMapTable; #else typedef struct _GSIMapTable GSIMapTable_t; typedef GSIMapTable_t *GSIMapTable; 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

parsing_GSIMapTableThe data structure

From 1.bucketView it as a one-way linked list

  • bucketsIt’s a one-way linked list that storesGSIMapBucket.GSIMapBucketIn itsfirstNode->nextInBucketFor the next onebucket.firstNodeRepresents the first element of another singly linked list.
  • bucketCountsaidbucketsThe number of

From 2.chunkView it as an array pointer

  • nodeChunksRepresents a pointer to an array that stores the first element of all singly linked listsnode
  • chunkCountRepresents array size

In addition, freeNodes is the element that needs to be freed. It is a one-way linked list. This is a hash table structure that can be retrieved either as an array or as a linked list. Each one-way linked list can be easily obtained by array, and the structure of linked list can be added and deleted easily.

// Hash % maximum number to get index, then fetch the list, 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

This is a form of hash table implementation in OC. But for A Java HashMap, it’s even simpler. Hashmap involves red-black tree and other search problems, and is later posted for comparison.

2. Storage containersNCTbl

Notify the global object table structure

// NSNotificationCenter holds typedef struct NCTbl {Observation *wildcard; /* GSIMapTable nameless; /* GSIMapTable nameless; /* Store notification with no name but object */ GSIMapTable named; /* Store notifications with name, regardless of object*/ 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; Typedef struct Obs {id observer; /* Observer, the object that receives the notification */ SEL selector; Struct Obs *next; /* Next item in linked list. */ ... } Observation;Copy the code

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.

3. Registration notification

Core source code –SelectorIn the form of

Name: notification name Object: carries an object */ - (void) addObserver: (id) Observer Selector: (SEL) Selector Name: (NSString*) Name object: (id)object {// Prerequisite check...... O = obsNew(TABLE, selector, observer); // Create an Observation object that holds the observer and SEL. / * = = = = = = = case1: if the name is = = = = = = = * / if (name) {/ / -- -- -- -- -- -- -- -- is a macro NAMED, said called NAMED dictionary. N = GSIMapNodeForKey(named, (GSIMapKey)(id)name); If (n == 0) {// if (n == 0) {m = mapNew(TABLE); GSIMapAddPair(NAMED, (GSIMapKey)(id)name, (GSIMapVal)(void*)m); . M = (GSIMapTable)n->value.ptr; } //-------- use object as the key, retrieve the corresponding value from the dictionary M, actually value is wrapped by the structure of 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: If name is empty but object is not =======*/ else if (object) {// If object is the key, fetch the corresponding value from nameless dictionary, Value is a linked list structure n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object); If (n == 0) {o->next = ENDOBS; GSIMapAddPair(NAMELESS, (GSIMapKey)object, (GSIMapVal)o); } else {// if it exists, join the value to the node of the list... }} /*======= case3: wildcard =======*/ else {o->next = wildcard; WILDCARD = o; }}Copy the code

NCTable structure in the core of the three variables and functions: wildcard, named, nameless, in the source code directly expressed by macro definition: wildcard, nameless, named

Case1:name(Whether object exists or not)

  1. Register notification, if notifiednameExists, thennameAs the key fromnamedRetrieves a value from a dictionaryn(thenIn fact beMapNodeThere is a layer of packaging, this onenOr a dictionary, a variety of empty new logic is not discussed
  2. And then toobjectFor key, from the dictionarynAnd the corresponding value, this value isObservationType (abbreviated afterobs) of the linked list and then put the one you just createdobsobjectoStored in

Data structure diagram:

If a name is passed in to register a notification, it will be a two-tier storage structure

  1. findNCTableIn thenamedTable, this table stores andnameThe notice of
  2. In order tonameAs key, findvaluethevalueIs still amap
  3. mapThe structure ofobjectAs the key,obsThe object is value, this oneobsThe structure of the object is explained above, mainly storedobserver & SEL

Case2: Only objects exist

  1. In order toobjectAs the key, fromnamelessFetch value from the dictionary, value isobsType of linked list
  2. To create theobsObjects of typeoStore it in a linked list

Data structure diagram:



There are onlyobjectTime storage has only one layer, and that isobjectandobsMapping between objects

Case3: There is no name or object

This case stores the OBS object directly in the Observation * Wildcard linked list structure

Core source code –blockIn the form of

The block form is just a wrapper around the Selector

- (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

This interface relies on selector, but with an additional layer of proxy observer GSNotificationObserver

  1. To create aGSNotificationObserverObjects of typeobserverAnd put thequeueandblockpreserved
  2. Call interface 1 to register the notification
  3. Responds when a notification is receivedobserverthedidReceiveNotification:Method, and then indidReceiveNotification:In theblockThrow to the specifiedqueueTo carry out

4. Send notifications

The core code

// Send notification - (void) postNotificationName: (NSString*)name Object: (id)object userInfo: (NSDictionary*)info {// create a GSNotification object, GSNotification inherits NSNotification 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]; } - (void) _postAndRelease: (NSNotification*)notification {//step1: Lookup notifications from named, nameless, wildcard tables... [o->observer Selector: O ->selector withObject: notification]; //step3: RELEASE(notification); }Copy the code

The code above does three main things

  1. throughname & objectFind all of themobsObject (savedobserverandsel) into an array
  2. throughPerformSelector:Call them one by oneselThis is a synchronous operation
  3. The release ofnotificationobject

The source logic can be seen as an overview of the sending process

  1. From three storage containers:named,nameless,wildcardI’m going to look for thetaobsobject
  2. Then throughPerformSelector:Invoking the response methods one by one completes the sending process

5. Delete notifications

  1. The search is still innameandobjectTheta is the dimension of theta plus thetaobserverDo distinguish
  2. Because the search iterates through the list, deletion removes all duplicate notifications
- (void) removeObserver: (id) Observer name: (NSString*)name object: (id)object { if (name == nil && object == nil && observer == nil) return; . } - (void) removeObserver: (id)observer{ if (observer == nil) return; [self removeObserver: observer name: nil object: nil]; }Copy the code

6. Asynchronous notification

The asynchronous sending of the NSNotificationQueue is not really asynchronous from the point of view of the thread, or it can be called delayed sending. It is triggered by the timing of the Runloop

logic

  1. According to thecoalesceMaskParameter Determines whether to merge notifications
  2. Then according to thepostingStyleParameter to determine when a notification should be sent, or to queue it if it is not sent immediately:_asapQueue,_idleQueue

emphasis

  1. Queues are bidirectional linked list implementations
  2. Is called when the postingStyle value is sent immediatelyNSNotificationCenterTo send it, soNSNotificationQueueWill have to rely onNSNotificationCenterTo send
/* * Adds notifications to the queue waiting to be sent * NSPostingStyle and coalesceMask are described in the class structure above * modes which are related to runloop, Runloop mode */ - (void) enqueueNotification: (NSNotification*)notification postingStyle: (NSPostingStyle)postingStyle coalesceMask: (NSUInteger)coalesceMask forModes: (NSArray*)modes { ...... If (coalesceMask! = NSNotificationNoCoalescing) { [self dequeueNotificationsMatching: notification coalesceMask: coalesceMask]; } switch (postingStyle) { case NSPostNow: { ... // If it is sent immediately, NSNotificationCenter is called to send [_center postNotification: notification]; break; } case NSPostASAP: // Add to _asapQueue, wait for sending add_to_queue(_asapQueue, notification, modes, _zone); break; Case NSPostWhenIdle: // Add to _idleQueue and wait for add_to_queue(_idleQueue, notification, modes, _zone); break; }}Copy the code

Sending notifications asynchronously

  • Runloop triggers an opportunity to call the GSPrivateNotifyASAP() and GSPrivateNotifyIdle() methods, both of which end up calling notify()

  • All notify() does is call postNotification: of the NSNotificationCenter to send the notification

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); }... Void GSPrivateNotifyASAP(NSString *mode) {notify(item->queue->_center, item->queue->_asapQueue, mode, item->queue->_zone); Void GSPrivateNotifyIdle(NSString *mode) {notify(item->queue->_center, item->queue->_idleQueue, item->queue->_idleQueue, mode, item->queue->_zone); }Copy the code

forNSNotificationQueueSummarized below

  1. Rely onrunloop, so if used in other child threadsNSNotificationQueueTo enable runloop
  2. And it finally passedNSNotificationCenterSend notifications, so it’s synchronous in this sense
  3. By asynchron, I mean it’s not sent in real time but sent at the right time, without the asynchronous thread being started

The main thread responds to asynchronous notifications

The asynchronous thread sends notifications and the response function is also on the asynchronous thread. If you perform UI refresh related tasks, there will be a problem. So how do you ensure that the notification is answered on the main thread?

In fact, it is also a common problem, basically the solution is as follows:

  1. useaddObserverForName: object: queue: usingBlockMethod registration notification, specified inmainqueueIn response to theblock
@interface A: NSObject <NSMachPortDelegate> - (void)test; @end @implementation A - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)test { [[NSNotificationCenter defaultCenter] addObserverForName:@"111" object:nil queue:[NSOperationQueue MainQueue] usingBlock:^(NSNotification * _Nonnull note) {NSLog(@"current thread % @refresh UI", [NSThread currentThread]); // refresh UI...}]; } @end // output current thread <NSThread: 0x7BF29110 >{number = 3, name = (null)} 2017-02-27 11:53:46.531 Notification [29510:12833116] Current Thread <NSThread: 0x7Be1d6f0 >{number = 1, name = main} Refresh the UICopy the code
  1. Register one in the main threadmachPortIt is used to do thread communication when an asynchronous thread receives a notification and then givesmachPortSend a message that must be handled in the main thread
/ / code # import < CoreFoundation/CFRunLoop. H > @ interface A: NSObject <NSMachPortDelegate> @property NSMutableArray *notifications; @property NSThread *notificationThread; @property NSLock *notificationLock; @property NSMachPort *notificationPort; - (void)setUpThreadingSupport; - (void)handleMachMessage:(void *)msg; - (void)processNotification:(NSNotification *)notification; - (void)test; @end @implementation A - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; } - (void)test { [self setUpThreadingSupport]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"111" object:nil];  } - (void)setUpThreadingSupport { if (self.notifications) { return; } self.notifications = [[NSMutableArray alloc] init]; self.notificationLock = [[NSLock alloc] init]; self.notificationThread = [NSThread mainThread]; self.notificationPort = [[NSMachPort alloc] init]; [self.notificationPort setDelegate:self]; [[NSRunLoop currentRunLoop] addPort:self.notificationPort forMode:(NSString *)kCFRunLoopCommonModes]; } - (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]; } - (void)processNotification:(NSNotification *)notification { if ([NSThread currentThread] ! = self.notificationThread) { [self.notificationLock lock]; [self.notifications addObject:notification]; [self.notificationLock unlock]; [self.notificationPort sendBeforeDate:[NSDate date] components:nil from:nil reserved:0]; } else {NSLog(@"current thread % @refresh UI", NSThread currentThread); // Refresh the UI... Notification [29036:12827315] Current thread <NSThread: 0x7Be3E000 >{number = 3, name = (null)} 2017-02-27 11:49:04.307 Notification [290360:12827268] Current Thread <NSThread: 0x7Bf3B970 >{number = 1, name = main} Refresh the UICopy the code

Problem 3.

1.observerWhere are objects stored in?

// NSNotificationCenter init method - (id) init {if ((self = [super init])! = nil) { _table = newNCTable(); } return self; }Copy the code

Each NSNotificationCenter has a default _table. “Observer” is referenced (unsafe_unretained in iOS9, weak retained in iOS9).

2.postIn what way is the notification object to be found?

Select * from a table where an observer object is selected first by an object and then by a name. Select * from a table where an observer object is selected first by an object and then by a name.

3. System notificationNameWhat are the?

/ / when the program has been pushed to the background when UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification NS_AVAILABLE_IOS _0 (4); / / when the program from the background will be returned to the front desk UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification NS_AVAILABLE_IOS(4_0); / / when the program after finish loading notice UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification; / / application into active UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification; / / the user home screen button call notice, does not enter into the state of the background UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification; / / memory low notification UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification; / / UIKIT_EXTERN notice when the program will quit NSNotificationName const UIApplicationWillTerminateNotification; / / when the system time change notification UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification; / / when the StatusBar frame direction will change notification UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation // Notify UIKIT_EXTERN NSNotificationName const when the StatusBar box changes orientation UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with old orientation // Notify UIKIT_EXTERN NSNotificationName const when the StatusBar Frame is about to change UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with new frame // Notify UIKIT_EXTERN NSNotificationName const when StatusBar frame changes UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with old frame // notification when background download status changes (available after iOS7.0) UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED; / / protected files currently become unavailable to inform UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS(4_0); / / protected files becomes available to inform current UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable NS_AVAILABLE_IOS(4_0); / / screenshots notification (available) after iOS7.0 UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS(7_0);Copy the code

4. Interview questions

1. Implementation principle of notifications (structure design, how notifications are stored, relationship between name&Observer &SEL, etc.)

The notification center structure can be divided into the following categories

  • NSNotificationNotification model name, Object, userInfo.
  • NSNotificationCenterThe notification center is responsible for sendingNSNotification
  • NSNotificationQueueNotification queues are responsible for triggering calls at certain timesNSNotificationCenterNotification centerpostnotice

Notifications are structures that store data through bidirectional linked lists

// NSNotificationCenter holds typedef struct NCTbl {Observation *wildcard; /* GSIMapTable nameless; /* GSIMapTable nameless; /* Store notification with no name but object */ GSIMapTable named; /* Store notifications with name, regardless of object */... } NCTable; Typedef struct Obs {id observer; /* Observer, the object that receives the notification */ SEL selector; Struct Obs *next; /* Next item in linked list. */ ... } Observation;Copy the code

It is mainly stored in the form of key values. It is important to emphasize that notifications are stored in the name and object dimensions, which are the two different methods passed in when we add notifications.

The relationship between name&Observer &SEL: Name serves as the key, and observer serves as the observer. When triggered, the SEL of the Observer is invoked

2. Are notifications sent synchronously or asynchronously

Send synchronously because message forwarding is invoked. By asynchron, I mean it’s not sent in real time but sent at the right time, without the asynchronous thread being started.

3.NSNotificationCenterAre messages received and sent in the same thread? How do I send messages asynchronously

Yes, the asynchronous thread sends the notification and the response function is also on the asynchronous thread. You can enable the asynchronous thread to send notifications asynchronously.

4. Is NSNotificationQueue sent asynchronously or synchronously? Which thread responds in?

Typedef NS_ENUM(NSUInteger, NSPostingStyle) {NSPostWhenIdle = 1, NSPostASAP = 2, NSPostNow = 3 // Send immediately or after the merge notification is complete};Copy the code
. NSPostWhenIdle NSPostASAP NSPostNow
NSPostingStyle Asynchronous send Asynchronous send The synchronous

NSNotificationCenter sends synchronously, while the asynchronous sending of NSNotificationQueue is introduced here. From the perspective of threads, it is not really asynchronous sending, or it can be called delayed sending. It is triggered by using the timing of Runloop.

The asynchronous thread sends the notification and the response function is sent on the asynchronous thread, and the main thread sends the notification on the main thread.

5. Relationship between NSNotificationQueue and Runloop

NSNotificationQueue relies on Runloop. Because the notification queue calls the notification center to send notifications at some point during a Runloop callback. This can be seen from the enumeration values below

Typedef NS_ENUM(NSUInteger, NSPostingStyle) {NSPostWhenIdle = 1, NSPostASAP = 2, NSPostNow = 3 // Send immediately or after the merge notification is complete};Copy the code

6. How to ensure that the thread receiving the notification is in the main thread

If you want to respond to asynchronous notifications on the main thread, you can do so in one of two ways

  1. The API specifies the queue for the system to receive notifications
  2. NSMachPortThe way through the main threadrunloopaddmachPort, set thisportdelegateAnd through thisPortOther threads can talk to the main thread, right hereportThe code executed in the agent callback must be running in the main thread, so it is called hereNSNotificationCenterJust send a notification

7. Does page destruction crash without removing notifications?

Before iOS9.0, crash occurred because: The notification center references the observer unsafe_unretained. As a result, when the observer is released, the pointer value of the observer is not nil, and the wild pointer occurs.

After iOS9.0, crash will not occur because: the notification center references the observer as weak.

8. What happens if you add the same notification more than once? Multiple removal notifications

Adding the same notification more than once will result in multiple notification callbacks. Removing notifications multiple times does not cause a crash.

9. Can I receive notifications in the following ways? why

// Send notification [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"TestNotification" object:@1]; / / receive notifications [NSNotificationCenter defaultCenter postNotificationName: @ "TestNotification" object: nil];Copy the code

Can’t

The notification center stores the structure of the notification observer

// NSNotificationCenter holds typedef struct NCTbl {Observation *wildcard; /* GSIMapTable nameless; /* GSIMapTable nameless; /* Store notification with no name but object */ GSIMapTable named; /* Store notifications with name, regardless of object */... } NCTable;Copy the code

Named table: key(name) : value-> Key (object) : value(Observation)

So if you pass in name and no Object when you send a notification, you will not find an Observation and you will not be able to perform an observer callback.