As for NSNotification, it has always stayed at the basic level of use. I understand that it is the architectural design of observer mode, but I have not yet understood the internal implementation principle. Until recently, I have brushed some interview questions about NSNotification, so THIS article came into being.

NSNotification principle

There are a lot of articles on how to use NSNotification that I won’t go into here. So let’s go straight into NSNotification and see how it’s implemented.

The data structure

Since Apple doesn’t open up the source code, let’s take a look at the apis and classes that Apple has opened up to us.

NSNotification

Let’s look at the data structure of the NSNotification class, and as you can see from the.h file, it contains the following properties.

@interface NSNotification : NSObject <NSCopying, NSCoding> ... /* Querying a Notification Object */ - (NSString*) name; // Name - (id) object; // The object to carry - (NSDictionary*) userInfo; // Configure information @endCopy the code

NSNotificationCenter

The NSNotificationCenter we use the most is a singleton class that does three things:

  • Add notification
  • Send a notification
  • Remove the notification

The core API is as follows:

// 添加通知观察者
- (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;

// 发出通知
- (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;
Copy the code

We can take a guess at the implementation from the API that Apple has made available to us. Apple should maintain mappings between NSNotificationName, Observer, Selector, and Object. How does that work? The GNUStep source code is not apple’s official source code, but it is of great reference significance. Through the source code, we know that NSNotificationCenter mainly defines two tables, and Observation is also defined to save observer information.

Observation first let’s look at the Observation structure:

typedef struct  Obs {
  id        observer;   /* Object to receive message.   */
  SEL       selector;   /* Method selector.     */
  struct Obs    *next;      /* Next item in linked list.    */
  int       retained;   /* Retain count for structure.  */
  struct NCTbl  *link;      /* Pointer back to chunk table  */
} Observation;
Copy the code
  • Observer and Selector information is saved
  • A linked list structure that points to the next observer that registers the same notification

NCTable contains two internal tables, one of which holds when a NotifcationName is passed in when an observer is added. One is used to save the case when no NotifcationName is passed in when adding an observer. The following two cases are analyzed.

typedef struct NCTbl {
  Observation       *wildcard;  /* Get ALL messages. */
  GSIMapTable       nameless;   /* Get messages for any name. */
  GSIMapTable       named;      /* Getting named messages only. */
} NCTable;
Copy the code

Named Table

Let’s start with the Named Table that holds the NotifcationName passed in when an observer is added. Based on the name of the Table, we can guess that in the Named Table NotifcationName is the key of the Table. However, according to the API provided by Apple, we can pass in an object parameter when registering an Observer to only listen to the notifications given by the specified object, so we need a table to store the corresponding relationship between object and Observer. In this table, object is the Key and Observer is the value.

  • The outer layer is a table with NotificationName NotificationName as the key and value as a table (hereafter referred to as inner table).
  • The inner table has object as its key and its value as a linked list, which is used to store all observers.
  • When object is nil, the system will automatically generate a key according to nil. In other words, the value (linked list) corresponding to this key stores all observers whose NotificationName is not passed in.

Nameless Table

Nameless tables are much simpler than Named tables because there is no NotificationName as the key and object is used as the key. There is one less layer of Table nesting than Named Table.

Wildcard A wildcard is a data structure of a linked list. If neither NotificationName nor Object is passed during observer registration, it is added to the wildcard linked list. Observers registered here receive all system notifications.

Add notification

First look at the source code:

Name: notification name Object: carries an object */ - (void) addObserver: (id) Observer Selector: (SEL)selector name: (NSString*)name object: (id)object { Observation *list; Observation *o; GSIMapTable m; GSIMapNode n; // Preconditions judgment...... O = obsNew(TABLE, selector, observer); // Create an Observation object that holds the observer and SEL. // if name exists if (name) {//-------- NAMED is a macro that means NAMED a 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; } //-------- fetch the corresponding value 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; }} // If name is null, but object is not null 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... Wildcard = wildcard; // wildcard = wildcard; WILDCARD = o; }}Copy the code
  • Instantiating Observation holds the selector of the observer object, which is executed when the notification is received
  • If the Name of the incoming notification exists, use Name as the Key in Named Table to find if there is a value. If there is no value, create a new Table. If there is, fetch the inner Table
    • If there is no object, there will be a default key, which means that all notifications sent anywhere will be listened on
    • The Key generated by Object is used to find the List of observations, and if not, a node is created and used as the head node. If there is a linked list, insert it to the end
  • If the Name of the incoming notification does not exist, but object is not null. An Observation list is found through the key generated by Object, and if not, a node is created and used as the head node. If there is a linked list, insert it to the end
  • If Name and Object are null, the Observation object is stored directly in the WildCard list structure

Send a notification

The _postAndRelease method is called to send the notification. The implementation of this method can be simplified as:

- (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; // Preconditions judgment...... object = [notification object]; / / create an Array to save Observation GSIArrayInitWithZoneAndStaticCapacity (a, _zone, 64, I); // Save all observations from wildcard to Array. These observations have no name or object for (o = wildcard = purgeCollected(wildcard); o ! = ENDOBS; o = o->next) { GSIArrayAddItem(a, (GSIArrayItem)o); } // In the nameless Table, find the Observation that defines the corresponding object, Add to Array if (object) {n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object); if (n ! = 0) { o = purgeCollectedFromMapNode(NAMELESS, n); while (o ! = ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o); o = o->next; }}} / * * in the named table lookup, start with the name as the key, find the corresponding value value, namely the inner layer in the table, the table *, according to the first object to find the corresponding Observation, * if object is not nil, then select nil as the object key to find the corresponding Observation, then add it to Array */ / If (name) {n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name); if (n) { m = (GSIMapTable)n->value.ptr; } else { m = 0; } if (m ! = 0) { n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object); if (n ! = 0) { o = purgeCollectedFromMapNode(m, n); while (o ! GSIArrayAddItem(a, (GSIArrayItem)o); o = o->next; } } if (object ! = nil) { n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil); if (n ! = 0) { o = purgeCollectedFromMapNode(m, n); while (o ! GSIArrayAddItem(a, (GSIArrayItem)o); GSIArrayAddItem(a, (GSIArrayItem)o); o = o->next; }}}}} // Iterate over the Array, calling observer selector count = GSIArrayCount(a); while (count-- > 0) { o = GSIArrayItemAtIndex(a, count).ext; if (o->next ! = 0) { [o->observer performSelector: o->selector withObject: notification]; } // RELEASE(notification); }Copy the code
  • Generates a Notification object based on name, object, and INFO
  • Create an Array to hold the Observation information
  • Save all observations in the Wildcard to Array. These observations have neither name nor object
  • In the Nameless Table, find the Observation that defines the corresponding object and add it to the Array
  • In named table, name is the key and value is the inner table
    • In the inner table, find the corresponding Observations according to object and add them to Array successively
    • If object is not nil, then nil is used as the object key to find the corresponding Observations and add them to the Array in turn
  • Iterating through the Array, calling observer selectors one by one, executing simultaneously in the same thread as you can see from the code
  • Releasing notification Objects

Remove the notification

The code for removing notifications is also very long, but the principle is similar to adding and sending notifications. The operation process for the two tables and wildcard is as follows:

  • If name and object are nil, the same Observation information of the Observer in the Wildcard linked list is removed
  • If name is nil,
    • We will first iterate through all inner tables in named, for each inner table
      • If object is nil, all values in the table are iterated to empty the observer of the same Observation information
      • If object is not nil, fetch the corresponding linked list from the found table with object as key, and then empty the observer of the same Observation information.
    • Then process the Nameless table
      • If object is nil, all values in the table are iterated to empty the observer of the same Observation information
      • If object is not nil, fetch the corresponding linked list from the found table with object as key, and then empty the observer of the same Observation information.
  • If name is not nil, find the corresponding inner table in named table with name as key
    • If object is nil, all values in the table are iterated to empty the observer of the same Observation information
    • If object is not nil, fetch the corresponding linked list from the found table with object as key, and then empty the observer of the same Observation information.

To extend

The basic principle of Notification has been introduced. Of course, there are still many issues not covered, such as NSNotificationQueue, and the following API for adding notifications. If you have time, you can further study it.

- (id) addObserverForName: (NSString *)name 
                   object: (id)object 
                    queue: (NSOperationQueue *)queue 
               usingBlock: (NSNotificationBlock)block
Copy the code

Problem solving

Back to the original intention of understanding the internal implementation principle of NSNotification, let’s list these interview questions and give the answers

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

That is what the previous chapter describes

Are notifications sent synchronously or asynchronously

In the source code for sending notifications, we can conclude that not only is it synchronous, but also that sending and receiving notifications are in the same thread.

Does page destruction crash without removing notifications

Let’s start with a description of the system:

If you used addObserverForName:object:queue:usingBlock: to create your observer, you should call 
this method or removeObserver:name:object: before the system deallocates any object that
addObserverForName:object:queue:usingBlock: specifies.  
If your app targets iOS 9.0 and later or macOS 10.11 and later, and you used addObserver:selector:name:object:,
you do not need to unregister the observer. If you forget or are unable to remove the observer,  the system
cleans up the next time it would have posted to it.
When removing an observer, remove it with the most specific detail possible. For example, if you used a name
and object to register the observer, use removeObserver:name:object: with the name and object.
Copy the code

We can see from this document that

  • Using addObserverForName: object: queue: usingBlock, must be removed manually
  • Using the addObserver: selector: name: object:, ios9 after the system will automatically remove

How to automatically removed after ios9 system using weak pointer modify observe, when observe is released, again send a message to send does not cause collapse nil, and according to the mentioned in the description, system will send a notification when next time, remove the oboserve observer of nil

What happens when you add the same notification more than once? Multiple removal notifications

Since the source code does not repeat filtering, adding the same notification is equivalent to adding it twice, and the callback is triggered twice. There is no problem with multiple removal, because it will be searched in the map and deleted only when it is found.

Can the following methods receive notifications? 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

No, according to the postNotification implementation,

  • A subtable with key TestNotification will be found in the named table
  • Then select the Observation list information with key nil from the table and send notifications in turn

Reference article: github.com/colourful98… Github.com/gnustep/lib…