自 iOS 9
开始(见 release notes ),Foundation
调整了 NSNotificationCenter
对观察者的引用方式( zeroing weak reference
),再也不给已释放的观察者发送通知,所以以往在 dealloc
时移除观察者的作法能够省去。html
若是是须要适配 iOS 8
,那么 UIViewController
及其子类能够省去移除通知的过程(亲测有效),而其余对象则须要在 dealloc
前移除观察者。ios
感谢 Ace 同窗第一时间的测试发现git
控制器对象对于通知的监听一般是在生命周期的 viewDidLoad
方法处理,也就是说,在 viewDidLoad
以前,还未添加观察者,对应地在在移除通知通知时能够作是否加载了视图的判断以下:github
- (void)dealloc {
if (self.isViewLoaded) {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
}
复制代码
这一点 isViewLoaded
的判断,对于 NSNotification 的监听来讲不是必要的,由于在未监听通知的状况下,调用 removeObserver:
方法是仍旧是安全的,而 KVO ( key-value observing
,则否则。由于 KVO
在未监听的状况下移除观察者是不安全的,因此若是是在 viewDidLoad
监听KVO
,则 KVO
的移除就须要执行判断:编程
- (void)dealloc {
if (self.isViewLoaded) {
[self removeObserver:someObj forKeyPath:@"someKeyPath"];
}
}
复制代码
此外,不少时候控制器的视图还未加载,也须要监听特定的通知,此时通知的监听适合在构造方法 initWithNibName:bundle
方法中监听,此构造方法在代码或者 Interface Builder
构建实例时都会调用:安全
- (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;
}
复制代码
NSNotificationCenter
是支持 block
手法的自 iOS 4
开始通知中心即支持 block
回调,其 API
以下:app
- (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);
复制代码
回调能够指定操做队列,并返回一个观察者对象。调用示例:框架
- (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];
}
复制代码
其中,有几点值得注意:函数
id<NSObject>
监听者对象,实际上是系统的私有类的实例,由于不必暴露其具体类型和接口,因此用一个 id<NSObject>
对象指明用途,从中可见协议的又一个应用场景。target-action
的封装实现,在其内部触发了 action
后调用起初传入的 block
参数。block
都会被通知中心所持有,所以使用者有义务在必要的时候调用 removeObserver:
方法,将此监听移除,不然监听者和 block
及其所捕获的变量都不会释放,从而致使内存泄露。此处详细的说明和解决方案能够参考 SwiftGG翻译组的翻译文章 Block 形式的通知中心观察者是否须要手动注销通知的使用在跨层和面向多个对象通讯时十分便利,也所以而致使难以管理的问题颇受诟病,发送通知时可能须要统一作一些工做,此时对通知进行拦截是必要的。NSNotificationCenter
是 CFNotificationCenter
的封装,有使用相似 NSArray
的类簇设计,并采用了单例模式返回共享实例 defaultCenter
。经过直接继承的方式进行发送通知的拦截是不可行的,由于得到的是始终是静态的单例对象,从 Telegram
公司的开源项目工程中能够看到:经过借鉴 KVO
的实现原理,将单例对象的类修改成特定的子类,从而实现通知的拦截。post
第一步,修改通知中心单例的类:
@interface GSNoteCenter : NSNotificationCenter
@end
/// 修改单例的类为一个子类的类型
void hack() {
id center = [NSNotificationCenter defaultCenter];
object_setClass(center, GSNoteCenter.class);
}
复制代码
第二步,拦截通知的发送事件: 利用继承多态特性,在发送通知的先后进行拦截:
@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
复制代码
PS:拦截以后能够发现系统发送通知的数量和频率真高,从这个侧面看发送通知的性能问题不用太过顾忌。
既不肯意手动移动通知,又想使用 block
实现通知监听,那么必要的封装是必须的。好比, ReactiveCocoa 中的实现以下:
@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:@""];
}
@end
复制代码
将通知做为一个信号源,直接订阅 next
收听结果便可,十分优雅地解决了 block
的使用以及通知的移除。
在不引入响应式框架的状况下,经过自定义通知名称与观察者的关系的方式,能够知足要求。基本思路是:
NSMapTable
。由此实现的初步封装完成放在 GitHub,通知的注册以下:
- (void)registerBlock:(GSNoticeBlock)block service:(NSString *)service forObserver:(id)observer {
GSServiceMap *mapModel = [self mapForService:service];
[mapModel.map setObject:block forKey:observer];
}
复制代码
通知的触发以下:
- (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);
}
}
复制代码
若是须要提早移除监听,操做以下:
- (void)unregisterService:(NSString *)service forObserver:(id)observer {
GSServiceMap *mapModel = [self mapForService:service];
[mapModel.map removeObjectForKey:observer];
}
复制代码
感谢 Mark 同窗说通知中心不安全,才尝试自定义一个安全的通知中心。
通知中心,做为观察者模式的运用,经过 block
的运用能够有更灵活的表现,好比前文分享的 Uber 用于解决通知中心难以管理的解决方案 以 Uber-signals 一窥响应式。
再到 ReactiveCocoa
、RxSwift
函数响应式的思想的进一步抽象,编程的思惟从命令式地调用一个方法/函数,转换为由于某个通知/信号而触发了下一步的操做,值得去进一步探索。
Unregistering NSNotificationCenter Observers in iOS 9 Telegram 源代码 Microsoft/WinObjc Reimplementate NSNotificationCenter Microsolf/WinObjc 真是一座金山啊