具体详情可以参考下面大神的链接,当我们在不

作者: 编程  发布:2019-09-13

参考:南峰子的技术博客:NSNotificationCenter天口三水羊:NSNotification,看完你就都懂了

notification即通知,当我们在不同类之间通信时就要用到通知方法。
使用notification,我们能够把消息发送给多个监听该消息的对象,而不需要知道监听该消息对象的任何信息。消息的发送者将消息发送给通知中心,接受消息者也只需要向通知中心注册自己感兴趣的消息即可。这样就降低了消息的发送者和接收者之间的耦合。

一个NSNotificationCenter对象(通知中心)提供了在程序中广播消息测机制,它实质上就是一个通知分发表。这个分发表负责维护为各个通知注册的观察者,并在通知到达时,去查找相应的观察者,将通知转发给他们进行处理。

Notification的命名方式及定义方法

[Name of associated class] + [Did | Will] + [UniquePartOfName] + Notification

Apple范例:

NSApplicationDidBecomeActiveNotification
NSWindowDidMiniaturizeNotification
NSTextViewDidChangeSelectionNotification
NSColorPanelColorDidChangeNotification

在发送通知的实现文件中,按如下方式定义:

NSNotificationName const 通知名 = @"text notification";

在需要接收改通知的类文件的顶部按如下方式声明该通知变量:

UIKIT_EXTERN NSNotificationName const 通知名;

NSNotificationName==NSString *
UIKIT_EXTERN==extern

具体详情可以参考下面大神的链接。

参考来源:http://www.jianshu.com/p/761f302c0bd5

监听通知
/** 监听通知 @param observer 观察者(不能为nil,通知中心会弱引用,ARC是weak,MRC是assign,所以这也是MRC不移除会crash,ARC不移除不会crash的原因,建议都要严格移除。) @param aSelector 收到消息后要执行的方法 @param aName 消息通知的名字(如果name设置为nil,则表示接收所有消息) @param anObject 消息发送者(表示接收哪个发送者的通知,如果第四个参数为nil,接收所有发送者的通知) */- addObserver:observer selector:aSelector name:(nullable NSNotificationName)aName object:(nullable id)anObject;

 // 监听通知 [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector name:@"AAAA" object:nil];

注意:1、每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知。2、observer 观察者(不能为nil,通知中心会弱引用,ARC是iOS9之前是unsafe_unretained,iOS9及以后是weak,MRC是assign,所以这也是MRC不移除会crash,ARC不移除不会crash的原因,建议都要严格移除。)

NSNotification

发送方将消息以NSNotification的形式发送给通知中心,然后通知中心将消息派发给注册了该消息的接收方。

@property (readonly, copy) NSString *name;
@property (nullable, readonly, retain) id object;
@property (nullable, readonly, copy) NSDictionary *userInfo;
  • name:通知的名字,一般为字符串。
  • object:通知携带的对象,一般为发送消息的独享本身。
  • userInfo:发送方在发送消息的同时想要传递的参数。
    创建一个notification有下列实例方法:
- (instancetype)initWithName:(NSNotificationName)name object:(nullable id)object userInfo:(nullable NSDictionary *)userInfo

类方法:

+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject;
+ (instancetype)notificationWithName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

本文主要整理了一下NSNotificationCenter的使用及需要注意的一些问题,并提出了一些未解决的问题,希望能在此得到解答。

NSNotification用法

总结自南峰子的技术博客
总结自天口三水羊的简书

发送通知
/** 发送通知 @param notification 通知对象 */- postNotification:(NSNotification *)notification;/** 发送通知 @param aName 消息通知的名字 @param anObject 消息发送者 @param aUserInfo 传递的数据 */- postNotificationName:(NSNotificationName)aName object:(nullable id)anObject;- postNotificationName:(NSNotificationName)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

 //发送通知 [[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];

发送通知

发送通知的方法主要有下列几种:

- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

添加观察者的两种方式:

方式一:

- (void)addObserver:(id)notificationObserver
           selector:(SEL)notificationSelector
               name:(NSString *)notificationName
             object:(id)notificationSender
  1. notificationObserver不能为nil。
  2. notificationSelector回调方法有且只有一个参数(NSNotification对象)。
  3. 如果notificationName为nil,则会接收所有的通知(如果notificationSender不为空,则接收所有来自于notificationSender的所有通知)。
  4. 如果notificationSender为nil,则会接收所有notificationName定义的通知;否则,接收由notificationSender发送的通知。
  5. 监听同一条通知的多个观察者,在通知到达时,它们执行回调的顺序是不确定的,所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。

方式二:

- (id<NSObject>)addObserverForName:(NSString *)name
                            object:(id)obj
                             queue:(NSOperationQueue *)queue
                        usingBlock:(void (^)(NSNotification *note))block
  1. name和obj为nil时的情形与前面一个方法是相同的。

  2. 如果queue为nil,则消息是默认在post线程中同步处理,即通知的post与转发是在同一线程中;但如果我们指定了操作队列,不管通知是在哪个线程中post的,都会在Operation Queue所属的线程中进行转发。

  3. block块会被通知中心拷贝一份(执行copy操作),以在堆中维护一个block对象,直到观察者被从通知中心中移除。所以,应该特别注意在block中使用外部对象,避免出现对象的循环引用。

  4. 如果一个给定的通知触发了多个观察者的block操作,则这些操作会在各自的Operation Queue中被并发执行。所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。
    该方法会返回一个表示观察者的对象,记得在不用时释放这个对象。

  5. 关于注册监听者,还有一个需要注意的问题是,每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知。

移除通知
/** 移除通知,通过多条件移除通知 @param observer 观察者 @param aName 通知名字 @param anObject 通知发送者 */- removeObserver:observer;- removeObserver:observer name:(nullable NSNotificationName)aName object:(nullable id)anObject ;

 // 移除self的全部通知 [[NSNotificationCenter defaultCenter] removeObserver:self]; // 移除self的AAAA通知 [[NSNotificationCenter defaultCenter] removeObserver:self name:@"AAAA" object:nil];

注册监听者

注册监听者有下列几个方法:

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject;
- (id <NSObject>)addObserverForName:(nullable NSString *)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(void (^)(NSNotification *note))block;

很多人不明白这里的object指的是什么,在发送通知的时候也会传递一个object参数,一般情况下发送通知的object参数传递的是发送方自己,那么在注册监听者这里,object参数指代的也是发送方的这个object参数,意思就是接收object对象发出的名为name的通知,如果有其它发送方发出同样name的通知,是不会接收到通知的。如果把name和object这两个参数同时置为nil,则会接收所有的通知。这个可以自行测试。

  • 在注册监听者的时候,大家用的最多的是第一种方式。第二种方式对于大家来说比较陌生,这里多了一个参数queue和一个block,block即受到通知时执行的回调,参数queue指定了这个block在哪个线程中执行,如果block传的是nil,则表示这个回调block在发送通知的线程中执行,也即同步执行。

获取通知中心

每个程序都会有一个默认的通知中心。为此,NSNotificationCenter提供了一个类方法来获取这个通知中心。

+ (NSNotificationCenter*)defaultCenter;

获取了这个默认的通知中心对象后,我们就可以使用它来处理通知相关的操作了,包括注册观察者,移除观察者,发送通知等。

通常如果不是出于必要,我们一般都使用这个默认的通知中心,而不自己创建维护一个通知中心。

移除观察者的方式

- (void)removeObserver:(id)notificationObserver
- (void)removeObserver:(id)notificationObserver
                                    name:(NSString *)notificationName
                                   object:(id)notificationSender
  1. 由于注册观察者时(不管是哪个方法),通知中心会维护一个观察者的弱引用,所以在释放对象时,要确保移除对象所有监听的通知。否则,可能会导致程序崩溃或一些莫名其妙的问题。

  2. 对于第二个方法,如果notificationName为nil,则会移除所有匹配notificationObserver和notificationSender的通知,同理notificationSender也是一样的。而如果notificationName和notificationSender都为nil,则其效果就与第一个方法是一样的了。

  3. 最有趣的应该是这两个方法的使用时机。–removeObserver:适合于在类的dealloc方法中调用,这样可以确保将对象从通知中心中清除;而在viewWillDisappear:这样的方法中,则适合于使用-removeObserver:name:object:方法,以避免不知情的情况下移除了不应该移除的通知观察者。例如,假设我们的ViewController继承自一个类库的某个ViewController类(假设为SKViewController吧),可能SKViewController自身也监听了某些通知以执行特定的操作,但我们使用时并不知道。如果直接在viewWillDisappear:中调用–removeObserver:,则也会把父类监听的通知也给移除。

Block方式的监听消息
/** 监听消息 @param name 消息通知的名字 @param obj 消息发送者(表示接收哪个发送者的通知,如果第四个参数为nil,接收所有发送者的通知) @param queue 如果queue为nil,则消息是默认在post线程中同一线程同步处理;但如果我们指定了操作队列,就在指定的队列里面执行。 @param block block块会被通知中心拷贝一份,需要注意的就是避免引起循环引用的问题,block里面不能使用self,需要使用weakSelf。 @return observer观察者的对象,最后需要[[NSNotificationCenter defaultCenter] removeObserver:observer]; */- (id <NSObject>)addObserverForName:(nullable NSNotificationName)name object:(nullable id)obj queue:(nullable NSOperationQueue *)queue usingBlock:(NSNotification *note))block;

注意:1、block块会被通知中心拷贝一份,需要注意的就是避免引起循环引用的问题,block里面不能使用self,需要使用weakSelf。2、一定要记得[[NSNotificationCenter defaultCenter] removeObserver:observer];3、如果queue为nil,则消息是默认在post线程中同一线程同步处理;但如果指定了操作队列,就在指定的队列里面执行。

只监听一次的使用(消息执行完,在block里面移除observer)

 //__block __weak id<NSObject> observer 可以在block里面移除 __block __weak id<NSObject> observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"AAAA" object:nil queue:NULL usingBlock:^(NSNotification *note) { NSLog(@"note : %@", note.object); //发送通知 [[NSNotificationCenter defaultCenter] removeObserver:observer]; }];

移除监听者

- (void)removeObserver:(id)observer;
- (void)removeObserver:(id)observer name:(nullable NSString *)aName object:(nullable id)anObject;

在iOS9以后已经不需要手动移除监听者。

添加观察者

- (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString*)aName object:(nullable id)anObject;

这个方法带有4个参数,分别指定了通知的观察者、处理通知的回调、通知名及通知的发送对象。这里需要注意几个问题:

observer不能为nil。                                                                                                     aSelector回调方法有且只有一个参数(NSNotification对象)。

如果aName为nil,则会接收所有的通知(如果anObject不为空,则接收所有来自于anObject的所有通知)。

如代码清单1所示。

如果anObject为nil,则会接收所有aName定义的通知;否则,接收由anObject发送通知。

监听同一个通知的多个观察者,在通知到达时,他们执行回调的顺序是不正确的,所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。

对于以上几点,我们来重点关注一下第3条。以下代码演示了当我们的aName设置为nil时,通知的监听情况。

代码清单1:添加一个Observer,其中aName为nil

@implementationfirstViewController

- (void)viewDidLoad {

[superviewDidLoad];

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(notificationAction:)name:nil object:nil];

[[NSNotificationCenter defaultCenter]postNotificationName:TEST_NOTIFICATION object:nil];

}

-(void)notificationAction:(NSNotification*)sender

{

NSLog(@"sender = %@",sender);

}

运行后的输出结果如下:

sender=TestNotification

sender=UIWindowDidBecomeVisibleNotification

sender=UIWindowDidBecomeKeyNotification

sender=UIApplicationDidFinishLaunchingNotification

sender=_UIWindowContentWillRotateNotification

sender=_UIApplicationWillAddDeactivationReasonNotification

sender=_UIApplicationDidRemoveDeactivationReasonNotification

sender=UIDeviceOrientationDidChangeNotification

sender=_UIApplicationDidRemoveDeactivationReasonNotification

sender=UIApplicationDidBecomeActiveNotification

可以看出,我们的对象基本上监听了测试程序启动后的所示消息。当然,我们很少会去这么做。

而对于第4条,使用得比较多的场景是监听UITextField的修改事件,通常我们在一个ViewController中,只希望去监听当前视图中的UITextField修改事件,而不希望监听所有UITextField的修改事件,这时我们就可以将当前页面的UITextField对象指定为anObject。

在iOS 4.0之后,NSNotificationCenter为了跟上时代,又提供了一个以block方式实现的添加观察者的方法,如下所示:

- (id)addObserverForName:(nullableNSString*)name object:(nullable id)obj queue:(nullableNSOperationQueue*)queue usingBlock:(void(^)(NSNotification*note))block

大家第一次看到这个方法时是否会有这样的疑问:观察者呢?参数中并没有指定具体的观察者,那谁是观察者呢?实际上,与前一个方法不同的是,前者使用一个现存的对象作为观察者,而这个方法会创建一个匿名的对象作为观察者(即方法返回的id对象),这个匿名对象会在指定的队列(queue)上去执行我们的block。

这个方法的优点在于添加观察者的操作与回调处理操作的代码更加紧凑,不需要拼命滚动鼠标就能直接找到处理代码,简单直观。这个方法也有几个地方需要注意:

name和obj为nil时的情形与前面一个方法是相同的。

如果queue为nil,则消息是默认在post线程中同步处理,即通知的post与转发是在同一线程中;但如果我们指定了操作队列,情况就变得有点意思了,我们一会再讲。

block块会被通知中心拷贝一份(执行copy操作),以在堆中维护一个block对象,直到观察者被从通知中心中移除。所以,应该特别注意在block中使用外部对象,避免出现对象的循环引用,这个我们在下面将举例说明。

如果一个给定的通知触发了多个观察者的block操作,则这些操作会在各自的Operation Queue中被并发执行。所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。

该方法会返回一个表示观察者的对象,记得在不用时释放这个对象。

下面我们重点说明一下第2点和第3点。

关于第2点,当我们指定一个Operation Queue时,不管通知是在哪个线程中post的,都会在Operation Queue所属的线程中进行转发,如代码清单2所示:

代码清单2:在指定队列中接收通知

@implementationViewController

-(void)viewDidLoad{

[super viewDidLoad];

[[NSNotificationCenter defaultCenter]addObserverForName:TEST_NOTIFICATIONobject:nil queue:[NSOperationQueue mainQueue]usingBlock:^(NSNotification*note){

NSLog(@"receive thread = %@",[NSThread currentThread]);

}];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0),^{

NSLog(@"post thread = %@",[NSThread currentThread]);

[[NSNotificationCenter defaultCenter]postNotificationName:TEST_NOTIFICATIONobject:nil];

});

}

在这里,我们在主线程里添加了一个观察者,并指定在主线程队列中去接收处理这个通知。然后我们在一个全局队列中post了一个通知。我们来看下输出结果:

post thread=NSThread:0x7ffe0351f5f0>{number=2,name=(null)}

receive thread=NSThread:0x7ffe03508b30>{number=1,name=main}

可以看到,消息的post与接收处理并不是在同一个线程中。如上面所提到的,如果queue为nil,则消息是默认在post线程中同步处理,大家可以试一下。

对于第3点,由于使用的是block,所以需要注意的就是避免引起循环引用的问题,如代码清单3所示:

代码清单3:block引发的循环引用问题

@interfaceObserver: NSObject

@property(nonatomic,assign)NSInteger i;

@property(nonatomic,weak)idNSObject>observer;

@end

@implementationObserver

-(instancetype)init

{

self=[super init];

if(self)

{

NSLog(@"Init Observer");

// 添加观察者

_observer=[[NSNotificationCenter defaultCenter]addObserverForName:TEST_NOTIFICATIONobject:nil queue:[NSOperationQueue mainQueue]usingBlock:^(NSNotification*note){

NSLog(@"handle notification");

// 使用self

self.i=10;

}];

}

return self;

}

@end

#pragma mark - ViewController

@implementationViewController

-(void)viewDidLoad{

[superviewDidLoad];

[self createObserver];

// 发送消息

[[NSNotificationCenter defaultCenter]postNotificationName:TEST_NOTIFICATION object:nil];

}

-(void)createObserver{

Observer*observer=[[Observer alloc]init];

}

@end

运行后的输出如下:

Init Observer

handle notification

我们可以看到createObserver中创建的observer并没有被释放。所以,使用addObserverForName:object:queue:usingBlock:一定要注意这个问题。

post消息

- postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:
  1. 每次post一个通知时,通知中心都会去遍历一下它的分发表,然后将通知转发给相应的观察者。
  2. 通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。
NSNotificationCenter的同步和异步

测试1

- viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector name:@"AAAA" object:nil]; //发送通知 NSLog(@"发送通知的线程=====%@",[NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil];}- aaaa { NSLog(@"通知执行的方法线程=====%@",[NSThread currentThread]);} /*运行输出: 发送通知的线程=====<NSThread: 0x604000072f00>{number = 1, name = main} 通知执行的方法线程=====<NSThread: 0x604000072f00>{number = 1, name = main} */

测试2

- viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector name:@"AAAA" object:nil]; dispatch_async(dispatch_get_global_queue, ^{ NSLog(@"发送通知的线程--post前=====%@",[NSThread currentThread]); [[NSNotificationCenter defaultCenter] postNotificationName:@"AAAA" object:nil]; NSLog(@"发送通知的线程--post后=====%@",[NSThread currentThread]); }); }- aaaa { NSLog(@"通知执行的方法线程=====%@",[NSThread currentThread]); [NSThread sleepForTimeInterval:5];} /* 2017-10-25 17:21:13.847114+0800 SortDemo[18827:46789692] 发送通知的线程--post前=====<NSThread: 0x60400027bac0>{number = 3, name = } 2017-10-25 17:21:13.847352+0800 SortDemo[18827:46789692] 通知执行的方法线程=====<NSThread: 0x60400027bac0>{number = 3, name = } 2017-10-25 17:21:18.850760+0800 SortDemo[18827:46789692] 发送通知的线程--post后=====<NSThread: 0x60400027bac0>{number = 3, name = } */

得知:1、执行监听方法是在发送通知的当前线程2、发送通知与执行监听方法是同步的(发送通知会等待全部监听执行完)

所以关于界面的操作用到通知就需要注意:通知发送是什么线程?执行监听方法是不是耗时的任务?

NSNotificationQueue(通知队列)

  • NSNotificationQueue是notification Center的缓冲池。
    如果我们使用普通的- (void)postNotification:(NSNotification *)notification这种方法来发送通知,那么这个通知就会直接发送到notification Center,notification Center则会直接将其发送给注册了该通知的观察者。但是如果我们使用NSNotificationQueue就不一样了,通知不是直接发送给notification Center,而是先发送给NSNotificationQueue,然后由NSNotificationQueue决定在当前runloop结束或者空闲的时候转发给notification Center,再由notification转发给注册的观察者。通过NSNotificationQueue,可以
    合并重复的通知,以便只发送一个通知。
  • NSNotificationQueue遵循FIFO的顺序,当一个通知移动到NSNotificationQueue的最前面,它就被发送给notification Center,然后notification Center再将通知转发给注册了该通知的监听者。
  • 每一个线程都有一个默认的NSNotificationQueue,这个NSNotificationQueue和通知中心联系在一起。当然我们也可以自己创建NSNotificationQueue,可以为一个线程创建多个NSNotificationQueue。
    NSNotificationQueue的核心方法有下列几个:
//类方法返回当前线程的默认的NSNotificationQueue。
defaultQueue

- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

上面这个方法是使用NSNotificationQueue来发送通知用的。这里面有四个参数。

  • notification是所要发送的通知。
  • postingStyle 这是一个枚举类型的参数。
typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,
    NSPostASAP = 2,
    NSPostNow = 3
};

NSPostingStyle即指发送通知的方式,一共有三种方式。

  1. NSPostWhenIdle
    通过字面意思大概可以知道是在空闲时发送。
    简单地说就是当本线程的runloop空闲时即发送通知到通知中心。
  2. NSPostASAP
    ASAP即as soon as possible,就是说尽可能快。
    当当前通知或者timer的回调执行完毕时发送通知到通知中心。
  3. NSPostNow
    多个相同的通知合并之后马上发送。
  • coalesceMask
    coalesceMask即多个通知的合并方式。它也是一个枚举类型。
    有时候会在一段时间内向NSNotificationQueue发送多个通知,有些通知是重复的,我们并不希望这些通知全部发送带通知中心,那么就可以使用这个枚举类型的参数。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,
    NSNotificationCoalescingOnName = 1,
    NSNotificationCoalescingOnSender = 2
};
  1. NSNotificationNoCoalescing
    不管是否重复,不合并。
  2. NSNotificationCoalescingOnName
    按照通知的名字,如果名字重复,则移除重复的。
  3. NSNotificationCoalescingOnSender
    按照发送方,如果多个通知的发送方是一样的,则只保留一个。
  • modes
    这里的mode指定的是当前的runloop的mode,指定mode后,只有当前线程的runloop在这个特定的mode下才能将通知发送到通知中心。

移除观察者

与注册观察者相对应的,NSNotificationCenter为我们提供了两个移除观察者的方法。它们的定义如下:

-(void)removeObserver:(id) notificationObserver

-(void)removeObserver:(id)notification Observername:(NSString*)notificationName object:(id)notificationSender

前一个方法会将notificationObserver从通知中心中移除,这样notificationObserver就无法再监听任何消息。而后一个会根据三个参数来移除相应的观察者。

这两个方法也有几点需要注意:

由于注册观察者时(不管是哪个方法),通知中心会维护一个观察者的弱引用,所以在释放对象时,要确保移除对象所有监听的通知。否则,可能会导致程序崩溃或一些莫名其妙的问题。

对于第二个方法,如果notificationName为nil,则会移除所有匹配notificationObserver和notificationSender的通知,同理notificationSender也是一样的。而如果notificationName和notificationSender都为nil,则其效果就与第一个方法是一样的了。大家可以试一下。

最有趣的应该是这两个方法的使用时机。–removeObserver:适合于在类的dealloc方法中调用,这样可以确保将对象从通知中心中清除;而在viewWillDisappear:这样的方法中,则适合于使用-removeObserver:name:object:方法,以避免不知情的情况下移除了不应该移除的通知观察者。例如,假设我们的ViewController继承自一个类库的某个ViewController类(假设为SKViewController吧),可能SKViewController自身也监听了某些通知以执行特定的操作,但我们使用时并不知道。如果直接在viewWillDisappear:中调用–removeObserver:,则也会把父类监听的通知也给移除。

关于注册监听者,还有一个需要注意的问题是,每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知,如代码清单4所示:

代码清单4:同一对象多次注册同一消息

其输出结果如下所示:

notification=TestNotification

notification=TestNotification

可以看到对象处理了两次通知。所以,如果我们需要在viewWillAppear监听一个通知时,一定要记得在对应的viewWillDisappear里面将观察者移除,否则就可能会出现上面的情况。

最后,再特别重点强调的非常重要的一点是,在释放对象前,一定要记住如果它监听了通知,一定要将它从通知中心移除。如果是用addObserverForName:object:queue:usingBlock:,也记得一定得移除这个匿名观察者。说白了就一句话,添加和移除要配对出现。

通知中心是如何维护观察者对象的

上面这个问题在《斯坦福大学公开课:iOS 7应用开发》的第5集的第57分50秒中得到了解答:确实使用的是unsafe_unretained,老师的解释是,之所以使用unsafe_unretained,而不使用weak,是为了兼容老版本的系统。

iOS8及以前,NSNotificationCenter持有的是观察者的unsafe_unretained指针(可能是为了兼容老版本),这样,在观察者回收的时候未removeOberser,而后再进行post操作,则会向一段被回收的区域发送消息,所以出现野指针crash。而iOS9以后,unsafe_unretained改成了weak指针,即使dealloc的时候未removeOberser,再进行post操作,则会向nil发送消息,所以没有任何问题。
处理同步问题的办法:

1、监听方法里面开线程或者异步执行

- aaaa { dispatch_async(dispatch_get_global_queue, ^{ }); }

2、NSNotificationQueue的NSPostASAP

 NSLog(@"发送通知的线程--post前=====%@",[NSThread currentThread]); NSNotification *notification = [NSNotification notificationWithName:@"AAAA" object:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:notification postingStyle:NSPostASAP]; NSLog(@"发送通知的线程--post后=====%@",[NSThread currentThread]);

同步与异步发送

  • 同步发送通知
    当我们使用下列这些方法时是使用的同步发送通知,这些也是我们平时常用的发送通知的方法。
- (void)postNotification:(NSNotification *)notification;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject;
- (void)postNotificationName:(NSString *)aName object:(nullable id)anObject userInfo:(nullable NSDictionary *)aUserInfo;

同步指的是,当发送方发送通知后,必须要等到所有的监听者完成监听回调,发送方才会接着执行下面的代码。所以如果监听者的回调有大量的计算要处理的话,发送方会一直等待,只有回调全部结束才接着往下执行。

  • 异步发送通知
    当我们使用NSNotificationQueue(通知队列)的下列方法发送通知时是异步发送通知:
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;
- (void)enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

异步发送通知即只要发送方的通知发送出去了,不管监听方的回调是否执行完毕,反正我就开始执行下面的代码。
但是!!!需要注意的是,当NSPostingStyle的类型是NSPostWhenIdle和NSPostASAP时确实是异步的,而当类型是NSPostNow时则是同步的。

POST消息

注册了通知观察者,我们便可以随时随地的去post一个通知了(当然,如果闲着没事,也可以不注册观察者,post通知随便玩,只是没人理睬罢了)。NSNotificationCenter提供了三个方法来post一个通知,如下所示:

-postNotification:

–postNotificationName:object:

–postNotificationName:object:userInfo:

我们可以根据需要指定通知的发送者(object)并附带一些与通知相关的信息(userInfo),当然这些发送者和userInfo可以封装在一个NSNotification对象中,由- postNotification:来发送。注意,- postNotification:的参数不能为空,否则会引发一个异常,如下所示:

***Terminating app due to uncaught exception'NSInvalidArgumentException',reason:'*** -[NSNotificationCenter postNotification:]: notification is nil'

每次post一个通知时,通知中心都会去遍历一下它的分发表,然后将通知转发给相应的观察者。

另外,通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。如代码清单5所示:

代码清单5:通知的同步处理

@implementationViewController

-(void)viewDidLoad{

[superviewDidLoad];

[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(handleNotification:) name:TEST_NOTIFICATION object:nil];

[[NSNotificationCenter defaultCenter]postNotificationName:TEST_NOTIFICATION object:nil];

NSLog(@"continue");

}

-(void)handleNotification:(NSNotification*)notification

{

NSLog(@"handle notification");

}

@end

运行后输出结果是:

handle notification

continue

Notification Queues和异步通知

异步通知原理
创建一个NSNotificationQueue队列(first in-first out),将定义的NSNotification放入其中,并为其指定三种状态之一:

typedef NS_ENUM(NSUInteger, NSPostingStyle) {
    NSPostWhenIdle = 1,      // 当runloop处于空闲状态时post
    NSPostASAP = 2,    // 当当前runloop完成之后立即post
    NSPostNow = 3    // 立即post,同步(为什么需要这种type,且看三.3)
};

异步通知的使用

- (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(@"测试同步还是异步");
    return YES;
}

// 输出
2017-02-26 19:56:32.805 notification[19406:12719309] 测试同步还是异步
2017-02-26 19:56:32.816 notification[19406:12719309] selector 1
2017-02-26 19:56:32.816 notification[19406:12719309] block 2
NSNotificationQueue

NSNotificationQueue给通知机制提供了2个重要的特性:1、异步发送通知2、通知合并

NSPostingStyle和NSNotificationCoalescing

typedef NS_ENUM(NSUInteger, NSPostingStyle) { NSPostWhenIdle = 1, (当runloop处于空闲状态时post) NSPostASAP = 2, (当runloop能够调用的时候立即post) NSPostNow = 3 (立即post,同步)};typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) { NSNotificationNoCoalescing = 0,  NSNotificationCoalescingOnName = 1, (根据NSNotification的name字段进行合成) NSNotificationCoalescingOnSender = 2 (根据NSNotification的object字段进行合成)};

/** 队列发送通知 @param notification 通知对象 @param postingStyle 发送时机 @param coalesceMask 合并规则(可以用|符号连接,指定多个) @param modes NSRunLoopMode 当指定了某种特定runloop mode后,该通知值有在当前runloop为指定mode的下,才会被发出(多线程使用时候,要开启线程的runloop且响应的mode) */- enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle;- enqueueNotification:(NSNotification *)notification postingStyle:(NSPostingStyle)postingStyle coalesceMask:(NSNotificationCoalescing)coalesceMask forModes:(nullable NSArray<NSRunLoopMode> *)modes;

简单demo

- viewDidLoad { [super viewDidLoad]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector name:@"AAAA" object:nil]; NSNotification *noti1 = [NSNotification notificationWithName:@"AAAA" object:@{@"1111":@"1111"}]; NSNotification *noti2 = [NSNotification notificationWithName:@"AAAA" object:@{@"2222":@"2222"}]; NSNotification *noti3 = [NSNotification notificationWithName:@"AAAA" object:@{@"3333":@"3333"}]; [[NSNotificationQueue defaultQueue] enqueueNotification:noti1 postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:noti2 postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil]; [[NSNotificationQueue defaultQueue] enqueueNotification:noti3 postingStyle:NSPostASAP coalesceMask:NSNotificationCoalescingOnName forModes:nil]; // 三个noti,监听方法只执行一次,还有NSPostNow使用合并没有效果}- aaaa:(NSNotification *)noti { NSLog(@"=====%@",noti.object); // 接受到的信息是noti1的 (这个阶段第一个enqueueNotification)}

所以:1、当前runloop状态使用合并通知,监听方法只执行一次;2、监听方法接受到信息是(当前runloop状态第一个enqueueNotification的信息object)3、由于NSPostNow性质可知,不能用于通知合成。

一些思考

翻了好些资料,还有两个问题始终没有明确的答案。

首先就是通知中心是如何维护观察者对象的。可以明确的是,添加观察者时,通知中心没有对观察者做retain操作,即不会使观察者的引用计数加1。那通知中心维护的是观察者的weak引用呢还是unsafe_unretained引用呢?

个人认为可能是unsafe_unretained的引用,因为我们知道如果是weak引用,其所指的对象被释放后,这个引用会被置成nil。而实际情况是通知中心还会给这个对象发送消息,并引发一个异常。而如果向nil发送一个消息是不会导致异常的。

另外,我们知道NSNotificationCenter实现的是观察者模式,而且通常情况下消息在哪个线程被post,就在哪个线程被转发。而从上面的描述可以发现,

-addObserverForName:object:queue:usingBlock:添加的匿名观察者可以在指定的队列中处理通知,那它的实现机制是什么呢?

Notification Queues的合成作用

NSNotificationQueue除了有异步通知的能力之外,也能对当前队列的通知根据NSNotificationCoalescing类型进行合成(即将几个合成一个)。

typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
    NSNotificationNoCoalescing = 0,  // 不合成
    NSNotificationCoalescingOnName = 1,  // 根据NSNotification的name字段进行合成
    NSNotificationCoalescingOnSender = 2  // 根据NSNotification的object字段进行合成
};
系统的通知Name
// 当程序被推送到后台时UIKIT_EXTERN NSNotificationName const UIApplicationDidEnterBackgroundNotification NS_AVAILABLE_IOS;// 当程序从后台将要重新回到前台时UIKIT_EXTERN NSNotificationName const UIApplicationWillEnterForegroundNotification NS_AVAILABLE_IOS;// 当程序完成载入后通知UIKIT_EXTERN NSNotificationName const UIApplicationDidFinishLaunchingNotification;// 应用程序转为激活状态时UIKIT_EXTERN NSNotificationName const UIApplicationDidBecomeActiveNotification;// 用户按下主屏幕按钮调用通知,并未进入后台状态UIKIT_EXTERN NSNotificationName const UIApplicationWillResignActiveNotification;// 内存较低时通知UIKIT_EXTERN NSNotificationName const UIApplicationDidReceiveMemoryWarningNotification;// 当程序将要退出时通知UIKIT_EXTERN NSNotificationName const UIApplicationWillTerminateNotification;// 当系统时间发生改变时通知UIKIT_EXTERN NSNotificationName const UIApplicationSignificantTimeChangeNotification;// 当StatusBar框方向将要变化时通知UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with new orientation// 当StatusBar框方向改变时通知UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarOrientationNotification __TVOS_PROHIBITED; // userInfo contains NSNumber with old orientation// 当StatusBar框Frame将要改变时通知UIKIT_EXTERN NSNotificationName const UIApplicationWillChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with new frame// 当StatusBar框Frame改变时通知UIKIT_EXTERN NSNotificationName const UIApplicationDidChangeStatusBarFrameNotification __TVOS_PROHIBITED; // userInfo contains NSValue with old frame// 后台下载状态发生改变时通知(iOS7.0以后可用)UIKIT_EXTERN NSNotificationName const UIApplicationBackgroundRefreshStatusDidChangeNotification NS_AVAILABLE_IOS __TVOS_PROHIBITED;// 受保护的文件当前变为不可用时通知UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataWillBecomeUnavailable NS_AVAILABLE_IOS;// 受保护的文件当前变为可用时通知UIKIT_EXTERN NSNotificationName const UIApplicationProtectedDataDidBecomeAvailable NS_AVAILABLE_IOS;// 截屏通知(iOS7.0以后可用)UIKIT_EXTERN NSNotificationName const UIApplicationUserDidTakeScreenshotNotification NS_AVAILABLE_IOS;

小结

在我们的应用程序中,一个大的话题就是两个对象之间如何通信。我们需要根据对象之间的关系来确定采用哪一种通信方式。对象之间的通信方式主要有以下几种:

直接方法调用

Target-Action

Delegate

回调(block)

KVO

通知

一般情况下,我们可以根据以下两点来确定使用哪种方式:

通信对象是一对一的还是一对多的

对象之间的耦合度,是强耦合还是松耦合

Objective-C中的通知由于其广播性及松耦合性,非常适合于大的范围内对象之间的通信(模块与模块,或一些框架层级)。通知使用起来非常方便,也正因为如此,所以容易导致滥用。所以在使用前还是需要多想想,是否有更好的方法来实现我们所需要的对象间通信。毕竟,通知机制会在一定程度上会影响到程序的性能。

对于使用NSNotificationCenter,最后总结一些小建议:

在需要的地方使用通知。

注册的观察者在不使用时一定要记得移除,即添加和移除要配对出现。

尽可能迟地去注册一个观察者,并尽可能早将其移除,这样可以改善程序的性能。因为,每post一个通知,都会是遍历通知中心的分发表,确保通知发给每一个观察者。

记住通知的发送和处理是在同一个线程中。

使用-addObserverForName:object:queue:usingBlock:务必处理好内存问题,避免出现循环引用。

NSNotificationCenter是线程安全的,但并不意味着在多线程环境中不需要关注线程安全问题。不恰当的使用仍然会引发线程问题。

指定Thread处理通知

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

可知,a中的observer的selector将会在DISPATCH_QUEUE_PRIORITY_BACKGROUND中执行,若该selector执行的是刷新UI的操作,那么这种方式显然是错误的。这里,我们需要保证selector永远在mainThread执行。所以,有以下方式,指定observer的回调方法的执行线程:

// 代码
@interface A : NSObject 
- (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 %@ 刷新UI", [NSThread currentThread]);
        // 刷新UI ...
    }];
}
@end

// 输出
current thread <NSThread: 0x7bf29110>{number = 3, name = (null)}
2017-02-27 11:53:46.531 notification[29510:12833116] current thread <NSThread: 0x7be1d6f0>{number = 1, name = main} 刷新UI

对象之间的通信方式主要有以下几种:

  1. 直接方法调用
  2. Target-Action
  3. Delegate
  4. 回调(block)
  5. KVO
  6. 通知

本文由9159.com发布于编程,转载请注明出处:具体详情可以参考下面大神的链接,当我们在不

关键词:

上一篇:如何利用block封装代码,block 就相当于是
下一篇:没有了