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
NSNotification用法
添加观察者的两种方式:
方式一:
- (void)addObserver:(id)notificationObserver
selector:(SEL)notificationSelector
name:(NSString *)notificationName
object:(id)notificationSender
- notificationObserver不能为nil。
- notificationSelector回调方法有且只有一个参数(NSNotification对象)。
- 如果notificationName为nil,则会接收所有的通知(如果notificationSender不为空,则接收所有来自于notificationSender的所有通知)。
- 如果notificationSender为nil,则会接收所有notificationName定义的通知;否则,接收由notificationSender发送的通知。
- 监听同一条通知的多个观察者,在通知到达时,它们执行回调的顺序是不确定的,所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。
方式二:
- (id<NSObject>)addObserverForName:(NSString *)name
object:(id)obj
queue:(NSOperationQueue *)queue
usingBlock:(void (^)(NSNotification *note))block
name和obj为nil时的情形与前面一个方法是相同的。
如果queue为nil,则消息是默认在post线程中同步处理,即通知的post与转发是在同一线程中;但如果我们指定了操作队列,不管通知是在哪个线程中post的,都会在Operation Queue所属的线程中进行转发。
block块会被通知中心拷贝一份(执行copy操作),以在堆中维护一个block对象,直到观察者被从通知中心中移除。所以,应该特别注意在block中使用外部对象,避免出现对象的循环引用。
如果一个给定的通知触发了多个观察者的block操作,则这些操作会在各自的Operation Queue中被并发执行。所以我们不能去假设操作的执行会按照添加观察者的顺序来执行。
该方法会返回一个表示观察者的对象,记得在不用时释放这个对象。关于注册监听者,还有一个需要注意的问题是,每次调用addObserver时,都会在通知中心重新注册一次,即使是同一对象监听同一个消息,而不是去覆盖原来的监听。这样,当通知中心转发某一消息时,如果同一对象多次注册了这个通知的观察者,则会收到多个通知。
移除观察者的方式
- (void)removeObserver:(id)notificationObserver
- (void)removeObserver:(id)notificationObserver
name:(NSString *)notificationName
object:(id)notificationSender
由于注册观察者时(不管是哪个方法),通知中心会维护一个观察者的弱引用,所以在释放对象时,要确保移除对象所有监听的通知。否则,可能会导致程序崩溃或一些莫名其妙的问题。
对于第二个方法,如果notificationName为nil,则会移除所有匹配notificationObserver和notificationSender的通知,同理notificationSender也是一样的。而如果notificationName和notificationSender都为nil,则其效果就与第一个方法是一样的了。
最有趣的应该是这两个方法的使用时机。
–removeObserver:
适合于在类的dealloc
方法中调用,这样可以确保将对象从通知中心中清除;而在viewWillDisappear:
这样的方法中,则适合于使用-removeObserver:name:object:
方法,以避免不知情的情况下移除了不应该移除的通知观察者。例如,假设我们的ViewController继承自一个类库的某个ViewController类(假设为SKViewController吧),可能SKViewController自身也监听了某些通知以执行特定的操作,但我们使用时并不知道。如果直接在viewWillDisappear:
中调用–removeObserver:
,则也会把父类监听的通知也给移除。
post消息
- postNotification:
– postNotificationName:object:
– postNotificationName:object:userInfo:
- 每次post一个通知时,通知中心都会去遍历一下它的分发表,然后将通知转发给相应的观察者。
- 通知的发送与处理是同步的,在某个地方post一个消息时,会等到所有观察者对象执行完处理操作后,才回到post的地方,继续执行后面的代码。
通知中心是如何维护观察者对象的
上面这个问题在《斯坦福大学公开课: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发送消息,所以没有任何问题。
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
Notification Queues的合成作用
NSNotificationQueue除了有异步通知的能力之外,也能对当前队列的通知根据NSNotificationCoalescing类型进行合成(即将几个合成一个)。
typedef NS_OPTIONS(NSUInteger, NSNotificationCoalescing) {
NSNotificationNoCoalescing = 0, // 不合成
NSNotificationCoalescingOnName = 1, // 根据NSNotification的name字段进行合成
NSNotificationCoalescingOnSender = 2 // 根据NSNotification的object字段进行合成
};
指定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
对象之间的通信方式主要有以下几种:
- 直接方法调用
- Target-Action
- Delegate
- 回调(block)
- KVO
- 通知