BeeHive 框架实现原理分析

BeeHive是什么?
BeeHive是阿里提出的在iOS平台上实现模块化编程的一套实现方案。模块化、组件化是目前在客户端发展的热点方向之一,在业务快速变化、迭代的今天,模块化编程可以有效的降低代码的耦合性,当开发人员面对业务代码不断臃肿的App而烦恼时,模块化编程为其指出了一条新的道路。从BeeHive开源的代码来看,BeeHive的代码显得非常精简,甚至可以说有些简陋,但其却提供了一种极其简化的模块设计方法,正所谓“重剑无锋,大巧不工”,在代码日益复杂的今天,回归简单提供了一种新的想法。
Module与Service
在BeeHive当中最重要的两个概念是Module 与Service,对应成中文可以翻译成“模块”与“服务”。Module是有生命周期的,其调用的时机是由事件来触发的,事件包括了系统事件,通用事件以及自定义事件。对于Module而言,BeeHive实现了队列化回调Module方法,开发人员可以给某个系统事件注册多个Module,BeeHive会依次调用不同的Module处理方法。Service则是单纯的代码实现模块,可以是实现某个功能的代码或业务代码,Service是没有生命周期的,在任何时候都可以调用。BeeHive借鉴了Spring的Service设计思想,Module与Service需要对接口进行抽象,通过接口来关联两个不同的模块,从而最大的降低代码的耦合性。

Paste_Image.png

模块调用关系
Module与Service的设计可以覆盖绝大多数的客户端的开发场景,在某个事件触发的时候进行某些处理,或者完全由用户触发某个功能调用。例如,在BeeHive当中实现了AppDelgate回调接口的Module化。AppDelgate是整个系统回调入口,在传统的App开发当中,需要在该类当中不同的回调函数写入大量的处理方法,例如像推送的注册、整个应用资源的初始化等,这会导致AppDelgate当中的代码非常臃肿。AppDelgate的Module化则彻底改变了这些,开发员只需要实现BHModuleProtocol协议,在相应的回调接口当中实现相应的处理。例如,可以实现一个推送注册的APNsModule了,该APNsModule只实现了推送功能的注册。开发人员只需要将Module注册即可,目前BeeHive支持文件形式的Module注册及宏定义的注册方式。在未来如果需要对推送进行升级时,只需要直接替换这个APNsModule类即可,对AppDelgate内部的代码不需要做任何修改。
下面是AppDelgate Module化的事件触发机制,在AppDelegate的回调接口didLaunch当中会依次调用Module 的ModSetup、ModInit以及ModSplash方法。


BeeHiveMod事件驱动

BeeHive实现原理
BeeHive提供了一个BHAppDelegate类,使用BeeHive必须继承该BHAppDelgate类,自己创建一个子类,在BeeHive创建的示例代码当中,子类需要重写

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions

方法,如下所示

- (BOOL)application:(UIApplication*)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
[BHContextshareInstance].application= application;
[BHContextshareInstance].launchOptions= launchOptions;
[BHContextshareInstance].moduleConfigName=@"BeeHive.bundle/BeeHive";//可选,默认为BeeHive.bundle/BeeHive.plist
[BHContextshareInstance].serviceConfigName=@"BeeHive.bundle/BHService";
[BeeHiveshareInstance].enableExpection=YES;
[[BeeHiveshareInstance]setContext:[BHContextshareInstance]];
[[BHTimeProfilersharedTimeProfiler]recordEventTime:@"BeeHive::super start launch"];
[superapplication:applicationdidFinishLaunchingWithOptions:launchOptions];
id homeVc = [[BeeHiveshareInstance]createService:@protocol(HomeServiceProtocol)];
if([homeVcisKindOfClass:[UIViewControllerclass]]) {
UINavigationController*navCtrl = [[UINavigationControlleralloc]initWithRootViewController:(UIViewController*)homeVc];
navCtrl.navigationBarHidden=YES;
[homeVcsetIsNavbarHidden:YES];
self.window= [[UIWindowalloc]initWithFrame:[UIScreenmainScreen].bounds];
self.window.rootViewController= navCtrl;
[self.windowmakeKeyAndVisible];
}
returnYES;
}

在[superapplication:applicationdidFinishLaunchingWithOptions:launchOptions];前面这些是实现BeeHive初始化的核心代码。BHContext提供了上下文的环境参数,系统的回调接口可以通过BHContext对象来获取相关的参数。开始几行代码是初始化context几个基本的环境变量。其定义了module与service初始化的文件路径。BeeHive是实现Module与Service注册的核心类。其内部实现的逻辑会分别从文件及宏定义当中读取Module及Service的映射关系,代码如下所示:

-(void)setContext:(BHContext*)context
{
_context= context;
staticdispatch_once_tonceToken;
dispatch_once(&onceToken, ^{
[selfloadStaticServices];
[selfloadStaticModules];
});
}

文件注册Module及Service的示例如下:

Module文件注册

Service文件注册

Module宏注册

Service宏注册

文件注册的方法相对比较简单,宏定义注册则看起来有点迷糊,下面来分析一下其中的原理。打开宏定义可以看到下面的代码:

#ifndef BeehiveModSectName
#define BeehiveModSectName"BeehiveMods"
#endif
#ifndef BeehiveServiceSectName
#define BeehiveServiceSectName"BeehiveServices"
#endif
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
#define BeeHiveMod(name) \
char * k##name##_mod BeeHiveDATA(BeehiveMods) =""#name"";
#define BeeHiveService(servicename,impl) \
char * k##servicename##_service BeeHiveDATA(BeehiveServices) ="{ \""#servicename"\" : \""#impl"\"}";
</pre>
宏会定义一个全局的变量,并将其放入在__DATA 的对应的sectname当中,Module会放在BeehiveMods当中,Service会放在BeehiveServices当中。在代码BHAnnotation.m文件当中
<pre>
staticNSArray* BHReadConfiguration(char*section)
{
NSMutableArray*configs = [NSMutableArrayarray];
Dl_infoinfo;
dladdr(BHReadConfiguration, &info);
#ifndef __LP64__
//const struct mach_header *mhp = _dyld_get_image_header(0); // both works as below line
conststructmach_header *mhp = (structmach_header*)info.dli_fbase;
unsignedlongsize =0;
uint32_t *memory = (uint32_t*)getsectiondata(mhp,"__DATA", section, & size);
#else/* defined(__LP64__) */
conststructmach_header_64*mhp = (structmach_header_64*)info.dli_fbase;
unsignedlongsize =0;
uint64_t*memory = (uint64_t*)getsectiondata(mhp,"__DATA", section, & size);
#endif/* defined(__LP64__) */
for(intidx =0; idx < size/sizeof(void*); ++idx){
char*string = (char*)memory[idx];
NSString*str = [NSStringstringWithUTF8String:string];
if(!str)continue;
BHLog(@"config = %@", str);
if(str) [configsaddObject:str];
}
returnconfigs;
}

该段代码会读取传入的sectname段的数据,根据其中的内容找到Module及service的对应关系。可以用otool查看可执行文件数据段的内容,如下所示,其中有两个char*指针,各占8字节。

BeeHiveMods section内容

BeeHive带来的启发
客户端在公司业务发展的过程中体积越来越庞大,其中堆叠了大量的业务逻辑代码,不同业务模块的代码相互调用,相互嵌套,代码之间的耦合性越来越高,调用逻辑会越来越混乱。当某个模块需要升级的时候,往往会有牵一发而动全身的感觉。特别是如果工程最初设计的时候没有考虑的接口的封装,而将大量的业务代码与功能模块代码混在一起时,将来的升级往往需要手动对代码的进行修改及调整,带来的工作量是非常巨大的。模块化、组件化可以将代码的功能逻辑尽量封装在一起,对外只提供接口,业务逻辑代码与功能模块通过接口进行弱耦合。这样在未来替换模块时,只需要动态注册接口与类的对应关系即可。BeeHive提供了很好的设计思路。另外由于我在公司属于基础框架组,为其它项目组提供基础功能组件使用。其它项目组在使用时,往往将直接将业务代码堆叠在我们框架的代码上,导致在升级上非常麻烦,经常需要手动升级。BeeHive的Module设计是一种非常好的想法,我可以自定义一些事件,对一些组件的执行逻辑像AppDelgate一样进行改造,例如像webView,功能逻辑以Module进行注册使用,这样可以将功能Module化,对其它项目组的使用更加规范。

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

推荐阅读更多精彩内容