BeeHive源码解析

1,199 阅读5分钟

前年的时候参与了项目的组件化改造,在系统事件和全局事件分发的场景下,我们使用了BeeHive,这里分析一下BeeHive(欠的历史债)!!!

在解析三方库之前,首先要弄清楚这个是做什么的,该怎么用?

ViewController/View级解耦

使用

在做组件化之前,我们的项目里各个模块不分彼此的相互调用,渐渐的就发展成下图:

BeeHive通过Protocol的方式来解决依赖,正常情况下我们从A页面跳转至B页面,会在A页面里直接引用B页面,如下图左侧的引用关系。而在BeeHive的思想里是把B页面对外公开的方法抽象出接口BServiceProtocolBViewController来实现这个接口,从而使外界本该依赖BViewController的页面通过引用BServiceProtocol就能达到相同的效果,如下图右侧。

image.png

遵循这个思想之后每个Modlue都可以抽出自己的ModuleServiceProtocol,然后将他们统一下沉在ServiceProtocol层中,Modlue之间不直接交互,而是通过ServiceProtocol进行交互,典型的面向接口编程,符合依赖倒置的思想。 WeChat734f8e7537ade11285ae2dc9f2c7b22a.png

具体使用如下: 创建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协议,然后在.mimplementation中添加代码BH_EXPORT_MODULE(),接下来就按自己的需要去订阅相应的事件,下侧我订阅的是闪屏页。

@interface GAModule : NSObject <BHModuleProtocol>
@end

@implementation GAModule

BH_EXPORT_MODULE()

- (void)modSplash:(BHContext *)context {
    NSLog(@"modSplash");
}
@end

实现

上面例子创建的GAModuleBeeHive的关系如下图,我们将GAModule通过registerModule注册给BHModuleManagerBHAppDelegate负责接收Application的生命周期消息,当有回调回来时会告诉BHModuleManager触发了某个事件,BHModuleManager会触发我们在GAModule里订阅的消息。

751628473646_.pic.jpg

具体的操作流程如下:

image.png

首先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;
}