iOS NSNotificationCenter not receive the notification message

 

Author: Gintok
link: https: //www.jianshu.com/p/e368a18ca7c2
Source: Jane books
are copyrighted by the author. Commercial reprint please contact the author authorized, non-commercial reprint please indicate the source.

Use notifications

NSNotificationCenter notification message center is to achieve a mechanism inside iOS broadcast program, may send a notification so as to realize communication between different objects, notification center uses many manner, notification of an object may be sent by a plurality of objects received this is similar to the mechanism KVO, KVO trigger callback function can also be the object of a response, but the agency model is one to one delegate mode, only one delegate object, and the object can only delegate object through a proxy way communication.

Notification mechanism in more central two classes: NSNotification and NSNotificationCenter

NSNotification

NSNotification notification is the basis of the center, the center of the notification will be sent notification encapsulated into objects of that class in turn transmitted between different objects. Class is defined as follows:

 

//通知的名称,可以根据名称区分不同的通知
@property (readonly, copy) NSNotificationName name;
//通知的对象,常使用nil,如果设置了值的话注册的通知监听器的object需要与通知的object匹配,否则接收不到通知
@property (nullable, readonly, retain) id object;
//字典类型的用户信息,用户可将需要传递的数据放入该字典中
@property (nullable, readonly, copy) NSDictionary *userInfo;

//下面三个是NSNotification的构造函数,一般不需要手动构造
- (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;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

NSNotificationCenter

Notification Center embodiment of a single mode, the entire system is only one notification center. You can [NSNotificationCenter defaultCenter]be obtained objects.
Notification Center a few core method is as follows:

 

/*
注册通知监听器,这是唯一的注册通知的方法
observer为监听器
aSelector为接到收通知后的处理函数
aName为监听的通知的名称
object为接收通知的对象,需要与postNotification的object匹配,否则接收不到通知
*/
- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

/*
发送通知,需要手动构造一个NSNotification对象
*/
- (void)postNotification:(NSNotification *)notification;

/*
发送通知
aName为注册的通知名称
anObject为接受通知的对象,通知不传参时可使用该方法
*/
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;

/*
发送通知
aName为注册的通知名称
anObject为接受通知的对象
aUserInfo为字典类型的数据,可以传递相关数据
*/
- (void)postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

/*
删除通知的监听器
*/
- (void)removeObserver:(id)observer;

/*
删除通知的监听器
aName监听的通知的名称
anObject监听的通知的发送对象
*/
- (void)removeObserver:(id)observer name:(nullable NSNotificationName)aName object:(nullable id)anObject;

/*
以block的方式注册通知监听器
*/
- (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));

Let's look at an example using actual notice, there are two pages, and NextViewController ViewController, there is a button and a label in ViewController, click the button to jump to NextViewController view, NextViewController contains an input box and a button, the user Once completed click the button to exit the view jumps back to the ViewController and display user data to fill in the ViewController tab. code show as below

 

//ViewController部分代码

- (void)viewDidLoad
{
    //注册通知的监听器,通知名称为inputTextValueChangedNotification,处理函数为inputTextValueChangedNotificationHandler:
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(inputTextValueChangedNotificationHandler:) name:@"inputTextValueChangedNotification" object:nil];

}

//按钮点击事件处理器
- (void)buttonClicked
{
    //按钮点击后创建NextViewController并展示
    NextViewController *nvc = [[NextViewController alloc] init];
    [self presentViewController:nvc animated:YES completion:nil];
}

//通知监听器处理函数
- (void)inputTextValueChangedNotificationHandler:(NSNotification*)notification
{
    //从userInfo字典中获取数据展示到标签中
    self.label.text = notification.userInfo[@"inputText"];
}

- (void)dealloc
{
    //当ViewController销毁前删除通知监听器
    [[NSNotificationCenter defaultCenter] removeObserver:self name:@"inputTextValueChangedNotification" object:nil];
}

//NextViewController部分代码
//用户完成输入后点击按钮的事件处理器
- (void)completeButtonClickedHandler
{
    //发送通知,并构造一个userInfo的字典数据类型,将用户输入文本保存
    [[NSNotificationCenter defaultCenter] postNotificationName:@"inputTextValueChangedNotification" object:nil userInfo:@{@"inputText": self.textField.text}];
    //退出视图
    [self dismissViewControllerAnimated:YES completion:nil];
}

The procedure is relatively simple, said here about the steps to use the notification:

  1. Where notification of a need to monitor the registration notification listeners
  2. Implement the notification listener callback function
  3. Delete notification listener in front of the listener object is destroyed
  4. If notification needs to be sent, using the method of transmitting notifications NSNotificationCenter the postNotification

Apple will not start sending notifications to have been destroyed after iOS9 listener, the listener object is destroyed when the notification is sent and will not cause wild pointer errors, which is more secure than KVO, KVO will trigger a callback when the listener object is destroyed function may cause dangling pointer errors, so it can not use the notification manually remove the listener, but if the system needs to adapt before iOS9 still need to develop the habit of manually remove the listener.

Notification multithreading

In Apple's official documentation, for use in multi-threaded notice the following explanation:

Regular notification centers deliver notifications on the thread in which the notification was posted. Distributed notification centers deliver notifications on the main thread. At times, you may require notifications to be delivered on a particular thread that is determined by you instead of the notification center. 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.

Simple to understand

In a multithreaded application, Notification post in which thread, which will be forwarded thread, and that thread is not necessarily the registration of the observer.

That Notification of sending and receiving treatment are in the same thread. You can use the following code verification:

 

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"当前线程为%@", [NSThread currentThread]);
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(handleNotification:) name:@"Test_Notification" object:nil];
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [[NSNotificationCenter defaultCenter] postNotificationName:@"Test_Notification" object:nil userInfo:nil];
        NSLog(@"发送通知的线程为%@", [NSThread currentThread]);
    });
}

- (void)handleNotification: (NSNotification *)notification {
    NSLog(@"转发通知的线程%@", [NSThread currentThread]);
}

The output is:

 

当前线程为<NSThread: 0x608000073780>{number = 1, name = main}
接收和处理通知的线程<NSThread: 0x608000261180>{number = 3, name = (null)}
发送通知的线程为<NSThread: 0x608000261180>{number = 3, name = (null)}

We can see, although we registered a notice of observers in the main thread, but post in the global queue Notification, not in the main thread. So, this time we need to pay attention to, if we want to deal with UI-related operations in the callback, the need to ensure the implementation of a callback in the main thread.
So how can we post a thread Notification of forwarding the same thread thread is not it? Apple document to a solution:

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.

Here we talked about "redirect" that we capture these notification distributed in the default thread Notification is located, and then redirects it to the specified thread.

Method 1: Use block

From then iOS4 Apple provides with a block of NSNotification. Used as follows:

 

-(id)addObserverForName:(NSString*)name object:(id)obj queue:(NSOperationQueue*)queue usingBlock:^(NSNotification * _Nonnull note);

When we use the block method, as long as the set [NSOperationQueuemainQueue], the refresh operation can be achieved in the main UI thread.
Our code therefore become some succinct:

 

[[NSNotificationCenter defaultCenter] addObserverForName:@"Test_Notification" object:nil queue [NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"接收和处理通知的线程%@", [NSThread currentThread]);
    }];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    [[NSNotificationCenter defaultCenter] postNotificationName:@"Test_Notification" object:nil userInfo:nil];
    NSLog(@"发送通知的线程为%@", [NSThread currentThread]);
});

Second way: custom notification queue

Customize a notification queue (note, not NSNotificationQueue object, but an array), so that the Notification queue to maintain that we need to redirect. We are still as usual to register a notice of an observer, when Notification came, take a look at this post Notification thread is not what we expect of thread, if not, then the Notification stored in our cohort, and sends a signal (signal) to the desired thread to tell the thread to deal with a Notification. Specified thread, after receiving the Notification signal removed from the queue and processed.
This way Apple's official code samples, as follows:

 

@interface ViewController () <NSMachPortDelegate>
@property (nonatomic) NSMutableArray    *notifications;         // 通知队列
@property (nonatomic) NSThread          *notificationThread;    // 期望线程
@property (nonatomic) NSLock            *notificationLock;      // 用于对通知队列加锁的锁对象,避免线程冲突
@property (nonatomic) NSMachPort        *notificationPort;      // 用于向期望线程发送信号的通信端口
    
@end
    
@implementation ViewController
    
- (void)viewDidLoad {
    [super viewDidLoad];
    
    NSLog(@"current thread = %@", [NSThread currentThread]);
    
    // 初始化
    self.notifications = [[NSMutableArray alloc] init];
    self.notificationLock = [[NSLock alloc] init];
    
    self.notificationThread = [NSThread currentThread];
    self.notificationPort = [[NSMachPort alloc] init];
    self.notificationPort.delegate = self;
    
    // 往当前线程的run loop添加端口源
    // 当Mach消息到达而接收线程的run loop没有运行时,则内核会保存这条消息,直到下一次进入run loop
    [[NSRunLoop currentRunLoop] addPort:self.notificationPort
                                forMode:(__bridge NSString *)kCFRunLoopCommonModes];
    
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(processNotification:) name:@"TestNotification" object:nil];
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        
        [[NSNotificationCenter defaultCenter] postNotificationName:TEST_NOTIFICATION object:nil userInfo:nil];
    
    });
}
    
- (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] != _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;
        NSLog(@"current thread = %@", [NSThread currentThread]);
        NSLog(@"process notification");
    }
}
    
@end

You can see, we dispatch queue thrown Notification global, and who wish to receive in the main thread. However flawed this way, just as Apple's official website said:

This implementation is limited in several aspects. 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.

Better implementation of our cases to the child of a NSNotificationCenter, then the associated processing custom.

reference

  1. iOS multi-threaded use NSNotification
  2. Notification and multithreading
  3. Detailed notification method using NSNotificationCenter
Published 79 original articles · won praise 9 · views 70000 +

Guess you like

Origin blog.csdn.net/LeeCSDN77/article/details/104359525