原創(chuàng)文章轉(zhuǎn)載請注明出處,謝謝
端午想好好休息一下,于是就沒出去玩了
關(guān)于NSNotification大家一定不會陌生,所以這里我就不講關(guān)于NSNotification的具體用法了,網(wǎng)上一搜一大片。NSNotification是基于Observe Design Pattern來實(shí)現(xiàn)的,所有Register了這個消息的對象,都會收到由NSNotification發(fā)送的消息。它有一個自己的消息中心是一個單例,我們基本通過以下三個函數(shù)就可以實(shí)現(xiàn)一個基本的Observe Pattern。
// 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;
但是系統(tǒng)的NSNotification實(shí)在是太臃腫了,想想要在所有的業(yè)務(wù)里到處散布著上面三行代碼,這顯然是不合適的。我們最理想的效果是只要存在兩個方法register和post,register中可以直接傳遞一個block來完成收到消息時觸發(fā)的事件,然后在dealloc中自動調(diào)用remove的操作。所以我們不妨先定義一下這兩個函數(shù):
+ (void)postNotificationNamed:(NSString*)notificationName
object:(id)object;
// default one parame in callback
+ (void)registerNotificationHandlerForName:(NSString *)notificationName
target:(id)target
action:(void(^)(id))action;
其實(shí)我們主要的任務(wù)就是要在register函數(shù)中去動態(tài)在相應(yīng)的target中添加一個函數(shù),來執(zhí)行對應(yīng)的callback操作。
@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:^(id<AspectInfo>info, 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];
}
上面的思想其實(shí)就是當(dāng)我們?nèi)プ砸粋€消息的時候,我們先去判斷目標(biāo)class中是否已經(jīng)添加了這個SEL,如果沒有,我們就去動態(tài)添加這個方法到目標(biāo)class,如果有了,我們就在已存在的方法中追加這個方法。最后把新創(chuàng)建的方法注冊到消息中。大致的思路就是這樣,剩下的就是關(guān)于remove的操作了,思路是我們通過在調(diào)用dealloc的時候追加一個remove操作函數(shù)到里面,這樣就可以保證當(dāng)我們的class被dealloc的時候,消息能被正確的remove掉。
// not use @selector(dealloc), because ARC
[self aspect_hookSelector:NSSelectorFromString(@"dealloc")
withOptions:AspectPositionBefore
usingBlock:^(id<AspectInfo>info) {
action();
} error:&error];
講到這里,基本上已經(jīng)實(shí)現(xiàn)了我們要講的一些功能,不過這里其實(shí)有一個很大的坑,不知道同學(xué)們看到這里已經(jīng)發(fā)現(xiàn)了沒有,當(dāng)我們新添加的函數(shù)已經(jīng)存在時,我們會去在已存在的函數(shù)后面追加新的action操作,這個時候考慮到如果一個class中有注冊消息的操作,但是這個class又被init了多次,那么就會出現(xiàn)我們新添加的SEL中,會有好幾個重復(fù)的action操作,如果我們的action操作只是為了更新數(shù)據(jù),那么這樣的開銷幾乎沒有,但是如果我們的action中會有大量的關(guān)于刷新界面或者其他UI的操作,其實(shí)是會額外附帶不少開銷的。所以這個就是上述實(shí)現(xiàn)的一個缺點(diǎn),我現(xiàn)在也沒有什么好的辦法來優(yōu)化這個問題,只能在代碼中盡量避免這種情況的發(fā)生,大家想到好的辦法也可以告訴我。
其實(shí)OC中的Observe Pattern可以分為三種:delegate,NSNotification,KVO,這里詳細(xì)介紹了這三種方式的區(qū)別。準(zhǔn)確來說對于model-view之間的數(shù)據(jù)同步,單純的數(shù)據(jù)更新來說KVO感覺更加適合一點(diǎn),因?yàn)橐坏┧臄?shù)據(jù)發(fā)生變化,自動所有接收消息的地方就能馬上收到通知。而NSNotification更佳適合模塊間的一些界面交互更新的操作,因?yàn)樗枰鲃尤グl(fā)送消息讓所有注冊了消息的地方都知道然后去執(zhí)行對應(yīng)的操作。所以針對上面提出的一些問題,我之后會在這個地方繼續(xù)補(bǔ)充,畢竟現(xiàn)在我還沒想出一個比較好的方案。