NSNotificationCenter+RACSupport把我坑了
熟悉RAC
的,應該都知道它本身針對iOS系統類提供了許多類目用於增加方法,方便使用。但是,今天在使用NSNotificationCenter+RACSupport
的時候遇到了坑,接下來便分享出來。
下面用到的完成測試用例在這裡。
首頁,建立兩個頁面A、B,然後A訂閱通知,B傳送通知,觀察通知的傳遞。
當點選A中的按鈕跳轉的B的頁面時,B傳送通知,這時候A收到通知。日誌如下
2018-09-06 18:14:20.902227+0800 TestRAC+NSNotification[35033:8811463] A收到B的通知了
這時是沒有問題的。
那如果這兩個頁面的通知順序反過來呢?
新建C頁面,並且在C頁面訂閱通知。然後先點選A頁面按鈕跳轉到B,日誌如下:
2018-09-06 18:20:43.901481+0800 TestRAC+NSNotification[35325:8830635] A收到B的通知了
跟上一步一樣,沒有什麼問題。接著繼續點選按鈕,跳轉到C頁面,然後返回到B頁面,繼續點選通知按鈕,日誌如下:
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沒有被釋放嗎?
在C中新增如下程式碼:
- (void)dealloc
{
NSLog(@"c掛了");
}
重新執行,看看C有沒有掛。當從C頁面返回時,日誌如下:
2018-09-06 18:22:53.286350+0800 TestRAC+NSNotification[35424:8837258] c掛了
C頁面確實掛了,但是仍舊能夠收到通知資訊。
接著點選通知按鈕,整個過程的日誌如下:
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:
方法的實現:
- (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
。
對addObserverForName:
方法不熟悉的可以看下方法的註釋:
- (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));
// 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.
返回一個被系統持有的物件,並且這個物件應當被呼叫者拿到,稍後用於呼叫removeObserver:
方法將其移除來停止觀察。
所以,既然上面的C中通知能夠繼續回撥,證明removeObserver:
沒有被呼叫。為什麼呢?
原因有兩點。
1. 訊號的建立中只調用了sendNext:
方法,沒有呼叫sendError:
sendCompleted
方法,所以清理物件的清理方法不會呼叫。
2. 這裡的self
為[NSNotificationCenter defaultCenter]
物件,這個物件是單例物件,所以不會釋放,這樣清理物件也不會呼叫清理方法。
既然存在這種問題,那我們應該怎麼解決呢?
其實我們可以直接使用addObserverForName:
API,這樣子我們既可以使用回撥的方式處理通知,也可以取消通知的訂閱。
新建D頁面新增如下程式碼:
- (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掛了");
}
同樣的操作過程,列印日誌如下:
2018-09-06 18:44:22.613633+0800 TestRAC+NSNotification[36323:8903067] A收到B的通知了
2018-09-06 18:44:26.360049+0800 TestRAC+NSNotification[36323:8903067] c掛了
2018-09-06 18:44:27.021684+0800 TestRAC+NSNotification[36323:8903067] A收到B的通知了
2018-09-06 18:44:28.830822+0800 TestRAC+NSNotification[36323:8903067] A收到B的通知了
2018-09-06 18:44:29.302511+0800 TestRAC+NSNotification[36323:8903067] A收到B的通知了
可以看到,不管點選多少次按鈕,D都不會接收到通知的。