mac开发系列30:ServiceCenter和ExtensionCenter源码理解

单例和delegate是oc最常用的两种设计模式。其实从设计模式层面而言,delegate叫做观察者模式更为贴切。ServiceCenter和ExtensionCenter分别为了集中管理单例和delegate。
一、ServiceCenter
微信中不少对象都是要面向全局提供service的,如:账号相关的AccountService、CDN相关的CdnComMgr、delegate管理相关的ExtensionCenter等等,频繁地创建和销毁这些对象显然是不科学的,因此要实现成单例。但若就此满足,这些单例创建方式不一,且散落在代码各处,降低了代码可读性,还不利于销毁并回收资源。于是,参考简单工厂模式的思想(新增一个工厂类来负责其他类的对象创建),即ServiceCenter,统一的创建接口如下:

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

上述GET_SERVICE宏中,getService方法实现及相关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) //单例还没创建 { // 单例所属的类必须继承MMService类,并遵循MMService协议 // 因为MMService类和协议声明了,管理单例的方法和变量,如: // 微信在前台时,这些单例怎么处理;微信进入后台,它们又是什么状态; // 微信退出后,单例是否还需要驻留内存等等 if (![cls isSubclassOfClass:[MMService class]]) { [m_lock unlock]; return nil; } if (![cls conformsToProtocol:@protocol(MMService)]) { [m_lock unlock]; return nil; } // 创建单例并添加到字典中 obj = [[cls alloc] init]; [m_dicService safeSetObject:obj forKey:cls]; [m_lock unlock]; // 如果单例实现了MMService协议中的可选方法onServiceInit,则 // 可以用这个方法来做一些额外的初始化工作 if ([obj respondsToSelector:@selector(onServiceInit)]) { [obj onServiceInit]; } } else { [m_lock unlock]; } return obj;}

@interface MMService : NSObject // 单例通过设置这个属性来决定自己在微信退出后,是否继续驻留内存@property (assign) BOOL m_isServicePersistent;@end@protocol MMService<NSObject>@optional-(void) onServiceInit; // 单例创建后,做些额外的初始化工作-(void) onServiceClearData; // 微信退出登录,回收资源。(其实真正删除单例的是removeService,只不过这两个方法常常一起被调用)// 如果m_isServicePersistent=YES,就不会调用removeService了@end

基本类图结构如下:



下面以AccountService为例,阐述整体流程:
1、定义AccountService类,继承MMService类,并遵循MMService协议:



2、从ServiceCenter获取AccountService单例,并调用所需方法:

3、微信退出登录,协议方法被调用来回收资源:



补充真正删除单例,彻底回收资源的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]; // 从字典中删除,单例的引用计数变为0,触发ARC调用单例的dealloc方法 [m_lock unlock];}

二、ExtensionCenter
我们知道,delegate是通过protocol来实现的:双方遵循同一个协议,一方实现协议中的方法(即观察者),另一方调用协议中的方法。原理图如下:



ExtensionCenter就是把这种delegate关系统一登记维护、集中管理,并提供自己的调用方式。至于为啥叫ExtensionCenter就不知道了,这个Extension(ext)跟类扩展没有半毛钱关系,可以看做Protocol加强版。下面阐述整体实现流程及其原理:
1、登记,其实就是把“观察者(obj)遵循协议(ext),并实现协议中的方法”这种关系用数据结构维护起来。这样,后续广播事件通知(即调用协议方法),就可以找到对应的观察者:
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管理的一个单例。主要存储数据结构,以及宏实现里的两个方法解析:

1)主要存储数据结构:
// 方法描述struct objc_method_description { SEL name; // 方法名,即selector char *types; // 方法参数类型数组};

typedef Protocol *MMExtKey;@interface MMExtension : NSObject { MMExtKey m_extKey; // 协议 unsigned int m_methodCount; // 协议中方法个数 struct objc_method_description *m_methods; // 协议方法数组 // key:协议方法名,value:MMExtensionObject(协议方法的实现者,即观察者)数组 MMExtensionDictionary *m_dicObserver;}@end@interface MMExtensionCenter : MMService <MMService> { // key:协议名,value:MMExtension NSMutableDictionary *m_dicExtension;}@end

// REGISTER_EXTENSION后,观察者会被引用;而观察者dealloc时才会调用UNREGISTER_EXTENSION,// 但是因为被引用了,观察者的dealloc不会被调到,也就没法调到UNREGISTER_EXTENSION,即造成相互// 引用。为了解决相互引用,使用如下观察者封装类MMExtensionObject@interface MMExtensionObject : NSObject { __unsafe_unretained id m_Obj; // 弱引用,不增加引用计数。这种套路在MMTimer中也有用到 BOOL m_deleteMark; // 标记删除,即取消登记}@property (nonatomic, assign) BOOL m_deleteMark;- (void)setObject:(id)Obj;- (id)getObject;- (BOOL)isObjectEqual:(id)Obj;@end

数据结构关系图如下:



2)getExtension:

  • (MMExtension *)getExtension:(MMExtKey)oKey { NSString *key = NSStringFromProtocol(oKey); // 获取协议名 MMExtension *ext = [m_dicExtension objectForKey:key]; // 查找协议名对应的MMExtension if (ext == nil) { // MMExtension还没创建 // 创建协议对应的MMExtension,并添加到字典中 ext = [[MMExtension alloc] initWithKey:oKey]; [m_dicExtension safeSetObject:ext forKey:key]; } return ext;}

3)registerExtension:

  • (BOOL)registerExtension:(id)oObserver {// 观察者没有遵循协议,不能登记 if ([oObserver conformsToProtocol:m_extKey] == NO) { return NO; } if (m_dicObserver == nil) { m_dicObserver = [[MMExtensionDictionary alloc] init]; } Class cls = [oObserver class];// 遍历协议中的所有方法 for (unsigned int index = 0; index < m_methodCount; index++) { objc_method_description *method = &m_methods[index]; // 观察者实现了协议方法,就登记 if (class_respondsToSelector(cls, method->name)) { [m_dicObserver registerExtension:oObserver forKey:NSStringFromSelector(method->name)]; } } return YES;}

登记“观察者实现了某个协议方法”:

  • (BOOL)registerExtension:(id)oObserver forKey:(id)nsKey { if (oObserver == nil || nsKey == nil) { return NO; }// 获取一个协议方法的实现者数组
    NSMutableArray *selectorImplememters = [m_dic objectForKey:nsKey];// 该协议方法还没有实现者,就创建一个空的实现者数组,并添加到字典中 if (selectorImplememters == nil) { selectorImplememters = [[NSMutableArray alloc] init]; [m_dic safeSetObject:selectorImplememters forKey:nsKey]; }// 要登记的观察者,已经登记过了,直接返回NO for (MMExtensionObject *extObj in selectorImplememters) { if ([extObj isObjectEqual:oObserver]) { return NO; } }// 没登记过,就登记 MMExtensionObject *extObj = [[MMExtensionObject alloc] initWithObject:oObserver]; [selectorImplememters safeAddObject:extObj]; return YES;}

2、向观察者广播事件通知,即调用一个协议方法,所有实现了该协议方法的观察者都会收到通知,其核心就是查找一个协议方法的所有观察者,然后调用它实现的协议方法(只要理解了上面的数据结构关系图,就很简单了):
SAFECALL_EXTENSION(IMessageExt, @selector(onAddMsg:msgData:), onAddMsg : nsChatName msgData : msgData);

define SAFECALL_EXTENSION(ext, sel, func) \ {// 对于LAZY_REGISTER_EXTENSION,只是登记了类名;SAFECALL_EXTENSION时, // 要根据类名,通过ServiceCenter,创建对应的观察者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; } // 根据协议名查找协议的监听者(其实就是实现者、观察者) NSString *nsExtKey = NSStringFromProtocol(extKey); NSMutableDictionary *dicListeners = [m_dicExtensions objectForKey:nsExtKey]; if (dicListeners == nil) {// 该协议还没有监听者,就创建一个监听者空数组,添加到字典中 dicListeners = [[NSMutableDictionary alloc] init]; [m_dicExtensions safeSetObject:dicListeners forKey:nsExtKey]; } // 可选协议方法登记 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); } // 必选协议方法登记 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)作为实现者来登记 [selectorImplememters safeAddObject:NSStringFromClass(cls)]; } }}

根据LAZY_REGISTER_EXTENSION登记的类名,通过ServiceCenter创建类名对应的单例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根据类名创建对应的观察者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宏实现里的unregisterExtension方法解析如下:

  • (void)unregisterExtension:(id)oObserver { BOOL bFound = [m_dicObserver unregisterKeyExtension:oObserver];}- (BOOL)unregisterKeyExtension:(id)oObserver { BOOL bFound = NO;// 遍历一个协议所有的方法 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;}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • 终于把前面的base文件夹简简单单的看了一遍,终于可以回到正片上来了,保证不烂尾。 项目天天用yymodel解析数...
    充满活力的早晨阅读 1,355评论 1 0
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,678评论 0 9
  • Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了运行时来处理。这种动态语言的...
    有一种再见叫青春阅读 574评论 0 3
  • 原文出处:南峰子的技术博客 Objective-C语言是一门动态语言,它将很多静态语言在编译和链接时期做的事放到了...
    _烩面_阅读 1,214评论 1 5
  • javascript Array 1. Properties Array.length var arr = ["a...
    echo_me阅读 166评论 0 0