原創文章轉載請註明出處,謝謝
端午想好好休息一下,於是就沒出去玩了,陪陪家人和妹子
關於NSNotification大家一定不會陌生,所以這裡我就不講關於NSNotification的具體用法了,網上一搜一大片。NSNotification是基於Observe Design Pattern來實現的,所有Register了這個訊息的物件,都會收到由NSNotification傳送的訊息。它有一個自己的訊息中心是一個單例,我們基本通過以下三個函式就可以實現一個基本的Observe Pattern。
1 2 3 4 5 6 7 8 9 10 11 |
// register notification - (void)addObserver:(id)observer selector:(SEL)aSelector name:(nullable NSString *)aName object:(nullable id)anObject; // post notification - (void)postNotification:(NSNotification *)notification; // dealloc reomove notification - (void)removeObserver:(id)observer; |
但是系統的NSNotification實在是太臃腫了,想想要在所有的業務裡到處散佈著上面三行程式碼,這顯然是不合適的。我們最理想的效果是隻要存在兩個方法register和post,register中可以直接傳遞一個block來完成收到訊息時觸發的事件,然後在dealloc中自動呼叫remove的操作。所以我們不妨先定義一下這兩個函式:
1 2 3 4 5 6 7 |
+ (void)postNotificationNamed:(NSString*)notificationName object:(id)object; // default one parame in callback + (void)registerNotificationHandlerForName:(NSString *)notificationName target:(id)target action:(void(^)(id))action; |
其實我們主要的任務就是要在register函式中去動態在相應的target中新增一個函式,來執行對應的callback操作。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |
@interface NotificationHelper : NSObject @end @implementation NotificationHelper - (void)justForGetTypeEncoding:(id)object {}; @end + (void)postNotificationNamed:(NSString *)notificationName object:(id)object { [[NSNotificationCenter defaultCenter] postNotificationName:notificationName object:object]; } + (void)registerNotificationHandlerForName:(NSString *)notificationName target:(id)target action:(void(^)(id))action { SEL sel = NSSelectorFromString([NSString stringWithFormat:@"_xxx_notification_%@:", notificationName]); if ([target respondsToSelector:sel]) { NSError* error = nil; [target aspect_hookSelector:sel withOptions:AspectPositionAfter usingBlock:^(idinfo, NSNotification* notification) { action(notification.object); } error:&error]; } else { typedef void (^BlockType)(id, NSNotification *); BlockType block = ^(id target, NSNotification *notification) { action(notification.object); }; BOOL success = class_addMethod([target class], sel, imp_implementationWithBlock(block), method_getTypeEncoding(class_getInstanceMethod([BMTBusinessUtilHelper class], @selector(justForGetTypeEncoding:)))); } [[NSNotificationCenter defaultCenter] addAutoDetachedObserver:target selector:sel name:notificationName object:nil]; } |
上面的思想其實就是當我們去註冊一個訊息的時候,我們先去判斷目標class中是否已經新增了這個SEL,如果沒有,我們就去動態新增這個方法到目標class,如果有了,我們就在已存在的方法中追加這個方法。最後把新建立的方法註冊到訊息中。大致的思路就是這樣,剩下的就是關於remove的操作了,思路是我們通過在呼叫dealloc的時候追加一個remove操作函式到裡面,這樣就可以保證當我們的class被dealloc的時候,訊息能被正確的remove掉。
1 2 3 4 5 6 |
// not use @selector(dealloc), because ARC [self aspect_hookSelector:NSSelectorFromString(@"dealloc") withOptions:AspectPositionBefore usingBlock:^(idinfo) { action(); } error:&error]; |
講到這裡,基本上已經實現了我們要講的一些功能,不過這裡其實有一個很大的坑,不知道同學們看到這裡已經發現了沒有,當我們新新增的函式已經存在時,我們會去在已存在的函式後面追加新的action操作,這個時候考慮到如果一個class中有註冊訊息的操作,但是這個class又被init了多次,那麼就會出現我們新新增的SEL中,會有好幾個重複的action操作,如果我們的action操作只是為了更新資料,那麼這樣的開銷幾乎沒有,但是如果我們的action中會有大量的關於重新整理介面或者其他UI的操作,其實是會額外附帶不少開銷的。所以這個就是上述實現的一個缺點,我現在也沒有什麼好的辦法來優化這個問題,只能在程式碼中儘量避免這種情況的發生,大家想到好的辦法也可以告訴我。
其實OC中的Observe Pattern可以分為三種:delegate,NSNotification,KVO,這裡詳細介紹了這三種方式的區別。準確來說對於model-view之間的資料同步,單純的資料更新來說KVO感覺更加適合一點,因為一旦它的資料發生變化,自動所有接收訊息的地方就能馬上收到通知。而NSNotification更佳適合模組間的一些介面互動更新的操作,因為它需要主動去傳送訊息讓所有註冊了訊息的地方都知道然後去執行對應的操作。所以針對上面提出的一些問題,我之後會在這個地方繼續補充,畢竟現在我還沒想出一個比較好的方案。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!