BeeHive

前言

BeeHive 是阿里用于iOS的 App 模块化编程的框架实现方案,吸收了 Spring 框架 Service 的理念来实现模块间的 API 耦合。

项目结构

项目模块

├── BHAnnotation.h
├── BHAnnotation.m
├── BHAppDelegate.h
├── BHAppDelegate.m
├── BHCommon.h
├── BHConfig.h
├── BHConfig.m
├── BHContext.h
├── BHContext.m
├── BHDefines.h
├── BHModuleManager.h
├── BHModuleManager.m
├── BHModuleProtocol.h
├── BHServiceManager.h
├── BHServiceManager.m
├── BHServiceProtocol.h
├── BHTimeProfiler.h
├── BHTimeProfiler.m
├── BHWatchDog.h
├── BHWatchDog.m
├── BeeHive.h
└── BeeHive.m

模块解读

BeeHive:是框架的门面,将几个组件封装在这个类里面组合,向外提供统一的接口。
BHAnnotation:注解类,提供一些简便的宏
BHAppDelegate:AppDelegate 的替换类,提供一些启动,进入后台的消息分发到模块的功能。
BHCommon:Log 功能(暂时,也许会增加)
BHConfig:提供全局的配置功能,类似于 NSUserDefaults 的功能
BHContext:框架的上下文对象,可以配置环境(dev, stage等),提供全局配置对象 BHConfig,还有一些关于 APP 上下文的功能。
BHModuleManager:模块的管理类,包括读取,加载,注册模块等功能。
BHModuleProtocol:要成为一个模块需要遵守的协议。
BHServiceManager:服务,也就是模块间的通信用服务来解决。
BHServiceProtocol:要成为服务需要遵守的协议。
BHTimeProfiler:提供性能监控的功能
BHWatchDog:检测线程的卡顿功能。

如何解耦

在 BeeHive 中一个个对象被分成模块(Module),模块之间相互独立,通过接口来调用(Service)来让模块间不相互之间依赖,通过暴露的接口来规范调用机制,但是过多的接口也会造成维护问题。

模块

想让自己的对象变成 Module 只需要遵守协议 BHModuleProtocolBHModuleProtocol 协议中包含了一个模块的声明周期的回调,和注册的方法,如提供 AppDelegate 周期回调的事件,以及模块初始化,配置入口等

/// 一部分事件
- (void)basicModuleLevel;

- (BOOL)async;

- (void)modSetUp:(BHContext *)context;

- (void)modInit:(BHContext *)context;

- (void)modSplash:(BHContext *)context;

- (void)modQuickAction:(BHContext *)context;

- (void)modTearDown:(BHContext *)context;

- (void)modWillResignActive:(BHContext *)context;

- (void)modDidEnterBackground:(BHContext *)context;

- (void)modWillEnterForeground:(BHContext *)context;

- (void)modDidBecomeActive:(BHContext *)context;

- (void)modWillTerminate:(BHContext *)context;

- (void)modUnmount:(BHContext *)context;

此外,模块注册方法提供从 plist 注册,使用宏BH_EXPORT_MODULE注册 2 种注册方法。

模块的注册

模块的注册主要集中在 BHModuleManager 这个对象单例中。
读取 plist 的模块

- (void)loadLocalModules
{
    
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
        return;
    }
    
    NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
    
    NSArray *modulesArray = [moduleList objectForKey:kModuleArrayKey];
    
    [self.BHModules addObjectsFromArray:modulesArray];
    
}

动态注册的方式,也就是宏注册

- (void)addModuleFromObject:(id)object
{
    Class class;
    NSString *moduleName = nil;
    
    if (object) {
        class = object;
        moduleName = NSStringFromClass(class);
    } else {
        return ;
    }
    
    if ([class conformsToProtocol:@protocol(BHModuleProtocol)]) {
        NSMutableDictionary *moduleInfo = [NSMutableDictionary dictionary];
        
        BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];

        int levelInt = 1;
        
        if (responseBasicLevel) {
            levelInt = 0;
        }
        
        [moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
        if (moduleName) {
            [moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
        }

        [self.BHModules addObject:moduleInfo];
    }
}

这个类除注册之外,还提供了事件的转发,这部分代码比较简单,想了解可以查看源代码。

接口 (Service)

接口规范了统一的调用方式,避免了直接依赖来调用对象方法,但是这样的做法如果对象多了,接口也会变得很多,维护接口也是一个巨大的工作量。
定义一个接口需要遵守BHServiceProtocol 协议,此协议主要是为了模块的单例实现提供服务。

接口注册部分也分为 2 种方式,一种是 plist,一种是代码注册。
用 plist 注册:

- (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];
}

使用代码也就是类似

[[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]];

实现如下

- (void)registerService:(Protocol *)service implClass:(Class)implClass
{
    NSParameterAssert(service != nil);
    NSParameterAssert(implClass != nil);
    
    if (![implClass conformsToProtocol:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
    }
    
    if ([self checkValidService:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
    }
    
    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];
    }
}

模块之间的调用

既然模块有了,接口也有了,那么他们是怎么调用的呢?
首先为了解耦,BeeHive 使用接口来解耦,那么调用方式会如下:

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

此方法,可以指定一个接口来完成对接口后面的模块的调用。

那么模块是否可以成为单例,对外界调用呢?
当然是可以的,只需要在Service对象中实现事件函数

声明

-(BOOL) singleton
{
    return YES;
}

通过createService获取的对象则为单例对象,如果实现上面函数返回的是NO,则createService返回的是多例。

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

好了,至此,BeeHive 的解耦的手法就是如此。

其他

BeeHive 还提供了环境变量的封装,BHContext 对象就是一个 APP 上下文的环境变量。想知道更详细的细节,您可以自己探索。

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

推荐阅读更多精彩内容