mac開發(fā)系列30:ServiceCenter和ExtensionCenter源碼理解

單例和delegate是oc最常用的兩種設計模式卢佣。其實從設計模式層面而言重荠,delegate叫做觀察者模式更為貼切。ServiceCenter和ExtensionCenter分別為了集中管理單例和delegate虚茶。
一戈鲁、ServiceCenter
微信中不少對象都是要面向全局提供service的,如:賬號相關的AccountService嘹叫、CDN相關的CdnComMgr婆殿、delegate管理相關的ExtensionCenter等等,頻繁地創(chuàng)建和銷毀這些對象顯然是不科學的罩扇,因此要實現(xiàn)成單例婆芦。但若就此滿足,這些單例創(chuàng)建方式不一喂饥,且散落在代碼各處消约,降低了代碼可讀性,還不利于銷毀并回收資源员帮。于是荆陆,參考簡單工廠模式的思想(新增一個工廠類來負責其他類的對象創(chuàng)建),即ServiceCenter集侯,統(tǒng)一的創(chuàng)建接口如下:

define GET_SERVICE(obj) ((obj*)[[MMServiceCenter defaultCenter] getService:[obj class]])

上述GET_SERVICE宏中被啼,getService方法實現(xiàn)及相關comment如下:
@interface MMServiceCenter : NSObject{ NSMutableDictionary *m_dicService; //存儲單例的字典。key:單例所屬的類棠枉,value:單例 NSRecursiveLock *m_lock;}@end

-(id) getService:(Class) cls{ [m_lock lock]; // 上鎖浓体,防止同時寫m_dicService id obj = [m_dicService objectForKey:cls]; //查找單例 if (obj == nil) //單例還沒創(chuàng)建 { // 單例所屬的類必須繼承MMService類,并遵循MMService協(xié)議 // 因為MMService類和協(xié)議聲明了辈讶,管理單例的方法和變量命浴,如: // 微信在前臺時,這些單例怎么處理;微信進入后臺生闲,它們又是什么狀態(tài)媳溺; // 微信退出后,單例是否還需要駐留內(nèi)存等等 if (![cls isSubclassOfClass:[MMService class]]) { [m_lock unlock]; return nil; } if (![cls conformsToProtocol:@protocol(MMService)]) { [m_lock unlock]; return nil; } // 創(chuàng)建單例并添加到字典中 obj = [[cls alloc] init]; [m_dicService safeSetObject:obj forKey:cls]; [m_lock unlock]; // 如果單例實現(xiàn)了MMService協(xié)議中的可選方法onServiceInit碍讯,則 // 可以用這個方法來做一些額外的初始化工作 if ([obj respondsToSelector:@selector(onServiceInit)]) { [obj onServiceInit]; } } else { [m_lock unlock]; } return obj;}

@interface MMService : NSObject // 單例通過設置這個屬性來決定自己在微信退出后悬蔽,是否繼續(xù)駐留內(nèi)存@property (assign) BOOL m_isServicePersistent;@end@protocol MMService<NSObject>@optional-(void) onServiceInit; // 單例創(chuàng)建后,做些額外的初始化工作-(void) onServiceClearData; // 微信退出登錄捉兴,回收資源蝎困。(其實真正刪除單例的是removeService,只不過這兩個方法常常一起被調(diào)用)// 如果m_isServicePersistent=YES倍啥,就不會調(diào)用removeService了@end

基本類圖結構如下:



下面以AccountService為例禾乘,闡述整體流程:
1、定義AccountService類虽缕,繼承MMService類始藕,并遵循MMService協(xié)議:



2、從ServiceCenter獲取AccountService單例氮趋,并調(diào)用所需方法:

3伍派、微信退出登錄,協(xié)議方法被調(diào)用來回收資源:



補充真正刪除單例凭峡,徹底回收資源的removeService方法:
-(void) removeService:(Class) cls{ [m_lock lock]; MMService<MMService>* obj = [m_dicService objectForKey:cls]; if (obj == nil) { [m_lock unlock]; return ; } [m_dicService safeRemoveObjectForKey:cls]; // 從字典中刪除,單例的引用計數(shù)變?yōu)?决记,觸發(fā)ARC調(diào)用單例的dealloc方法 [m_lock unlock];}

二摧冀、ExtensionCenter
我們知道,delegate是通過protocol來實現(xiàn)的:雙方遵循同一個協(xié)議系宫,一方實現(xiàn)協(xié)議中的方法(即觀察者)索昂,另一方調(diào)用協(xié)議中的方法。原理圖如下:



ExtensionCenter就是把這種delegate關系統(tǒng)一登記維護扩借、集中管理椒惨,并提供自己的調(diào)用方式。至于為啥叫ExtensionCenter就不知道了潮罪,這個Extension(ext)跟類擴展沒有半毛錢關系康谆,可以看做Protocol加強版。下面闡述整體實現(xiàn)流程及其原理:
1嫉到、登記沃暗,其實就是把“觀察者(obj)遵循協(xié)議(ext),并實現(xiàn)協(xié)議中的方法”這種關系用數(shù)據(jù)結構維護起來何恶。這樣孽锥,后續(xù)廣播事件通知(即調(diào)用協(xié)議方法),就可以找到對應的觀察者:
REGISTER_EXTENSION(IMessageExt, self);

define REGISTER_EXTENSION(ext, obj) \ { \ MMExtension *oExt = [GET_SERVICE(MMExtensionCenter) getExtension:@protocol(ext)]; \ if (oExt) { \ [oExt registerExtension:obj]; \ } \ }

   從REGISTER_EXTENSION宏可以看到,ExtensionCenter就是ServiceCenter管理的一個單例惜辑。主要存儲數(shù)據(jù)結構唬涧,以及宏實現(xiàn)里的兩個方法解析:

1)主要存儲數(shù)據(jù)結構:
// 方法描述struct objc_method_description { SEL name; // 方法名,即selector char *types; // 方法參數(shù)類型數(shù)組};

typedef Protocol *MMExtKey;@interface MMExtension : NSObject { MMExtKey m_extKey; // 協(xié)議 unsigned int m_methodCount; // 協(xié)議中方法個數(shù) struct objc_method_description *m_methods; // 協(xié)議方法數(shù)組 // key:協(xié)議方法名盛撑,value:MMExtensionObject(協(xié)議方法的實現(xiàn)者碎节,即觀察者)數(shù)組 MMExtensionDictionary *m_dicObserver;}@end@interface MMExtensionCenter : MMService <MMService> { // key:協(xié)議名,value:MMExtension NSMutableDictionary *m_dicExtension;}@end

// REGISTER_EXTENSION后撵彻,觀察者會被引用钓株;而觀察者dealloc時才會調(diào)用UNREGISTER_EXTENSION,// 但是因為被引用了陌僵,觀察者的dealloc不會被調(diào)到轴合,也就沒法調(diào)到UNREGISTER_EXTENSION,即造成相互// 引用碗短。為了解決相互引用受葛,使用如下觀察者封裝類MMExtensionObject@interface MMExtensionObject : NSObject { __unsafe_unretained id m_Obj; // 弱引用,不增加引用計數(shù)偎谁。這種套路在MMTimer中也有用到 BOOL m_deleteMark; // 標記刪除总滩,即取消登記}@property (nonatomic, assign) BOOL m_deleteMark;- (void)setObject:(id)Obj;- (id)getObject;- (BOOL)isObjectEqual:(id)Obj;@end

數(shù)據(jù)結構關系圖如下:



2)getExtension:

  • (MMExtension *)getExtension:(MMExtKey)oKey { NSString *key = NSStringFromProtocol(oKey); // 獲取協(xié)議名 MMExtension *ext = [m_dicExtension objectForKey:key]; // 查找協(xié)議名對應的MMExtension if (ext == nil) { // MMExtension還沒創(chuàng)建 // 創(chuàng)建協(xié)議對應的MMExtension,并添加到字典中 ext = [[MMExtension alloc] initWithKey:oKey]; [m_dicExtension safeSetObject:ext forKey:key]; } return ext;}

3)registerExtension:

  • (BOOL)registerExtension:(id)oObserver {// 觀察者沒有遵循協(xié)議巡雨,不能登記 if ([oObserver conformsToProtocol:m_extKey] == NO) { return NO; } if (m_dicObserver == nil) { m_dicObserver = [[MMExtensionDictionary alloc] init]; } Class cls = [oObserver class];// 遍歷協(xié)議中的所有方法 for (unsigned int index = 0; index < m_methodCount; index++) { objc_method_description *method = &m_methods[index]; // 觀察者實現(xiàn)了協(xié)議方法闰渔,就登記 if (class_respondsToSelector(cls, method->name)) { [m_dicObserver registerExtension:oObserver forKey:NSStringFromSelector(method->name)]; } } return YES;}

登記“觀察者實現(xiàn)了某個協(xié)議方法”:

  • (BOOL)registerExtension:(id)oObserver forKey:(id)nsKey { if (oObserver == nil || nsKey == nil) { return NO; }// 獲取一個協(xié)議方法的實現(xiàn)者數(shù)組
    NSMutableArray *selectorImplememters = [m_dic objectForKey:nsKey];// 該協(xié)議方法還沒有實現(xiàn)者,就創(chuàng)建一個空的實現(xiàn)者數(shù)組铐望,并添加到字典中 if (selectorImplememters == nil) { selectorImplememters = [[NSMutableArray alloc] init]; [m_dic safeSetObject:selectorImplememters forKey:nsKey]; }// 要登記的觀察者冈涧,已經(jīng)登記過了,直接返回NO for (MMExtensionObject *extObj in selectorImplememters) { if ([extObj isObjectEqual:oObserver]) { return NO; } }// 沒登記過正蛙,就登記 MMExtensionObject *extObj = [[MMExtensionObject alloc] initWithObject:oObserver]; [selectorImplememters safeAddObject:extObj]; return YES;}

2督弓、向觀察者廣播事件通知,即調(diào)用一個協(xié)議方法乒验,所有實現(xiàn)了該協(xié)議方法的觀察者都會收到通知愚隧,其核心就是查找一個協(xié)議方法的所有觀察者,然后調(diào)用它實現(xiàn)的協(xié)議方法(只要理解了上面的數(shù)據(jù)結構關系圖锻全,就很簡單了):
SAFECALL_EXTENSION(IMessageExt, @selector(onAddMsg:msgData:), onAddMsg : nsChatName msgData : msgData);

define SAFECALL_EXTENSION(ext, sel, func) \ {// 對于LAZY_REGISTER_EXTENSION狂塘,只是登記了類名;SAFECALL_EXTENSION時鳄厌, // 要根據(jù)類名睹耐,通過ServiceCenter,創(chuàng)建對應的觀察者obj單例 \ [GET_SERVICE(LazyExtensionAgent) ensureLazyListenerInitedForExtension:@protocol(ext) withSelector:sel]; \ MMExtension *oExt = [GET_SERVICE(MMExtensionCenter) getExtension:@protocol(ext)]; \ if (oExt) { \ NSArray *ary = [oExt getExtensionListForSelector:sel]; \ for (UInt32 index = 0; index < ary.count; index++) { \ MMExtensionObject *obj = [ary objectAtIndex:index]; \ if (obj.m_deleteMark == YES) continue; \ id oExtObj = [obj getObject]; \ { [oExtObj func]; } \ } \ } \ }

下面給出LAZY_REGISTER_EXTENSION的解析:

define LAZY_REGISTER_EXTENSION(ext, cls) \ { [GET_SERVICE(LazyExtensionAgent) registerLazyListener:[cls class] onExtension:@protocol(ext)]; }

  • (void)registerLazyListener:(Class)cls onExtension:(MMExtKey)extKey { if (cls == NULL || extKey == nil) { return; } if (![cls conformsToProtocol:extKey]) { return; } // 根據(jù)協(xié)議名查找協(xié)議的監(jiān)聽者(其實就是實現(xiàn)者部翘、觀察者) NSString *nsExtKey = NSStringFromProtocol(extKey); NSMutableDictionary *dicListeners = [m_dicExtensions objectForKey:nsExtKey]; if (dicListeners == nil) {// 該協(xié)議還沒有監(jiān)聽者硝训,就創(chuàng)建一個監(jiān)聽者空數(shù)組,添加到字典中 dicListeners = [[NSMutableDictionary alloc] init]; [m_dicExtensions safeSetObject:dicListeners forKey:nsExtKey]; } // 可選協(xié)議方法登記 unsigned int count = 0; objc_method_description *optionalMethods = protocol_copyMethodDescriptionList(extKey, NO, YES, &count); if (optionalMethods && count > 0) { [self addListener:cls toDic:dicListeners forMethods:optionalMethods methodCount:count]; free(optionalMethods); } // 必選協(xié)議方法登記 count = 0; objc_method_description *requiredMethods = protocol_copyMethodDescriptionList(extKey, YES, YES, &count); if (requiredMethods && count > 0) { [self addListener:cls toDic:dicListeners forMethods:requiredMethods methodCount:count]; free(requiredMethods); }}

  • (void)addListener:(Class)cls toDic:(NSMutableDictionary *)dicListeners forMethods:(objc_method_description *)arrMethods methodCount:(unsigned int)count { for (unsigned int index = 0; index < count; index++) { objc_method_description method = arrMethods[index]; if (class_respondsToSelector(cls, method.name)) { NSString *nsSelector = NSStringFromSelector(method.name); NSMutableSet *selectorImplememters = [dicListeners objectForKey:nsSelector]; if (selectorImplememters == nil) { selectorImplememters = [[NSMutableSet alloc] init]; [dicListeners safeSetObject:selectorImplememters forKey:nsSelector]; }// 以類名(而非obj)作為實現(xiàn)者來登記 [selectorImplememters safeAddObject:NSStringFromClass(cls)]; } }}

根據(jù)LAZY_REGISTER_EXTENSION登記的類名,通過ServiceCenter創(chuàng)建類名對應的單例obj:

  • (void)ensureLazyListenerInitedForExtension:(MMExtKey)extKey withSelector:(SEL)selector { if (extKey == nil || selector == NULL) { return; } NSMutableDictionary *dicListeners = [m_dicExtensions objectForKey:NSStringFromProtocol(extKey)]; if (dicListeners != nil) { NSMutableSet *selectorImplememters = [dicListeners objectForKey:NSStringFromSelector(selector)]; if (selectorImplememters != nil) { for (NSString *nsClassName in selectorImplememters) {// ServiceCenter根據(jù)類名創(chuàng)建對應的觀察者obj [[MMServiceCenter defaultCenter] getService:NSClassFromString(nsClassName)]; } } }}

3窖梁、取消登記:
UNREGISTER_EXTENSION(IMessageExt, self);

define UNREGISTER_EXTENSION(ext, obj) \ { \ MMExtension *oExt = [GET_SERVICE(MMExtensionCenter) getExtension:@protocol(ext)]; \ if (oExt) { \ [oExt unregisterExtension:obj]; \ } \ }

UNREGISTER_EXTENSION宏實現(xiàn)里的unregisterExtension方法解析如下:

  • (void)unregisterExtension:(id)oObserver { BOOL bFound = [m_dicObserver unregisterKeyExtension:oObserver];}- (BOOL)unregisterKeyExtension:(id)oObserver { BOOL bFound = NO;// 遍歷一個協(xié)議所有的方法 for (NSArray *selectorImplememters in [m_dic allValues]) {// 遍歷一個方法所有的觀察者 for (MMExtensionObject *extObj in selectorImplememters) { if ([extObj isObjectEqual:oObserver]) { extObj.m_deleteMark = YES; // 只是標記刪除 bFound = YES; break; } } } return bFound;}
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赘风,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子纵刘,更是在濱河造成了極大的恐慌邀窃,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件假哎,死亡現(xiàn)場離奇詭異瞬捕,居然都是意外死亡,警方通過查閱死者的電腦和手機舵抹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門肪虎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人惧蛹,你說我怎么就攤上這事扇救。” “怎么了香嗓?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵迅腔,是天一觀的道長。 經(jīng)常有香客問我靠娱,道長沧烈,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任像云,我火速辦了婚禮锌雀,結果婚禮上,老公的妹妹穿的比我還像新娘苫费。我一直安慰自己汤锨,他們只是感情好双抽,可當我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布百框。 她就那樣靜靜地躺著,像睡著了一般牍汹。 火紅的嫁衣襯著肌膚如雪铐维。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天慎菲,我揣著相機與錄音嫁蛇,去河邊找鬼。 笑死露该,一個胖子當著我的面吹牛睬棚,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼抑党,長吁一口氣:“原來是場噩夢啊……” “哼包警!你這毒婦竟也來了?” 一聲冷哼從身側響起底靠,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤害晦,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后暑中,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體壹瘟,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年鳄逾,在試婚紗的時候發(fā)現(xiàn)自己被綠了稻轨。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡严衬,死狀恐怖澄者,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情请琳,我是刑警寧澤粱挡,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站俄精,受9級特大地震影響询筏,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竖慧,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一嫌套、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧圾旨,春花似錦踱讨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至廓鞠,卻和暖如春帚稠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背床佳。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工滋早, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人砌们。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓杆麸,卻偏偏與公主長得像搁进,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子昔头,可洞房花燭夜當晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容

  • 終于把前面的base文件夾簡簡單單的看了一遍拷获,終于可以回到正片上來了,保證不爛尾减细。 項目天天用yymodel解析數(shù)...
    充滿活力的早晨閱讀 1,358評論 1 0
  • 轉至元數(shù)據(jù)結尾創(chuàng)建: 董瀟偉匆瓜,最新修改于: 十二月 23, 2016 轉至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • Objective-C語言是一門動態(tài)語言,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了運行時來處理未蝌。這種動態(tài)語言的...
    有一種再見叫青春閱讀 577評論 0 3
  • 原文出處:南峰子的技術博客 Objective-C語言是一門動態(tài)語言驮吱,它將很多靜態(tài)語言在編譯和鏈接時期做的事放到了...
    _燴面_閱讀 1,215評論 1 5
  • javascript Array 1. Properties Array.length var arr = ["a...
    echo_me閱讀 167評論 0 0