BeeHive源码学习笔记

1,068 阅读4分钟

随着项目的发展,很多规模较大的App,例如淘宝、美团之类的App,已经变成了多个App的大集合。有众多的业务线和不同的开发团队,分别负责开发各自的部分(模块)。相互之间需要调用(通信)的时候,如果直接引用并调用,就会变成这样。


可以看出,变现在工程中就是一个工程中,多个模块之间的相互import,耦合在一起。

1. 所有模块在一个工程里,无法分开编译,开发时每次编译,需要编译全部源码,严重影响开发效率。

2. 由于所有模块耦合在一起,开发A模块功能时,可能需要动到B模块里面的代码。可能会导致B模块的问题。

3. 如果淘宝App里面的C模块,想要挪到天猫App里面,几乎肯定是要在天猫App中重写一遍C模块的代码了,因为C模块耦合在淘宝App中根本拆不出来。以后C模块的需求开发都要分别在两个App中开发一次。

诉求:

1. 每个模块分开编译,互不影响。

2. 每个模块分成不同的仓库,不同团队进行维护。

3. 整个模块可以轻松的复用。

根据现象猜实现

首先看下BeeHive给出的结果,demo中的解耦结果是这样:

#import "HomeServiceProtocol.h"

id<HomeServiceProtocol> homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];

// 尽情操作 homeVc,调用 协议HomeServiceProtocol中提供的方法。

createService:方法,只要传进去一个参数protocol,就能创建出来一个homeVC

显然,HomeServiceProtocol和homeVC肯定提前偷偷注册进一个字典里了。key是HomeServiceProtocol,value是homeVC。然后给一个protocol就能找到对应的对象。不然不可能根据协议找到实现的。(除非你遍历所有类,扯远了)


homeVC遵守HomeServiceProtocol,所以可以在下面随意调用HomeServiceProtocol中提供的方法,即其他模块需要调用homeVC的所有方法,全都暴露在这个协议中,供其他模块调用。

(这样做就只需要依赖抽象的protocol,而不需要依赖class了)


我在我的模块里调用homeVC,只要protocol中定义了方法,就能调用,并编译通过。进行模块的发版。

代码实现

从上面的猜测中可知,有一个全局的字典注册了所有protocol与class的对应关系,使后来可以在工程的任意地方都能根据protocol创建出来具体的class,并调用protocol里所有的方法。

看一下注册方式读取时机

注册方式一:


BeeHiveService的宏定义:

#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";

展开,将参数带进去之后

@class BeeHive; 
char * kHomeServiceProtocol_service BeeHiveDATA(BeehiveServices) = "{ ""HomeServiceProtocol"" : ""BHViewController""}";

BeeHiveDATA的宏定义:

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))

完全展开带进去之后,得到

@class BeeHive; 
char * kHomeServiceProtocol_service __attribute((used, section("__DATA,"BeehiveServices" "))) = "{ ""HomeServiceProtocol"" : ""BHViewController""}";

上面的意思是,会将等号后面的字符串,{ ""HomeServiceProtocol"" : ""BHViewController""},存储到mach-o文件的__DATA段的一个名叫BeehiveServices的section中。

读取时机一:

在BHAnnotation.m 中(main函数之前

__attribute__((constructor))
void initProphet() {
    _dyld_register_func_for_add_image(dyld_callback);
}

首先,__attribute__((constructor))修饰的函数,会在main函数之前被调用。

_dyld_register_func_for_add_image函数,dyld加载每个库的时候,都会调用其中的回调block,即dyld_callback

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    //register services
// 这里是读取之前注册在BeehiveServices的section中的字符串
    NSArray<NSString *> *services = BHReadConfiguration("BeehiveServices",mhp);
    for (NSString *map in services) {
        NSData *jsonData =  [map dataUsingEncoding:NSUTF8StringEncoding];
        NSError *error = nil;
        id json = [NSJSONSerialization JSONObjectWithData:jsonData options:0 error:&error];
        if (!error) {
            if ([json isKindOfClass:[NSDictionary class]] && [json allKeys].count) {
                // 这里解析出来 protocol 和对应的class
                NSString *protocol = [json allKeys][0];
                NSString *clsName  = [json allValues][0];
                
                if (protocol && clsName) {
                    // 注册进全局的BHServiceManager的一个字典里
                    [[BHServiceManager sharedManager] registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
                }
            }
        }
    }
    
}

出去出来后转换成键值对,存放在[BHServiceManager shareManager].allServiceDict的字典中。

整个流程图:


注册方式二:


放进一个plist文件中来维护。

读取时机二:

application:didFinishLauchingWithOptions:(main函数之后


- (void)registerLocalServices
{
    NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName;
    
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"];
    if (!plistPath) {
        return;
    }
    
    NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath];
    
    [self.lock lock];
    for (NSDictionary *dict in serviceList) {
        NSString *protocolKey = [dict objectForKey:@"service"];
        NSString *protocolImplClass = [dict objectForKey:@"impl"];
        if (protocolKey.length > 0 && protocolImplClass.length > 0) {
            [self.allServicesDict addEntriesFromDictionary:@{protocolKey:protocolImplClass}];
        }
    }
    [self.lock unlock];
}

通过上述方式,结构就变成了下图:


ABCD之间相互依赖被解除,如果是模块A的开发者,开发时只需要编译模块A,可以选择性编译模块BCD,不影响运行调试。


APP生命周期分发