What? You think you got it all figured out? Sorry, you know just the basics.

Why use NSNotification

The standard way to pass information between objects is message passing — one object invokes The method of another object. However, message passing requires that the object sending the message know who the receiver is and what messages it responds to. At times, This tight coupling of two objects is undesirable — most glaring because it would join together two otherwise independent Spaces subsystems. For these cases, a broadcast model is introduced: An object posts a notification, which is dispatched to the appropriate observers through an NSNotificationCenter object, or simply notification center.

ObjectA needs to call one of objectB’s methods. In general, objectB needs to be accessible in objectA, but this is an unwelcome way to create coupling. Therefore, a new mechanism called NSNotificationCenter was introduced. NSNotificationCenter manages registered objects such as objectB, while objectA does not need to know objectB. Simply find the corresponding object in the NSNotificationCenter using the tag (such as NotificationName) and invoke the method on that object. This is certainly a way of loose-coupling and allowing many-to-many.

The basic differences between notifications and delegates:

  • Notifications are many-to-many, and the delegate can only be 1-to-1.
  • Notifications are loosely coupled, and the notifier doesn’t need to know anything about the notified party, whereas the delegate doesn’t.
  • Notifications are slightly less efficient than delegates.

Two, the basic use of notice

1. Basic concepts in notifications

NSNotification

// nsnotification. h
@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 NS_AVAILABLE(10_6, 4_0) NS_DESIGNATED_INITIALIZER;
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder 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 /*NS_UNAVAILABLE*/;    /* do not invoke; not a valid initializer for this class */
@end

/ / understand
NSNotificationAs a medium for messaging, it contains three public member variables that pass throughNSNotificationNameType name to find the corresponding observer, and parameters can be passed in object and userInfo. You can use several of the above initialization methods to initialize.Copy the code

NSNotificationCenter

// There is only one notification center globally
@property (class.readonly.strong) NSNotificationCenter *defaultCenter;
// Add an observer to the notification center
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// Send a notification to the notification center
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;
// Remove the observer from the notification center
- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;
// After iOS4, we'll add an observer to the notification center in the form of a block instead of selector
- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void(^) (NSNotification *note))block NS_AVAILABLE(10_6, 4_0);Copy the code

NSDistributedNotificationCenter

// This class is not found in the iOS framework. The notification center is used for interprocess notification in Mac OS. Not much introduction.Copy the code

2. Basic use of notifications

code

/ / code
#import "AppDelegate.h"
@interface A : NSObject
- (void)test;
@end
@implementation A
static int count = 0;
- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)test {
    // Observe method A:selector method
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(xxx) name:@ "111" object:nil];
    // Observe mode B:block mode (the queue parameter determines which NSOperationQueue you want to execute the block in)
    [[NSNotificationCenter defaultCenter] addObserverForName:@ "111" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"block %d", ++count);
    }];
}
- (void)xxx {
    NSLog(@"selector %d", ++count);
}
@end
@interface AppDelegate(a)
@property (nonatomic.strong) A *a;
@end
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    // Send mode A: Manually define NSNotification
    NSNotification *noti = [NSNotification notificationWithName:@ "111" object:nil];
    [[NSNotificationCenter defaultCenter] postNotification:noti];
    // Send mode B: NSNotification is automatically defined
    [[NSNotificationCenter defaultCenter] postNotificationName:@ "111" object:nil userInfo:nil];
    return YES;
}
@end

/ / output
2017.26 - 19:00:27.461 notification[14092:12661907] selector 1
2017.26 - 19:00:27.462 notification[14092:12661907] block 2
2017.26 - 19:00:27.462 notification[14092:12661907] selector 3
2017.26 - 19:00:27.462 notification[14092:12661907] block 4Copy the code

Understanding that there are two observers, two senders, and four logs shows that notifications support this many-to-many messaging.

3. Difficult points

Synchronous or asynchronous Both synchronous and asynchronous are relative to the thread on which notifications are sent. This can be easily detected by the following code:

/ / replace the didFinishLaunchingWithOptions function
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    // Send mode A: Manually define NSNotification
    NSNotification *noti = [NSNotification notificationWithName:@ "111" object:nil];
    [[NSNotificationCenter defaultCenter] postNotification:noti];
    // Send mode B: NSNotification is automatically defined
    [[NSNotificationCenter defaultCenter] postNotificationName:@ "111" object:nil userInfo:nil];
    NSLog(@" Test synchronous or asynchronous");
    return YES;
}Copy the code

The output is always:

2017- 02-26 1913:: 04.739 notification[15240:12674807] selector1, 2017- 02-26 1913:: 04.743 notification[15240:12674807] block2, 2017- 02-26 1913:: 04.743 notification[15240:12674807] selector3, 2017- 02-26 1913:: 04.744 notification[15240:12674807] block4, 2017- 02-26 1913:: 04.744 notification[15240:12674807]Test synchronous versus asynchronousCopy the code

As you can see, postNotification: is always stuck on the current thread until the observer execution (if no special processing is done, the selector will be executed on the thread where postNotification: is located) ends. So it’s synchronous.

Forget about remove and I’m not going to test this, because I don’t have iOS8 or anything before that. Write the conclusion directly:

  • If on or before iOS8 version in the system, for an object addObserver: selector: name: object: (assuming that the name is @ “111”), but not in dealloc before or on the remove operation. So, when sending a notification (name @ “111”), it will crash because of bad_access (wild pointer).
  • In iOS9 or later, do the same.

In iOS8 and previously, NSNotificationCenter holds an unsafe_unretained pointer for the observer (perhaps for compatibility with older versions), so that the observer can be retrieved without removeOberser and post. A message is sent to a region that is reclaimed, so a wild pointer crash occurs. But after iOS9, unsafe_unretained was changed to weak pointer. Even though dealloc is not removeOberser, post operation will send message to nil, so there is no problem.

Queues and asynchronous notifications

1. The principle of asynchronous notification

Create an NSNotificationQueue (first in-first Out), place the defined NSNotification into it, and assign it one of three states:

typedef NS_ENUM(NSUInteger.NSPostingStyle) {
    NSPostWhenIdle = 1.// Post when runloop is idle
    NSPostASAP = 2.// Post immediately after the current runloop completes
    NSPostNow = 3    // Post immediately, synchronize (why this type is needed, see 3.3)
};Copy the code

So, you put the NSNotification in the queue, and the NSNotificationQueue posts it to the NSNotificationCenter at the appropriate time, depending on its type. This completes the asynchronous requirement.

2. Use of asynchronous notifications

/ / replace the didFinishLaunchingWithOptions function
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    NSNotification *noti = [NSNotification notificationWithName:@ "111" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP];
    //[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle];
    //[[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow];
    NSLog(@" Test synchronous or asynchronous");
    return YES;
}

/ / output
2017.26 - 19:56:32.805 notification[19406:12719309] tests synchronous or asynchronous2017.26 - 19:56:32.816 notification[19406:12719309] selector 1
2017.26 - 19:56:32.816 notification[19406:12719309] block 2Copy the code

As you can see from the output above, this does fulfill the requirement for asynchronous notification. Of course, if type is changed to NSPostNow, it is executed synchronously and has the same effect as not using NSNotificationQueue.

3. The synthesis of Notification Queues

In addition to the capability of asynchronous notification, an NSNotificationQueue can combine the notifications of the current queue according to the type of NSNotificationCoalescing.

typedef NS_OPTIONS(NSUInteger.NSNotificationCoalescing) {
    NSNotificationNoCoalescing= 0, // no compositionNSNotificationCoalescingOnName= 1, // according toNSNotificationThe name field is synthesizedNSNotificationCoalescingOnSender= 2 // according toNSNotification};Copy the code

Comparisons can be made in the following two ways:

/ / way 1:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    NSNotification *noti = [NSNotification notificationWithName:@ "111" 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];
    NSLog(@" Test synchronous or asynchronous");
    return YES;
}
/ / way 2:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    NSNotification *noti = [NSNotification notificationWithName:@ "111" object:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostWhenIdle coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:noti postingStyle:NSPostNow coalesceMask:NSNotificationCoalescingOnName forModes:nil];
    NSLog(@" Test synchronous or asynchronous");
    return YES;
}
/ / output
/ / way
2017.26 - 20:09:31.834 notification[20612:12733161] selector 1
2017.26 - 20:09:31.835 notification[20612:12733161] block 2
2017.26 - 20:09:31.835 notification[20612:12733161] tests synchronous or asynchronous2017.26 - 20:09:31.851 notification[20612:12733161] selector 3
2017.26 - 20:09:31.851 notification[20612:12733161] block 4
2017.26 - 20:09:31.854 notification[20612:12733161] selector 5
2017.26 - 20:09:31.855 notification[20612:12733161] block 6
2 / / way
2017.26 - 20:11:31.186 notification[20834:12736113] selector 1
2017.26 - 20:11:31.186 notification[20834:12736113] block 2
2017.26 - 20:11:31.186 notification[20834:12736113] tests synchronous or asynchronousCopy the code

The requirement of composition is probably fulfilled, but there is still a question here, for example, three notifications are combined into one, so what is the actual NSNotification sent? Leave it to the readers to find out for themselves.

4. Specify Thread to handle notification

demand

Suppose will didFinishLaunchingWithOptions instead:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    A *a = [A new];
    [a test];
    self.a = a;
    dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
    dispatch_async(queue, ^{
        NSLog(@"current thread %@", [NSThread currentThread]);
        [[NSNotificationCenter defaultCenter] postNotificationName:@"111" object:nil];
    });
    return YES;
}Copy the code

The observer selector of DISPATCH_QUEUE_PRIORITY_BACKGROUND will be executed in DISPATCH_QUEUE_PRIORITY_BACKGROUND. Here, we need to make sure that the selector is always executing on the mainThread. So, there are two ways to specify the thread of execution for the observer callback method.

Solution 1:NSMachPort

/ / 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 ([NSThreadcurrentThread] ! =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...}}@end

/ / output
2017.27 - 11:49:04.296 notification[29036:12827315] current thread <NSThread: 0x7be3e000>{number = 3, name = (null)}
2017.27 - 11:49:04.307 notification[29036:12827268] current thread <NSThread: 0x7bf3b970>{number = 1, name = main} Refresh the UICopy the code

As you can see from the output, although the post is not on the main thread, the UI refresh is on the main thread, fulfilling the requirement. This is one of the ways that the official documentation provides, but I won’t explain it in detail, and it’s easy to understand. If you don’t know, read the document.

Solution 2: Block addObserver

The document provides a difficult and lengthy way, here we can use the following way instead.

 / / code
@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 the UI...
    }];
}
@end

/ / output
current thread <NSThread: 0x7bf29110>{number = 3, name = (null)}
2017.27 - 11:53:46.531 notification[29510:12833116] current thread <NSThread: 0x7be1d6f0>{number = 1, name = main} Refresh the UICopy the code

Perfect! Convenient!

Five, notice implementation analysis

Here I refer to the pseudo source code. Source code a lot, not an explanation, I focus on only two points:

  • Where are observer objects stored?
  • In what way is the notification object found during POST?

Where observer objects are stored

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

This makes it easy to see that each NSNotificationCenter has a default _table. “Observer” is referenced (unsafe_unretained in iOS9, weak retained in iOS9).

According to how to find the object receiving notification when post

// Part of the postNotification code
/* * Find the observers that specified OBJECT, but didn't specify NAME. */
if (object) {
  n = GSIMapNodeForSimpleKey(NAMELESS, (GSIMapKey)object);
  if(n ! =0)  {
    o = purgeCollectedFromMapNode(NAMELESS, n);
    while(o ! = ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o);o = o->next; }}}/* * Find the observers of NAME, except those observers with a non-nil OBJECT * that doesn't match the notification's OBJECT). */
if (name) {
  n = GSIMapNodeForKey(NAMED, (GSIMapKey)((id)name));
  if (n) {
    m = (GSIMapTable)n->value.ptr;
  } else {
     m = 0;
  }
  if(m ! =0) {
  /* * First, observers with a matching object. */
    n = GSIMapNodeForSimpleKey(m, (GSIMapKey)object);
    if(n ! =0) {
      o = purgeCollectedFromMapNode(m, n);
      while(o ! = ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o);o = o->next; }}if(object ! =nil) {
    /* * Now observers with a nil object. */
        n = GSIMapNodeForSimpleKey(m, (GSIMapKey)nil);
        if(n ! =0) {
          o = purgeCollectedFromMapNode(m, n);
          while(o ! = ENDOBS) { GSIArrayAddItem(a, (GSIArrayItem)o);o = o->next;
        }
      }
    }
  }
}Copy the code

When you look for an Observer object in a table, object is the first object and name is the next.

Six, gleaning

1. Notification names for those systems

// When the application is pushed to the background
UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification       NS_AVAILABLE_IOS(4_0);
// When the program is about to return to the foreground from the background
UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification      NS_AVAILABLE_IOS(4_0);
// Notify when the program has finished loading
UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;
// When the application becomes active
UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;
// The user presses the button on the home screen to invoke the notification and does not enter the background state
UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;
// Notify when memory is low
UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;
// Notify when the program is about to exit
UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;
// Notify when system time changes
UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;
// Notify when the StatusBar box orientation is about to change
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation
// Notify when the StatusBar box direction changes
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED;  // userInfo contains NSNumber with old orientation
// Notify when the StatusBar Frame is about to change
UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED;       // userInfo contains NSValue with new frame
// Notify when the StatusBar Frame changes
UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED;        // userInfo contains NSValue with old frame
// notifications when background download status changes (available after iOS7.0)
UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS(7_0) __TVOS_PROHIBITED;
// Notification when a protected file is currently unavailable
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable    NS_AVAILABLE_IOS(4_0);
// Notify when a protected file is currently available
UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable       NS_AVAILABLE_IOS(4_0);
// screenshot notification (available after iOS7.0)
UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS(7_0);Copy the code

2. Efficiency of notification

Just use it and don’t worry about it.

Seven, literature

1, developer.apple.com/library/con… 2, github.com/gnustep/bas…