首页,建立两个页面A、B,然后A订阅通知,B发送通知,观察通知的传递。
当点击A中的按钮跳转的B的页面时,B发送通知,这时候A收到通知。日志如下
1 |
2018-09-06 18:14:20.902227+0800 TestRAC+NSNotification[35033:8811463] A收到B的通知了 |
这时是没有问题的。文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
那如果这两个页面的通知顺序反过来呢?文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
新建C页面,并且在C页面订阅通知。然后先点击A页面按钮跳转到B,日志如下:文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
1 |
2018-09-06 18:20:43.901481+0800 TestRAC+NSNotification[35325:8830635] A收到B的通知了 |
跟上一步一样,没有什么问题。接着继续点击按钮,跳转到C页面,然后返回到B页面,继续点击通知按钮,日志如下:文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
1 2 |
2018-09-06 18:20:57.481049+0800 TestRAC+NSNotification[35325:8830635] A收到B的通知了 2018-09-06 18:20:57.481345+0800 TestRAC+NSNotification[35325:8830635] C收到B的通知了 |
What?这是什么情况,为毛C也能收到通知。难道C没有被释放吗?文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
在C中添加如下代码:文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
1 2 3 4 |
- (void)dealloc { NSLog(@"c挂了"); } |
重新运行,看看C有没有挂。当从C页面返回时,日志如下:文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
1 |
2018-09-06 18:22:53.286350+0800 TestRAC+NSNotification[35424:8837258] c挂了 |
C页面确实挂了,但是仍旧能够收到通知信息。
接着点击通知按钮,整个过程的日志如下:文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
1 2 3 4 |
2018-09-06 18:22:48.908253+0800 TestRAC+NSNotification[35424:8837258] A收到B的通知了 2018-09-06 18:22:53.286350+0800 TestRAC+NSNotification[35424:8837258] c挂了 2018-09-06 18:24:33.474609+0800 TestRAC+NSNotification[35424:8837258] A收到B的通知了 2018-09-06 18:24:33.475009+0800 TestRAC+NSNotification[35424:8837258] C收到B的通知了 |
看到了吧,这就是我遇到的坑。那为什么会这个样子呢。其实是因为rac_addObserverForName:方法的实现:文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (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:@"-rac_addObserverForName: %@ object: <%@: %p>", notificationName, [object class], object]; } |
这个方法返回一个信号,创建信号时通过self调用addObserverForName:方法订阅通知。接着返回一个清理对象,清理对象的工作是removeObserver。文章源自大腿Plus-https://www.zhaoshijun.com/archives/1724
对addObserverForName:方法不熟悉的可以看下方法的注释:
1 2 3 4 5 6 |
// The return value is retained by the system, and should be held onto by the caller in // order to remove the observer with removeObserver: later, to stop observation. - (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)); |
1 2 3 4 |
<span class="token operator">-</span> <span class="token punctuation">(</span>id <span class="token operator"><</span>NSObject<span class="token operator">></span><span class="token punctuation">)</span>addObserverForName<span class="token punctuation">:</span><span class="token punctuation">(</span>nullable NSNotificationName<span class="token punctuation">)</span>name object<span class="token punctuation">:</span><span class="token punctuation">(</span>nullable id<span class="token punctuation">)</span>obj queue<span class="token punctuation">:</span><span class="token punctuation">(</span>nullable NSOperationQueue <span class="token operator">*</span><span class="token punctuation">)</span>queue usingBlock<span class="token punctuation">:</span><span class="token punctuation">(</span><span class="token keyword">void</span> <span class="token punctuation">(</span><span class="token operator">^</span><span class="token punctuation">)</span><span class="token punctuation">(</span>NSNotification <span class="token operator">*</span>note<span class="token punctuation">)</span><span class="token punctuation">)</span>block <span class="token function">API_AVAILABLE</span><span class="token punctuation">(</span><span class="token function">macos</span><span class="token punctuation">(</span><span class="token number">10.6</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">ios</span><span class="token punctuation">(</span><span class="token number">4.0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">watchos</span><span class="token punctuation">(</span><span class="token number">2.0</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">tvos</span><span class="token punctuation">(</span><span class="token number">9.0</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span> <span class="token comment">// The return value is retained by the system, and should be held onto by the caller in</span> <span class="token comment">// order to remove the observer with removeObserver: later, to stop observation.</span> |
返回一个被系统持有的对象,并且这个对象应当被调用者拿到,稍后用于调用removeObserver:方法将其移除来停止观察。
所以,既然上面的C中通知能够继续回调,证明removeObserver:没有被调用。为什么呢?
原因有两点。
信号的创建中只调用了sendNext:方法,没有调用sendError: sendCompleted方法,所以清理对象的清理方法不会调用。
这里的self为[NSNotificationCenter defaultCenter]对象,这个对象是单例对象,所以不会释放,这样清理对象也不会调用清理方法。
既然存在这种问题,那我们应该怎么解决呢?
其实我们可以直接使用addObserverForName: API,这样子我们既可以使用回调的方式处理通知,也可以取消通知的订阅。
新建D页面添加如下代码:
1 2 3 4 5 6 7 8 9 10 11 |
- (void)viewDidLoad { [super viewDidLoad]; __block id observer; observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"B" object:nil queue:nil usingBlock:^(NSNotification * _Nonnull note) { NSLog(@"D收到B的通知了"); [[NSNotificationCenter defaultCenter] removeObserver:observer]; }]; - (void)dealloc { NSLog(@"c挂了"); } |
同样的操作过程,打印日志如下:
1 2 3 4 5 6 |
2018-09-06 18<span class="token punctuation">:</span>44<span class="token punctuation">:</span>22.613633+0800 TestRAC+NSNotification[36323<span class="token punctuation">:</span>8903067] A收到B的通知了 2018-09-06 18<span class="token punctuation">:</span>44<span class="token punctuation">:</span>26.360049+0800 TestRAC+NSNotification[36323<span class="token punctuation">:</span>8903067] d挂了 2018-09-06 18<span class="token punctuation">:</span>44<span class="token punctuation">:</span>27.021684+0800 TestRAC+NSNotification[36323<span class="token punctuation">:</span>8903067] A收到B的通知了 2018-09-06 18<span class="token punctuation">:</span>44<span class="token punctuation">:</span>28.830822+0800 TestRAC+NSNotification[36323<span class="token punctuation">:</span>8903067] A收到B的通知了 2018-09-06 18<span class="token punctuation">:</span>44<span class="token punctuation">:</span>29.302511+0800 TestRAC+NSNotification[36323<span class="token punctuation">:</span>8903067] A收到B的通知了 |
可以看到,不管点击多少次按钮,D都不会接收到通知的。
其实,rac_addObserverForName 方法中是有removeObserver的调用的,只是没有触发而已。如果将通知信号的生命周期跟当前控制器一样(通常也应该是这个样子的),也可以解决这个问题。
新建E界面,并将代码改为这样:
1 2 3 4 5 6 7 8 9 10 11 |
- (void)viewDidLoad { [super viewDidLoad]; [[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"B" object:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) { NSLog(@"E收到B的通知了"); }]; } - (void)dealloc { NSLog(@"c挂了"); } |
同样的操作,打印日志如下:
1 2 3 4 5 |
2019-01-09 15:46:12.182847+0800 TestRAC+NSNotification[92564:1259997] A收到B的通知了 2019-01-09 15:46:15.277290+0800 TestRAC+NSNotification[92564:1259997] e挂了 2019-01-09 15:46:15.646536+0800 TestRAC+NSNotification[92564:1259997] A收到B的通知了 2019-01-09 15:46:16.247784+0800 TestRAC+NSNotification[92564:1259997] A收到B的通知了 2019-01-09 15:46:16.470586+0800 TestRAC+NSNotification[92564:1259997] A收到B的通知了 |
可见,结果一样。
所以,不是这个方法的坑,而是我自己使用不当造成的。
评论