前年的时候参与了项目的组件化改造,在系统事件和全局事件分发的场景下,我们使用了BeeHive,这里分析一下BeeHive(欠的历史债)!!!
在解析三方库之前,首先要弄清楚这个是做什么的,该怎么用?
ViewController/View级解耦
使用
在做组件化之前,我们的项目里各个模块不分彼此的相互调用,渐渐的就发展成下图:
BeeHive
通过Protocol
的方式来解决依赖,正常情况下我们从A
页面跳转至B
页面,会在A
页面里直接引用B
页面,如下图左侧的引用关系。而在BeeHive
的思想里是把B页面对外公开的方法抽象出接口BServiceProtocol
,BViewController
来实现这个接口,从而使外界本该依赖BViewController的页面通过引用BServiceProtocol
就能达到相同的效果,如下图右侧。
遵循这个思想之后每个Modlue
都可以抽出自己的ModuleServiceProtocol
,然后将他们统一下沉在ServiceProtocol
层中,Modlue
之间不直接交互,而是通过ServiceProtocol
进行交互,典型的面向接口编程,符合依赖倒置的思想。
具体使用如下:
创建BServiceProtocol
// BServiceProtocol
@protocol BServiceProtocol <NSObject, BHServiceProtocol>
- (void)setParam:(NSDictionary *)param;
@end
创建BViewController
实现BServiceProtocol
@interface BViewController : UIViewController <BServiceProtocol>
@end
@implementation BViewController
- (void)setParam:(NSDictionary *)param {
// do ...
}
@end
在BViewController
所在module
里注册:
[[BeeHive shareInstance] registerService:@protocol(BServiceProtocol) service:[BViewController class]];
在AViewController
里就可以直接使用:
id <BServiceProtocol> bViewController = [[BeeHive shareInstance] createService:@protocol(BServiceProtocol)];
[bViewController setParam:@{@"name":@"gong"}];
实现
- (void)registerService:(Protocol *)service implClass:(Class)implClass {
// 检查入参是否为空
NSParameterAssert(service != nil);
NSParameterAssert(implClass != nil);
// 检查传入的类是否遵循了service协议
if (![implClass conformsToProtocol:service]) {
if (self.enableException) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
}
return;
}
// 检查是否已经注册了service协议,如果注册过则抛出异常
if ([self checkValidService:service]) {
if (self.enableException) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
}
return;
}
// 将协议和类都转化成String进行保存。
NSString *key = NSStringFromProtocol(service);
NSString *value = NSStringFromClass(implClass);
if (key.length > 0 && value.length > 0) {
[self.lock lock];
[self.allServicesDict addEntriesFromDictionary:@{key:value}];
[self.lock unlock];
}
}
这块逻辑比较简单,需要注意的是每个协议只能对应一个实现类,这里使用了线程锁来保证线程安全,一般在单例类里保证简单数据的线程安全的场景下,我会使用串行队列去实现。
- (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache {
// 检查入参是否为空
if (!serviceName.length) {
serviceName = NSStringFromProtocol(service);
}
id implInstance = nil;
// 检查是否已经注册了service协议,如果没有则跑出异常
if (![self checkValidService:service]) {
if (self.enableException) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol does not been registed", NSStringFromProtocol(service)] userInfo:nil];
}
}
NSString *serviceStr = serviceName;
// 如果设置了缓存,则从缓存中读取
if (shouldCache) {
id protocolImpl = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];
if (protocolImpl) {
return protocolImpl;
}
}
// 通过service获取Class类
Class implClass = [self serviceImplClass:service];
// 如果BViewController实现了singleton方法则将其创建为单例
if ([[implClass class] respondsToSelector:@selector(singleton)]) {
if ([[implClass class] singleton]) {
if ([[implClass class] respondsToSelector:@selector(shareInstance)])
implInstance = [[implClass class] shareInstance];
else
implInstance = [[implClass alloc] init];
// 如果设置了缓存则将这个实例进行保存
if (shouldCache) {
[[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
return implInstance;
} else {
return implInstance;
}
}
}
return [[implClass alloc] init];
}
不建议使用三方库的缓存逻辑,最好自己去管理这个生成的实例的生命周期。
系统事件的分发
进行组件化之前,application
的生命周期事件可以直接供各个业务模块使用,组件化之后,业务模块已经被拆出为独立的pod
组件,使用application
的生命周期事件是一件挺麻烦的事。BeeHive
提供了一个比较方便的系统事件分发和订阅的体系。
使用
将main
方法里的AppDelegate
替换为BHAppDelegate
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([BHAppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
在自己的Pod
组件里创建module
订阅事件,首先要遵循BHModuleProtocol
协议,然后在.m
的implementation
中添加代码BH_EXPORT_MODULE()
,接下来就按自己的需要去订阅相应的事件,下侧我订阅的是闪屏页。
@interface GAModule : NSObject <BHModuleProtocol>
@end
@implementation GAModule
BH_EXPORT_MODULE()
- (void)modSplash:(BHContext *)context {
NSLog(@"modSplash");
}
@end
实现
上面例子创建的GAModule
和BeeHive
的关系如下图,我们将GAModule
通过registerModule
注册给BHModuleManager
,BHAppDelegate
负责接收Application
的生命周期消息,当有回调回来时会告诉BHModuleManager触发了某个事件,BHModuleManager
会触发我们在GAModule
里订阅的消息。
具体的操作流程如下:
首先BHModuleManager
会维护一个字典,其key
为事件的类型,value
为注册该事件的BHModule
对象集合,BHModule
记录着等级和该Module
的类名。外界通过registerDynamicModule
进行注册,当相应事件被触发时会调用triggerEvent
方法,在获取相应事件的集合后,分别通过performSelector:withObject:
方法触发。
BHAnnotation
BHAnnotation
中提供了宏的方式供使用者简单的注册。其是在编译阶段,将数据存储在特殊的section
中,启动APP
时在main
函数之前从section
中取出之前的数据进行注册。
存数据
#ifndef BeehiveModSectName
#define BeehiveModSectName "BeehiveMods"
#endif
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";
__attribute((used, section("__DATA,"#sectname" ")))
其作用是在编译阶段将作用的函数或数据放入指定名为"BeehiveMods"
对应的段中。
注册取数据的时机
__attribute__((constructor))
void initProphet() {
_dyld_register_func_for_add_image(dyld_callback);
}
使用constructor
修饰后的initProphet
函数在+load
之后main()
函数之前调用。接着使用_dyld_register_func_for_add_image
来注册dyld_callback
,当dyld链接符号时,会调用dyld_callback
,这样做是为了在dyld_callback
中获取mach_header
。
取数据并注册服务
NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp);
static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide) {
// 获取所有的modName数据
NSArray *mods = BHReadConfiguration(BeehiveModSectName, mhp);
for (NSString *modName in mods) {
Class cls;
if (modName) {
cls = NSClassFromString(modName);
if (cls) {
// 进行注册
[[BHModuleManager sharedManager] registerDynamicModule:cls];
}
}
}
// 获取所有服务的映射
NSArray<NSString *> *services = BHReadConfiguration(BeehiveServiceSectName,mhp);
for (NSString *map in services) {
NSData *jsonData = [map dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
// 解析存储的json
id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
if (!error) {
if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
NSString *protocol = [json allKeys][0];
NSString *clsName = [json allValues][0];
if (protocol && clsName) {
// 注册所有的服务映射
[[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
}
}
}
}
}
NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp) {
NSMutableArray *configs = [NSMutableArray array];
unsigned long size = 0;
// 获取指定section的数据
const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
unsigned long counter = size/sizeof(void*);
// 遍历获取存储的数据
for(int idx = 0; idx < counter; ++idx){
char *string = (char*)memory[idx];
NSString *str = [NSString stringWithUTF8String:string];
if(!str)continue;
BHLog(@"config = %@", str);
if(str) [configs addObject:str];
}
return configs;
}