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需要对接口进行抽象,通过接口来关联两个不同的模块,从而最大的降低代码的耦合性。
模块调用关系
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化,对其它项目组的使用更加规范。