NSNotification&NSNotificationCenter(实现原理,多线程,内存管理角度)

http://www.cocoachina.com/ios/20150316/11335.html (通知愈多线程的关系)
http://www.jianshu.com/p/2d3c8e084205 runloop相关的
http://www.jianshu.com/p/a307587ac62c 通知中心
http://blog.csdn.net/wzzvictory/article/details/8489516
http://www.cnblogs.com/heyonggang/p/3681689.html 概念解析
http://www.cnblogs.com/wangyang1213/p/5234228.html
简单的展示通知 在按钮按下时传值,

- (IBAction)buttonClick:(id)sender{
   //添加字典,将label的值通过key值设置传递
   NSDictionary *dict=[[NSDictionary alloc]initWithObjectsAndKeys:self.textFieldOne.text,@"textOne",self.textFieldTwo.text,@"textTwo",nil];
   //创建通知
   NSNotification*notification=[NSNotificationnotificationWithName:@"tongzhi"object:niluserInfo:dict];
   //通过通知中心发送通知
  [[NSNotificationCenterdefaultCenter] postNotification:notification];
  [self.navigationControllerpopViewControllerAnimated:YES];
 
}

在发送通知后,在所要接收的控制器中注册通知监听者,将通知发送的信息接收

- (void)viewDidLoad{
  [superviewDidLoad];
   //注册通知
  [[NSNotificationCenterdefaultCenter]addObserver:self selector:@selector(tongzhi:) name:@"tongzhi" object:nil];
 
}
- (void)tongzhi:(NSNotification*)text{
  NSLog(@"%@",text.userInfo[@"textOne"]);
     NSLog(@"-----接收到通知------");
 
}

移除通知:removeObserver:和removeObserver:name:object:
其中,removeObserver:是删除通知中心保存的调度表一个观察者的所有入口,而removeObserver:name:object:是删除匹配了通知中心保存的调度表中观察者的一个入口。
这个比较简单,直接调用该方法就行。例如:

[[NSNotificationCenter defaultCenter] removeObserver:observername:nil object:self];

注意参数notificationObserver为要删除的观察者,一定不能置为nil。

从使用的角度上考虑 通知中心属于十分简单的
我们从三个角度来理解 通知中心, 原理,多线程角度,内存管理角度等三方面深入理解他.


1.实现原理,

我们可以看到 通知中心里面是两个类 NSNotificationCenter NSNotification 而 NSNotificationCenter 是用来统一管理整体的通知消息

使用时 还是把 NSNotificationCenter 以单利的形式创建 通过 center post 通知
通过center addObserver:(id)observer selector:(SEL)Selector name:(NSString *)Name object:(id)Object 建立监听
,通知中心的优点在于 一处发送,全局接受.那么也就意味着 转发和监听同步的
addObserver:(id)observer selector:(SEL)Selector name:(NSString *)Name object:(id)Object 里面做了
将 name Selector Object 赋值给一个model 并且每注册一个监听,就会给一个model 赋值 并且 model添加到一个可变数组中

当我们post 通知是 我们就去便利这个可变数组,并且取得数组中的model 这时候我们就可以拿到 通知的name,去执行通知的对象 ,以及执行的通知的方法,
[对象 performSelector:selector withObject:notification];
那么就好啦 这时候相应的对象就去只想这个selector啦并且将参数 notification传递过去啦
还有就是 remove 就是从数组中移除就好
一个通正中心简单的原理就是这样,当然还有更加会有多线程一会解释


2.多线程的角度考虑
读官方文档时多次提到 通知是在那个线程发送的就在那个线程接受.
也就是会涉及到一个问题就是我实在次级线程post 然后想在其他线程接受那该怎么办
官网文档给的方法就是重定向
代码如下:

@interface ViewController () @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

上面的方法是将子线程通知到主线程中去执行任务
当然如果想在某个子线称重执行那么稍微修改一下就搞定啦

 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        self.notificationThread = [NSThread currentThread];
 NSLog(@"指定current thread = %@", self.notificationThread);
        self.notificationPort = [[NSMachPort alloc] init];
        self.notificationPort.delegate = self;
        
        [[NSRunLoop currentRunLoop] addPort:self.notificationPort
                                    forMode:(__bridge NSString *)kCFRunLoopCommonModes];
        [[NSRunLoop currentRunLoop] run];
    });
创建子线程并且为其开启runloop,这样就搞定啦可以再指定的子线程中去执行通知

通告队列

NSNotificationQueue
对象(或者简单称为通告队列)的作用是充当通告中心(NSNotificationCenter
的实例)的缓冲区。通告队列通常以先进先出(FIFO)的顺序维护通告。当一个通告上升到队列的前面时,队列就将它发送给通告中心,通告中心随后将它派发给所有注册为观察者的对象。

每个线程都有一个缺省的通告队列,与任务的缺省通告中心相关联。图5-9展示了这种关联。您可以创建自己的通告队列,使得每个线程和通告中心有多个队列。

**图5-9 **通告队列和通告中心

聚结的通告

NSNotificationQueue
类为Foundation Kit的通告机制增加了两个重要的特性:即通告的聚结和异步发送。聚结是把和刚进入队列的通告相类似的其它通告从队列中移除的过程。如果一个新的通告和已经在队列中的通告相类似,则新的通告不进入队列,而所有类似的通告(除了队列中的第一个通告以外)都被移除。然而,您不应该依赖于这个特殊的聚结行为。

您可以为enqueueNotification:postingStyle:coalesceMask:forModes:
方法的第三个参数指定如下的一或多个常量,指示简化的条件:

NSNotificationNoCoalescing

NSNotificationCoalescingOnName

NSNotificationCoalescingOnSender

您可以对NSNotificationCoalescingOnName
和NSNotificationCoalescingOnSender
常量进行位或操作,指示Cocoa同时使用通告名称和通告对象进行聚结。那样的话,和刚刚进入队列的通告具有相同名称和发送者对象的所有通告都会被聚结。
异步发送通告

通过NSNotificationCenter
类的postNotification:
方法及其变体,您可以将通告立即发送给通告中心。但是,这个方法的调用是同步的:即在通告发送对象可以继续执行其所在线程的工作之前,必须等待通告中心将通告派发给所有的观察者并将控制权返回。但是,您也可以通过NSNotificationQueue
的enqueueNotification:postingStyle:
和enqueueNotification:postingStyle:coalesceMask:forModes:
方法将通告放入队列,实现异步发送,在把通告放入队列之后,这些方法会立即将控制权返回给调用对象。

Cocoa根据排队方法中指定的发送风格和运行循环模式来清空通告队列和发送通告。模式参数指定在什么运行循环模式下清空队列。举例来说,如果您指定NSModalPanelRunLoopMode
模式,则通告只有当运行循环处于该模式下才会被发送。如果当前运行循环不在该模式下,通告就需要等待,直到下次运行循环进入该模式。

向通告队列发送通告可以有三种风格:NSPostASAP
、NSPostWhenIdle
、和NSPostNow
,这些风格将在接下来的部分中进行说明。

尽快发送

以NSPostASAP
风格进入队列的通告会在运行循环的当前迭代完成时被发送给通告中心,如果当前运行循环模式和请求的模式相匹配的话(如果请求的模式和当前模式不同,则通告在进入请求的模式时被发出)。由于运行循环在每个迭代过程中可能进行多个调用分支(callout),所以在当前调用分支退出及控制权返回运行循环时,通告可能被分发,也可能不被分发。其它的调用分支可能先发生,比如定时器或由其它源触发了事件,或者其它异步的通告被分发了。

您通常可以将NSPostASAP
风格用于开销昂贵的资源,比如显示服务器。如果在运行循环的一个调用分支过程中有很多客户代码在窗口缓冲区中进行描画,在每次描画之后将缓冲区的内容刷新到显示服务器的开销是很昂贵的。在这种情况下,每个draw...
方法都会将诸如“FlushTheServer” 这样的通告排入队列,并指定按名称和对象进行聚结,以及使用NSPostASAP
风格。结果,在运行循环的最后,那些通告中只有一个被派发,而窗口缓冲区也只被刷新一次。

空闲时发送

以NSPostWhenIdle
风格进入队列的通告只在运行循环处于等待状态时才被发出。在这种状态下,运行循环的输入通道中没有任何事件,包括定时器和异步事件。以NSPostWhenIdle
风格进入队列的一个典型的例子是当用户键入文本、而程序的其它地方需要显示文本字节长度的时候。在用户输入每一个字符后都对文本输入框的尺寸进行更新的开销是很大的(而且不是特别有用),特别是当用户快速输入的时候。在这种情况下,Cocoa会在每个字符键入之后,将诸如“ChangeTheDisplayedSize”这样的通告进行排队,同时把聚结开关打开,并使用NSPostWhenIdle
风格。当用户停止输入的时候,队列中只有一个“ChangeTheDisplayedSize”通告(由于聚结的原因)会在运行循环进入等待状态时被发出,显示部分也因此被刷新。请注意,运行循环即将退出(当所有的输入通道都过时的时候,会发生这种情况)时并不处于等待状态,因此也不会发出通告。

立即发送
以NSPostNow
风格进入队列的通告会在聚结之后,立即发送到通告中心。您可以在不需要异步调用行为的时候 使用NSPostNow
风格(或者通过NSNotificationCenter的postNotification:
方法来发送)。在很多编程环境下,我们不仅允许同步的行为,而且希望使用这种行为:即您希望通告中心在通告派发之后返回,以便确定观察者对象收到通告并进行了处理。当然,当您希望通过聚结移除队列中类似的通告时,应该用enqueueNotification
...方法,且使用NSPostNow
风格,而不是使用postNotification:
方法。

发送通知时,这么写 创建notification 然后加入队列 并且选择发送模式
 NSNotification *notifacation = [[NSNotification alloc]initWithName:@"qqqq" object:nil userInfo:nil];
    [[NSNotificationQueue defaultQueue] enqueueNotification:notifacation postingStyle:3];

合并通知
一些情况下,某一事件一旦发生,开发者可能只想要至少发送一次通知,即使该事件可能频繁多次发生。此时即可采用合并通知,合并是把和刚进入队列的通知相类似的其它通知从队列中移除的过程。如果一个新的通知和已经在队列中的通知相类似,则新的通知不进入队列,而所有类似的通知(除了队列中的第一个通知以外)都被移除。**但是,为安全起见,开发者不应该完全依赖于这个特殊的合并行为。
我想说说 合并并没有用过

3.从内存管理的角度考虑
使用通知中心时,有事会报错:thread1:exc_bad)access(code=exc_1386_gpflt)
这个错误的意思就是你想一个已经释放的对象发送了消息
那么我们回顾一下 通话中中心的使用啊
当我们 注册一个通知中心 并且对其进行监听时 我们会 把自身的类 也就是self 还有通知的名字 已经处理通知结果的的方法,放入一个model中并将model添加到一个可变数组中.当我们去post通知时,从数组中遍历 model 然后取出model中的值 进行
[对象 performSelector:selector withObject:notification];
然而 如果监听通知的类这是已经被释放 那么 这时再去post 那么就会想一个已经释放了的对象进行发送消息那么 自然而然的就会报错.那么解决这种错误的方法就是
移除通知.
(1)那么为什么移除通知就不会再崩溃了呢
首先我们要理解remove的原理,前面我们知道 监听时会给model赋值 然后将model添加可变数组中,然后post时 便利数组取出model.根据model 去[对象 performSelector:selector withObject:notification];发送消息.
那么如果我们在可变数组中直接移除了某个已经释放对象的model,那么post时 就不会遍历到相应的model,也不会[对象 performSelector:selector withObject:notification];发送消息. 那么自然就不会发送消息崩溃
(2)如果 我们在vc1 中post 通知 在vc2中监听通知
我们做这样一个通知 从vc1 跳转到vc2 这是vc2 进行了注册和监听,这是我们pop返回vc1 这是vc2 已经释放,这时 我们在vc1中点击post 发送通知,你会发现不科学,因为没有抛出异常.难道是理论出了错误. 这是我们队 center创建一个分类,然后重新remove方法,(这里其实已经覆盖了原方法),那么在remove方法中进行打印,再重复刚才的步骤,发现分类执行了remove并且打印了 同时也抛出了异常
这说明,系统自动帮我们执行了 remove

好了 就到这了吧 各位道友有啥建议或者想法 欢迎评论

你可能感兴趣的:(NSNotification&NSNotificationCenter(实现原理,多线程,内存管理角度))