1. It is not always necessary to remove the observer

Since iOS 9 (see Release Notes), Foundation has adjusted the way NSNotificationCenter references observers (Zeroing Weak Reference) to no longer send notifications to released observers, Thus, the usual practice of removing observers in dealloc can be eliminated.

For iOS 8, UIViewController and its subclasses can dispense with the need to remove the notification (the test works), while other objects need to remove the observer before dealloc.

Thanks to Ace for finding out the test at the first time

Good practice for controllers to add and remove observers

Controller objects listening for notifications are usually handled in the viewDidLoad method of the lifecycle, that is, before viewDidLoad is added, the corresponding judgment can be made as follows when removing the notification:

- (void)dealloc { if (self.isViewLoaded) { [[NSNotificationCenter defaultCenter] removeObserver:self]; }}Copy the code

This isViewLoaded judgment is not necessary for NSNotification listening because removeObserver is called without listening for notifications: The method is still safe, while KVO (key-value observing, is not. Since it is not safe for KVO to remove an observer without listening, if KVO is listening on viewDidLoad, then KVO removal requires a judgment:

- (void)dealloc { if (self.isViewLoaded) { [self removeObserver:someObj forKeyPath:@"someKeyPath"]; }}Copy the code

In addition, there are many times when the controller’s view is not loaded and you need to listen for specific notifications. This is best done in the initWithNibName:bundle constructor, which is called when the code or Interface Builder builds an instance:

- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
    if (self) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(onNotification:)
                                                     name:@"kNotificationName"
                                                   object:nil];
    }
    
    return self;
}
Copy the code

3, systemNSNotificationCenterIs to supportblockMethods the

Notification Center has supported block callbacks since iOS 4, with the following apis:

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

The callback can specify the action queue and return an observer object. Call example:

- (void)observeUsingBlock {
    NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
    observee = [center addObserverForName:@"kNotificationName"
                                   object:nil
                                    queue:[NSOperationQueue mainQueue]
                               usingBlock:^(NSNotification * _Nonnull note) {
                                   NSLog(@"got the note %@", note);
                               }];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:observee];
}

Copy the code

Among them, a few points are worth noting:

  • Method returns aid<NSObject>The listener object is actually an instance of a private class in the system, and since there is no need to expose its specific type and interface, use oneid<NSObject>Object indicates the purpose, from which you can see another application scenario of the protocol.
  • The return value object is used as the originaltarget-actionThe encapsulation implementation is triggered within itactionAfter the call was originally passed inblockParameters.
  • The sum of the returned observersblockBoth are held by the notification center, so the consumer is obligated to call them when necessaryremoveObserver:Method to remove this listener, otherwise the listener andblockAnd the captured variables are not released, resulting in a memory leak. For detailed instructions and solutions, please refer to the SwiftGG Translation Team’s translated articleBlock notifies the central observer whether to log out manually

4. Intercept notifications in advance if necessary

Notification is very convenient for cross-layer and multi-object communication, which leads to difficult management problems and is often criticized. In this case, notification interception is necessary. NSNotificationCenter is the encapsulation of CFNotificationCenter. It uses a class cluster design similar to NSArray and uses singleton mode to return the shared instance defaultCenter. Through direct inheritance of the way to send a notice of interception is not feasible, because the acquisition is always a static singleton object, from the Telegram company’s open source project engineering can see: by drawing on the implementation principle of KVO, the singleton object class modified for a specific subclass, so as to achieve the interception of the notice.

Step 1: Modify the class of the notification center singleton:

@interface GSNoteCenter : Void hack() {id Center = [NSNotificationCenter defaultCenter]; NSNotificationCenter defaultCenter [NSNotificationCenter defaultCenter]; object_setClass(center, GSNoteCenter.class); }Copy the code

The second step is to intercept the sending event of the notification: using inherited polymorphism, intercept the notification before and after sending:

@implementation GSNoteCenter

- (void)postNotificationName:(NSNotificationName)aName
                      object:(id)anObject
                    userInfo:(NSDictionary *)aUserInfo
{
    // do something before post
    [super postNotificationName:aName
                         object:anObject
                       userInfo:aUserInfo];
    // do something after post
}

@end

Copy the code

PS: After interception, it can be found that the number and frequency of notifications sent by the system is really high. From this perspective, the performance problems of sending notifications need not be too concerned.

5, custom does not need to remove the listening block notification center (attached source code)

If you don’t want to move notifications manually, and you want to use blocks for notification listening, the necessary encapsulation is required. For example, the implementation in ReactiveCocoa looks like this:

@implementation NSNotificationCenter (RACSupport) - (RACSignal *)rac_addObserverForName:(NSString *)notificationName object:(id)object { @unsafeify(object); return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) { @strongify(object);  id observer = [self addObserverForName:notificationName object:object queue:nil usingBlock:^(NSNotification *note) { [subscriber sendNext:note]; }]; return [RACDisposable disposableWithBlock:^{ [self removeObserver:observer]; }];  }] setNameWithFormat:@""]; } @endCopy the code

Using notifications as a source and simply subscribing to next listening results elegantly solves block use and notification removal.

This can be done by customizing the relationship between the notification name and the observer without introducing a reactive framework. The basic idea is:

  • An observer for a block is stored in a dictionary-like collection, but the block needs to be released at the same time when the observer is released. Here we consider using collections that support weak references, such asNSMapTable.
  • An observer listens for a notification name, so a notification name corresponds to a collection of all observers that exist within the collection when a notification name is triggered.
  • The notification center uniformly holds all notification names and their associated relationships.

The initial encapsulation of this implementation is put on GitHub and the notification is registered as follows:

- (void)registerBlock:(GSNoticeBlock)block service:(NSString *)service forObserver:(id)observer { GSServiceMap *mapModel  = [self mapForService:service]; [mapModel.map setObject:block forKey:observer]; }Copy the code

Notification triggers are as follows:

- (void)triggerService:(NSString *)service userInfo:(id)userInfo { GSServiceMap *mapModel = [self mapForService:service]; NSString *key = nil; NSEnumerator *enumerator = [mapModel.map keyEnumerator]; while (key = [enumerator nextObject]) { GSNoticeBlock block = [mapModel.map objectForKey:key]; ! block ? : block(userInfo); }}Copy the code

To remove the listener in advance, perform the following operations:

- (void)unregisterService:(NSString *)service forObserver:(id)observer {
    GSServiceMap *mapModel = [self mapForService:service];
    [mapModel.map removeObjectForKey:observer];
}
Copy the code

Thank Mark for saying that the notification center is not safe, so he tried to define a secure notification center.

The source code

GitHub

summary

As the application of observer mode, the notification center can have a more flexible performance through the application of block. For example, Uber used to solve the difficult management solution of the notification center, uber-signals was a view of the response.

As for the further abstraction of the responsive thought of ReactiveCocoa and RxSwift function, the programming thought is transformed from calling a method/function imperatively to triggering the next operation because of a certain notification/signal, which is worth further exploration.

The resources

Unregistering NSNotificationCenter Observers in iOS 9 Telegram source code Microsoft/WinObjc Reimplementate NSNotificationCenter Microsolf/WinObjc is a gold mine