模块化方案 - BeeHive

为什么使用 BeeHive

在狼人杀项目时,iOS 团队有 9 个人协作开发,如果没有一个好的模块化方案支撑,诸如代码冲突 / 逻辑复用等问题将占用不少开发时间,势必面临效率问题。当时的解决方案,就使用了类似 BeeHive 的模块化方案进行团队开发。

业务模块化的好处:

  • 维护便利,划分业务模块,业务逻辑在一个模块内。
  • 代码复用,相似度高的模块可以低成本迁移到其它项目。
  • 易于扩展,业务解耦使得新功能的开发负担小。

什么时候应进行模块化

一套框架肯定有学习和使用成本,这里涉及一个产出和投入的问题。那么如何具体的衡量是否使用呢?

个人愚见,有以下情况是值得去思考做模块化的:

  • 业务规模,一是业务代码本身的体量大,二是业务发展速度,业务增速快,有必要考虑行模块化,适应日后复杂情形。
  • 团队规模,团队人数也是业务规模的体现,除代码提交的冲突增加,沟通成本也直线上升,需要更具效率的协同。

模块化的标准是什么

目前业界并没有形成标准共识,一定之规。

而一定要量化标准的话,个人的粗浅实践认为是:

业务模块数<=研发人员数*3

如果模块化造成了负效果,违背我们让业务跑得更好更快的初衷,那就是本末倒置了。

BeeHive 是什么

BeeHive 是阿里巴巴开源的一款模块化框架,按照官方的介绍:

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

架构图:

与上面的架构图所对应的,BeeHive 重点关注的有两个部分:

  • 业务模块
    • 模块如何注册
    • 模块如何分发系统事件&应用事件
  • 服务
    • 服务如何注册
    • 服务如何被模块调用

BeeHive 如何实现模块化

BeeHive 模块注册

具体进行业务开发时, 所有业务模块 Module 统一交由 BeeHive 来进行管理,如何将 Module 注册到 BeeHive 是关键。

BeeHive 提供了 3 种方式来进行注册:

  • 动态注册
  • 静态注册
  • Annotation

BeeHive 框架在模块解耦方面,比较特别的是 Annotaition 方式。

动态注册
动态注册使用

动态注册一个 NewModule 的代码如下:

1
2
3
4
5
6
7
8
9
10
11
#import "BeeHive.h"

@interface NewModule()<BHModuleProtocol>
@end

@implementation NewModule

//如果参数为 YES 代表启动之后异步执行模块的初始化,为 NO 则同步执行
BH_EXPORT_MODULE(NO)

@end
动态注册实现

动态注册使用需要利用宏 BH_EXPORT_MODULE

1
2
3
#define BH_EXPORT_MODULE(isAsync) \
+ (void)load { [BeeHive registerDynamicModule:[self class]]; } \
-(BOOL)async { return [[NSString stringWithUTF8String:#isAsync] boolValue];}

可以发现,上面的注册时机是固定在 load 方法的。

对于模块的动态注册,调用实现如下:

最后会走到 -[BHModuleManager addModuleFromObject:shouldTriggerInitEvent:] 实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
- (void)addModuleFromObject:(id)object
shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent
{
Class class;
NSString *moduleName = nil;

if (object) {
class = object;
//取出 module 类名字符串
moduleName = NSStringFromClass(class);
} else {
return ;
}

__block BOOL flag = YES;


//遍历查看是否已有相同的类注册
[self.BHModules enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if ([obj isKindOfClass:class]) {
flag = NO;
*stop = YES;
}
}];
if (!flag) {//已经注册过,则直接返回
return;
}


if ([class conformsToProtocol:@protocol(BHModuleProtocol)]) {//检查是否遵循相关 BHModuleProtocol 协议
NSMutableDictionary *moduleInfo = [NSMutableDictionary dictionary];

BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];

int levelInt = 1;

if (responseBasicLevel) {
levelInt = 0;
}

//设置 moudle 字典信息
[moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
if (moduleName) {
[moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
}

[self.BHModuleInfos addObject:moduleInfo];

//将模块初始化,存进 BHModules 数组
id<BHModuleProtocol> moduleInstance = [[class alloc] init];
[self.BHModules addObject:moduleInstance];

//标志为已实例化
[moduleInfo setObject:@(YES) forKey:kModuleInfoHasInstantiatedKey];

[self.BHModules sortUsingComparator:^NSComparisonResult(id<BHModuleProtocol> moduleInstance1, id<BHModuleProtocol> moduleInstance2) {// BHModules 根据优先级进行排序

// 默认两个模块都是用 BHModuleNormal=1
NSNumber *module1Level = @(BHModuleNormal);
NSNumber *module2Level = @(BHModuleNormal);

// 如果模块实现了 basicModuleLevel 方法, BHModuleBasic=0
if ([moduleInstance1 respondsToSelector:@selector(basicModuleLevel)]) {
module1Level = @(BHModuleBasic);
}
if ([moduleInstance2 respondsToSelector:@selector(basicModuleLevel)]) {
module2Level = @(BHModuleBasic);
}

if (module1Level.integerValue != module2Level.integerValue) {//两个模块 basicModuleLevel 不相等

// BHModuleBasic 的模块会排在 BHModuleNormal
return module1Level.integerValue > module2Level.integerValue;
} else {//两个模块 basicModuleLevel 相等

NSInteger module1Priority = 0;
NSInteger module2Priority = 0;

if ([moduleInstance1 respondsToSelector:@selector(modulePriority)]) {
module1Priority = [moduleInstance1 modulePriority];
}
if ([moduleInstance2 respondsToSelector:@selector(modulePriority)]) {
module2Priority = [moduleInstance2 modulePriority];
}

//比较两个模块的 modulePriority ,看哪一个值大就在前面
return module1Priority < module2Priority;
}
}];

//将模块的实例和 BeeHive 事件,进行对应的注册
[self registerEventsByModuleInstance:moduleInstance];

if (shouldTriggerInitEvent) {//是否需要分发初始化事件,动态注册 registerDynamicModule 过来的参数是 NO ,不做分发
[self handleModuleEvent:BHMSetupEvent forTarget:moduleInstance withSeletorStr:nil andCustomParam:nil];
[self handleModulesInitEventForTarget:moduleInstance withCustomParam:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[self handleModuleEvent:BHMSplashEvent forTarget:moduleInstance withSeletorStr:nil andCustomParam:nil];
});
}
}
}
静态注册
静态注册使用

首先,静态注册要在配置中 指定 plist 文件名

1
[BHContext shareInstance].moduleConfigName = @"BHModule";

然后在 Plist 文件里面配置好 module 对应的信息:

  • moduleClass: 模块类的类名。
  • moduleLevel: 如果不去设置 Level 默认是 BHModuleNormal。 只会有两种 Level ,BHModuleBasic = 0 ,BHModuleNormal = 1。
  • modulePriority: 对应 BHModuleProtocol 的同名方法,moduleLevel 相等情况下比较 modulePriority 。
静态注册实现

重点关注 2 个方法:

  • -[BHModuleManager loadLoacalModules] 从 plist 文件扫描出来对应模块字典信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)loadLocalModules
{
//根据 [BHContext shareInstance].moduleConfigName 拿到文件路径
NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"];
if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
return;
}

NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
//plist 里的的静态 module 数组
NSArray<NSDictionary *> *modulesArray = [moduleList objectForKey:kModuleArrayKey];
//初始化一个存储 self.BHModuleInfos 已有的 moudule 类名的字典
NSMutableDictionary<NSString *, NSNumber *> *moduleInfoByClass = @{}.mutableCopy;
[self.BHModuleInfos enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[moduleInfoByClass setObject:@1 forKey:[obj objectForKey:kModuleInfoNameKey]];
}];
//遍历 plist 里的的静态 module 数组
[modulesArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
if (!moduleInfoByClass[[obj objectForKey:kModuleInfoNameKey]]) {//如果是没有在 self.BHModuleInfos 的 module 类,则把对应的字典添加进取
[self.BHModuleInfos addObject:obj];
}
}];
}
  • -[BHModuleManager registedAllModules] 根据模块信息,实例化静态注册的模块类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
- (void)registedAllModules
{
// self.BHModuleInfos 排序一次
[self.BHModuleInfos sortUsingComparator:^NSComparisonResult(NSDictionary *module1, NSDictionary *module2) {
NSNumber *module1Level = (NSNumber *)[module1 objectForKey:kModuleInfoLevelKey];
NSNumber *module2Level = (NSNumber *)[module2 objectForKey:kModuleInfoLevelKey];
if (module1Level.integerValue != module2Level.integerValue) {
return module1Level.integerValue > module2Level.integerValue;
} else {
NSNumber *module1Priority = (NSNumber *)[module1 objectForKey:kModuleInfoPriorityKey];
NSNumber *module2Priority = (NSNumber *)[module2 objectForKey:kModuleInfoPriorityKey];
return module1Priority.integerValue < module2Priority.integerValue;
}
}];

NSMutableArray *tmpArray = [NSMutableArray array];

//module init
[self.BHModuleInfos enumerateObjectsUsingBlock:^(NSDictionary *module, NSUInteger idx, BOOL * _Nonnull stop) {

NSString *classStr = [module objectForKey:kModuleInfoNameKey];

Class moduleClass = NSClassFromString(classStr);
//判断模块实例化标记
BOOL hasInstantiated = ((NSNumber *)[module objectForKey:kModuleInfoHasInstantiatedKey]).boolValue;
if (NSStringFromClass(moduleClass) && !hasInstantiated) {//对没有进行实例化的 module 进行初始化
id<BHModuleProtocol> moduleInstance = [[moduleClass alloc] init];
[tmpArray addObject:moduleInstance];
}

}];
//添加到模块数组
[self.BHModules addObjectsFromArray:tmpArray];
//将模块与对应事件进行注册
[self registerAllSystemEvents];
}

在经历registedAllModules方法之后,所有注册的module都生成了对应的实例对象。

PS:如果对模块顺序有要求时要注意,动态注册的模块,整体的顺序会排在静态注册的前面,因为 load 方法的时机在更前面。

Annotation 注册模块
Annotation 注册使用

模块如果使用 Annotaiton 方式注册,代码非常的简单:

1
2
3
4
5
6
7
#import <BHModuleProtocol.h>

@BeeHiveMod(YourModule)

@interface YourModule() <BHModuleProtocol>

@end
Annotation 注册实现

查看 BeeHiveMod() 的宏定义:

1
2
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";

其中的 BeeHiveDATA 也是一个宏:

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

直接展开变成:

1
@class BeeHive; char * kYourModule_mod __attribute((used, section("__DATA,"BeehiveMods" "))) = ""YourModule"";

Annotation 相关扩展

编译属性 __attribute__

这里的关键是 __attribute__ ,日常开发中不少场景也有它的身影,它的使用格式也很简单:

1
__attribute__((属性列表))

例如作为 方法过期的警告 对已废弃方法使用:

实现效果:

查看相关的 官方解释

An attribute specifier is of the form __attribute__ ((attribute-list)). An attribute list is a possibly empty comma-separated sequence of attributes, where each attribute is one of the following:

  • Empty. Empty attributes are ignored.
  • An attribute name (which may be an identifier such as unused, or a reserved word such as const).
  • An attribute name followed by a parenthesized list of parameters for the attribute. These parameters take one of the following forms:
    • An identifier. For example, mode attributes use this form.
    • An identifier followed by a comma and a non-empty comma-separated list of expressions. For example, format attributes use this form.
    • A possibly empty comma-separated list of expressions. For example, format_arg attributes use this form with the list being a single integer constant expression, and alias attributes use this form with the list being a single string constant.

大意是说的 __attribute__ ((attribute-list)) 使用的 attribute-list 属性列表有三种情况:

  • 为空,空的会被忽略。
  • 单纯的属性名,例如 unused 这样的:__attribute__((unused))
  • 属性名带参数使用:
    • 特定的标记,例如 : __attribute__((mode(DI)))
    • 特定的标记,可能是逗号分割的表达式。例如 : __attribute__((format(printf, a, b)))
    • 一个可能为空的逗号分隔的表达式列表。例如: __attribute__((noreturn, format(printf, 1, 2)) );

而在 BeeHive 当中使用到的代码则是:

1
__attribute((used, section("__DATA,"BeehiveMods" ")))

其实就是分别使用了 2 个关键字,一个关键字是 used,另外一个则是 section()

used

used 具体描述见 gun 官方文档

used

This attribute, attached to a variable with static storage, means that the variable must be emitted even if it appears that the variable is not referenced.

When applied to a static data member of a C++ class template, the attribute also means that the member is instantiated if the class itself is instantiated.

假如 used 比较陌生的话,那 unused 的警告,作为 iOS 开发者我们应该很熟悉了。

对于 __attribute((used)) 它的作用与 unused 相反, 是向编译器说明这段代码有用,即使在没有用到的情况下编译器也不会警告。

代码如果没有加 used 变量会被编译器给优化了。

section

要了解 section ,要知道 Mach-O 的构成:

Mach-O 主要由 3 部分组成:

  • Mach-O 头(Mach Header):Mach Header 描述了 Mach-O 的 CPU 架构、文件类型以及加载命令等信息。
  • 加载命令(Load Commands):Load Commands 描述了文件中数据的具体组织结构,不同的数据类型使用不同的加载命令表示。
  • 数据区(Data):Data 中每一个段(Segment)的数据都保存在此,段的概念和 ELF 文件中段的概念类似,都拥有一个或多个 Section ,用来存放数据和代码。

作为与 Mach-O 文件格式有关的 Segment 的定义,可以在 /usr/include/mach-o/loader.h 中看到:

1
2
3
4
5
6
7
8
9
#define SEG_PAGEZERO    "__PAGEZERO"    /* the pagezero segment which has no */
/* protections and catches NULL */
/* references for MH_EXECUTE files */

#define SEG_TEXT "__TEXT" /* the tradition UNIX text segment */

#define SEG_DATA "__DATA" /* the tradition UNIX data segment */

#define SEG_OBJC "__OBJC" /* objective-C runtime segment */

而关于 section 的结构也可以看到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
struct section { /* for 32-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint32_t addr; /* memory address of this section */
uint32_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
};

struct section_64 { /* for 64-bit architectures */
char sectname[16]; /* name of this section */
char segname[16]; /* segment this section goes in */
uint64_t addr; /* memory address of this section */
uint64_t size; /* size in bytes of this section */
uint32_t offset; /* file offset of this section */
uint32_t align; /* section alignment (power of 2) */
uint32_t reloff; /* file offset of relocation entries */
uint32_t nreloc; /* number of relocation entries */
uint32_t flags; /* flags (section type and attributes)*/
uint32_t reserved1; /* reserved (for offset or index) */
uint32_t reserved2; /* reserved (for count or sizeof) */
uint32_t reserved3; /* reserved */
};

明白 section 的定义,就很容易理解__attribute((section("__DATA,"BeehiveMods" "))) 的意思,就是将数据放进 segname__DATA , sectnameBeehiveModssection 中。

读取 section

在 BeeHive 中既然把数据存进 section,自然也需要将 section 存储的数据取出来。

这里的关键在于 BHReadConfiguration 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp)
{
NSMutableArray *configs = [NSMutableArray array];
unsigned long size = 0;
// 利用 getsectiondata 方法取出数据
#ifndef __LP64__
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp, SEG_DATA, sectionName, &size);
#else
const struct mach_header_64 *mhp64 = (const struct mach_header_64 *)mhp;
uintptr_t *memory = (uintptr_t*)getsectiondata(mhp64, SEG_DATA, sectionName, &size);
#endif

unsigned long counter = size/sizeof(void*);
for(int idx = 0; idx < counter; ++idx){//遍历获取 section 中存储的字符串
char *string = (char*)memory[idx];
NSString *str = [NSString stringWithUTF8String:string];
if(!str)continue;

BHLog(@"config = %@", str);
if(str) [configs addObject:str];
}

return configs;
}

利用 constructor 完成模块加载

可以看到代码中有一个 initProphet 函数的代码如下:

1
2
3
4
__attribute__((constructor))
void initProphet() {
_dyld_register_func_for_add_image(dyld_callback);
}

其中的玄机就在 __attribute__((constructor)) ,它到作用是系统在执行 main() 函数之前调用该函数 。

官方解释如下:

constructor
destructor

The constructor attribute causes the function to be called automatically before execution enters main (). Similarly, the destructor attribute causes the function to be called automatically after main () has completed or exit () has been called. Functions with these attributes are useful for initializing data that will be used implicitly during the execution of the program.

These attributes are not currently implemented for Objective-C.

感兴趣的可以去看看相关文档和资料 attribute((constructor))用法解析 ,这里不再展开。

_dyld_register_func_for_add_image() 则表示每一次镜像的加载,都会触发传入的回调函数。

在 BeeHive 的上述代码里,每次 App 启动都会触发 dyld_callback 回调函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
//获取模块信息并进行注册
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;
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)];
}

}
}
}

}

回调函数的主要作用,就是每次启动,去注册模块和服务的数据信息。

BeeHive 模块分发事件

将模块注册到 BeeHive 后,就可以接收到事件:

  • UIApplicationDelegate 系统事件
  • BeeHive 模块事件
  • 业务自定义事件

BHModuleProtocol 定义事件

上述的 3 种事件,都在 BHModuleProtocol 当中定义了方法,Module 类可根据需求去实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
@protocol BHModuleProtocol <NSObject>

@optional
...

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- (void)modDidCustomEvent:(BHContext *)context;
@end

模块事件分发实现

BHAppDelegate 替换

main 函数入口,要替换 AppDelegate 为 BHAppDelegate 或者 BHAppDelegate 的子类,是使用 BeeHive 的一个前提:

1
2
3
4
5
6
7
int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    @autoreleasepool {
        appDelegateClassName = NSStringFromClass([BHAppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

这样才能让业务模块收到 BeeHive 分发对应的事件。

BHAppDelegate 实现监听

如下代码,在 BHAppDelegate 中,触发 系统事件 时,由 BHModuleManager 分发 BeeHive 事件到模块当中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@implementation BHAppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
[[BHModuleManager sharedManager] triggerEvent:BHMSetupEvent];
[[BHModuleManager sharedManager] triggerEvent:BHMInitEvent];

dispatch_async(dispatch_get_main_queue(), ^{
[[BHModuleManager sharedManager] triggerEvent:BHMSplashEvent];
});

...
return YES;
}

- (void)applicationWillResignActive:(UIApplication *)application
{
[[BHModuleManager sharedManager] triggerEvent:BHMWillResignActiveEvent];
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
[[BHModuleManager sharedManager] triggerEvent:BHMDidEnterBackgroundEvent];
}
...
@end

可以看到,上述监听都收拢到了 -[BHModuleManager triggerEvent:] 调用当中。

BHModuleManager 事件分发逻辑

除了 eventType 为 BHMInitEvent / BHMTearDownEvent 时特殊,其它的处理方法都一样。

defalut 默认的模块方法执行实现流程如下图:

BHMInitEvent

BeeHive 模块 初始化 事件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
- (void)handleModulesInitEventForTarget:(id<BHModuleProtocol>)target
withCustomParam:(NSDictionary *)customParam
{
...
[moduleInstances enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
__weak typeof(&*self) wself = self;
void ( ^ bk )(void);
//定义 bk()
bk = ^(){
__strong typeof(&*self) sself = wself;
if (sself) {
//判断 module 类实例是否响应 modInit 方法,并进行回调
if ([moduleInstance respondsToSelector:@selector(modInit:)]) {
[moduleInstance modInit:context];
}
}
};
...
// 对于 async 方法的响应判断,再决定如何执行 bk()
if ([moduleInstance respondsToSelector:@selector(async)]) {
BOOL async = [moduleInstance async];

if (async) {
dispatch_async(dispatch_get_main_queue(), ^{
bk();
});

} else {
bk();
}
} else {
bk();
}
}];
}
BHMTearDownEvent

BeeHive 模块 销毁 事件:

1
2
3
4
5
6
7
8
9
10
11
12
- (void)handleModulesTearDownEventForTarget:(id<BHModuleProtocol>)target
withCustomParam:(NSDictionary *)customParam
{
...
//数组倒序循环,依次对 module 类实例调用 modTearDown
for (int i = (int)moduleInstances.count - 1; i >= 0; i--) {
id<BHModuleProtocol> moduleInstance = [moduleInstances objectAtIndex:i];
if (moduleInstance && [moduleInstance respondsToSelector:@selector(modTearDown:)]) {
[moduleInstance modTearDown:context];
}
}
}

BHMTearDownEvent 事件特别的是,按优先级从低到高销毁。

业务自定义事件

BeeHive 还提供了自定义事件的方式来进行分发。

自定义事件分发
1
2
3
4
5
-(void)someMethod
{
//调用 tiggerCustomEvent 分发自定义事件 BHMDidCustomEvent
[[BHModuleManager sharedManager] tiggerCustomEvent:BHMDidCustomEvent];
}

-[BHModuleManager tiggerCustomEvent:]方法的实现为:

1
2
3
4
5
6
7
8
- (void)tiggerCustomEvent:(NSInteger)eventType
{
if(eventType < 1000) {//判断是否为小于 1000 的自定义事件
return;
}

[[BHModuleManager sharedManager] triggerEvent:eventType];
}

判断 eventType 后,直接透传给 BHModuleManager 进行处理。

自定义事件 - 手动注册

如果定义好的枚举值 BHMDidCustomEvent 无法满足需求,也使用满足条件(大于1000)的事件值:

1
2
3
4
5
6
7
8
//Module 
-(void)modInit:(BHContext *)context
{
//注册 10404 的事件
[[BHModuleManager sharedManager] registerCustomEvent:10404
withModuleInstance:self
andSelectorStr:NSStringFromSelector(@selector(eventForCustom))];
}

具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
- (void)registerCustomEvent:(NSInteger)eventType
withModuleInstance:(id)moduleInstance
andSelectorStr:(NSString *)selectorStr {
if (eventType < 1000) {//判断是否为小于 1000 的自定义事件
return;
}
[self registerEvent:eventType withModuleInstance:moduleInstance andSelectorStr:selectorStr];
}

- (void)registerEvent:(NSInteger)eventType
withModuleInstance:(id)moduleInstance
andSelectorStr:(NSString *)selectorStr {
//生成 SEL
SEL selector = NSSelectorFromString(selectorStr);
if (!selector || ![moduleInstance respondsToSelector:selector]) {
return;
}

//将 eventType 注册进字典 BHSelectorByEvent,通过 eventType 绑定 SEL
NSNumber *eventTypeNumber = @(eventType);
if (!self.BHSelectorByEvent[eventTypeNumber]) {
[self.BHSelectorByEvent setObject:selectorStr forKey:eventTypeNumber];
}

//将 eventType 注册进字典 BHModulesByEvent
if (!self.BHModulesByEvent[eventTypeNumber]) {
[self.BHModulesByEvent setObject:@[].mutableCopy forKey:eventTypeNumber];
}

/**
1.判断 eventType 对应的模块实例列表 eventModules 是否包括 moduleInstance
2.1 eventModules 没有 moduleInstance ,则添加 moduleInstance
2.2 对添加完对象的 eventModules 按优先级排序
*/
NSMutableArray *eventModules = [self.BHModulesByEvent objectForKey:eventTypeNumber];
if (![eventModules containsObject:moduleInstance]) {
[eventModules addObject:moduleInstance];
[eventModules sortUsingComparator:^NSComparisonResult(id<BHModuleProtocol> moduleInstance1, id<BHModuleProtocol> moduleInstance2) {
NSNumber *module1Level = @(BHModuleNormal);
NSNumber *module2Level = @(BHModuleNormal);
if ([moduleInstance1 respondsToSelector:@selector(basicModuleLevel)]) {
module1Level = @(BHModuleBasic);
}
if ([moduleInstance2 respondsToSelector:@selector(basicModuleLevel)]) {
module2Level = @(BHModuleBasic);
}
if (module1Level.integerValue != module2Level.integerValue) {
return module1Level.integerValue > module2Level.integerValue;
} else {
NSInteger module1Priority = 0;
NSInteger module2Priority = 0;
if ([moduleInstance1 respondsToSelector:@selector(modulePriority)]) {
module1Priority = [moduleInstance1 modulePriority];
}
if ([moduleInstance2 respondsToSelector:@selector(modulePriority)]) {
module2Priority = [moduleInstance2 modulePriority];
}
return module1Priority < module2Priority;
}
}];
}
}

BeeHive 模块间调用 Service

BeeHive 的模块间通过 Service 调用,来协同其它模块完成功能。

Service 是什么

这里的 Service 实际上指 Protocol,且都需要遵循 BHServiceProtocol ,官方例子示例 HomeServiceProtocol

1
2
3
4
5
6
@protocol HomeServiceProtocol <NSObject, BHServiceProtocol>


-(void)registerViewController:(UIViewController *)vc title:(NSString *)title iconName:(NSString *)iconName;

@end

Protocol 的好处是,不同的模块,不会再依赖某一个类实例,而是 依赖接口,对于使用者屏蔽具体实现。

Service 注册

ServiceProtocol 必须注册,才能由 BHServiceManager 管理,从而被不同模块使用。与模块注册相同,也有 3 种注册方式:

  • 代码动态注册
  • Plist 文件静态注册
  • Annoatation 注册

Service 动态注册

在需要的地方直接利用代码注册 Service ,

1
2

[[BeeHive shareInstance] registerService:@protocol(TradeServiceProtocol) service:[BHTradeViewController class]];

官方的示例是在模块事件的 modInit 中进行注册。

动态注册实现

最终的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- (void)registerService:(Protocol *)service implClass:(Class)implClass
{
// 参数校验
NSParameterAssert(service != nil);
NSParameterAssert(implClass != nil);

if (![implClass conformsToProtocol:service]) {//如果实现类,没有遵守 protocol 则 return
if (self.enableException) {//判断异常开关
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
}
return;
}

if ([self checkValidService:service]) {//从 allServicesDict 看 service 是否已注册过,已注册则 return
if (self.enableException) {//判断异常开关
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
}
return;
}

//获取协议名和实现类类名作为 key-value
NSString *key = NSStringFromProtocol(service);
NSString *value = NSStringFromClass(implClass);

if (key.length > 0 && value.length > 0) {//加锁,将协议名和对应类名存进 allServicesDict,
[self.lock lock];
[self.allServicesDict addEntriesFromDictionary:@{key:value}];
[self.lock unlock];
}

}

主要作用就是协议名和对应类名放到 allServicesDict 保存,等待创建使用。

Serivce 静态注册

通过 BHContext 配置注册 Service 的 Plist 文件:

1
2
//Service 静态配置文件
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";

如图,在 Plist 文件中,配置对应的 ServiceProtocol 与实现类名:

这就完成 BeeHive 服务注册了。

静态注册实现

最终的实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void)registerLocalServices
{
//获取设置的文件名
NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName;

NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"];
if (!plistPath) {//校验文件是否存在
return;
}

//获取 service 数组
NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath];

[self.lock lock];
for (NSDictionary *dict in serviceList) {//遍历 serviceList 数组, 添加协议名与对应的实现类名到 allServicesDict
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];
}

Serivce Annotation 注册

类似业务模块 Annoatation 注册,Service 注册使用 BeeHiveService() 宏:

1
2
3
4
5
@BeeHiveService(HomeServiceProtocol,BHViewController)

@interface BHViewController ()<HomeServiceProtocol>

@end
Annotation 注册实现

展开 BeeHiveService 宏:

1
2
3

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

上述 HomeServiceProtocol 注册示例最终展开为:

1
2

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

本质实现,也与模块 Annoatation 注册一样,将数据存储在 segment 中。然后在 App 启动时,通过回调函数 dyld_callback 注册:

最后会走进 -[BHServcieManager registerService:implClass:] 的方法,和上面服务动态注册的方法一样,添加到BHServcieManagerallServicesDict 字典当中。

使用 Service

通过 Service 调用其它模块非常简单:

1
2
3
4
#import "BHService.h"
...
id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
...

Service 调用实现

Service 的调用过程如下:

最终调用 -[BHServiceManager createService:withServiceName:shouldCache:] 的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
- (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache {
if (!serviceName.length) {//校验 protocol 名,为空则根据 service 创建
serviceName = NSStringFromProtocol(service);
}
id implInstance = nil;//声明一个为 nil 的实现类变量

if (![self checkValidService:service]) {//检查 allServicesDict 是否已经由 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) {//如果需要缓存,则先通过 BHContext ,传递 serviceName 查询实例
id protocolImpl = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];
if (protocolImpl) {
return protocolImpl;
}
}

// 生成 Service 对应的实现类Class
Class implClass = [self serviceImplClass:service];
if ([[implClass class] respondsToSelector:@selector(singleton)]) {//是否实现类 singleton 方法,实现了则调用不同的方法进行初始化
if ([[implClass class] singleton]) {
if ([[implClass class] respondsToSelector:@selector(shareInstance)])
implInstance = [[implClass class] shareInstance];
else
implInstance = [[implClass alloc] init];
if (shouldCache) {//是否需要缓存,如果需要则通过 BHContext 存起来
[[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
return implInstance;
} else {
return implInstance;
}
}
}

//implClass 调用初始化方法,直接返回
return [[implClass alloc] init];
}


- (Class)serviceImplClass:(Protocol *)service
{
//获取 service 对应的实现类名
NSString *serviceImpl = [[self servicesDict] objectForKey:NSStringFromProtocol(service)];
if (serviceImpl.length > 0) {
//通过实现类名,生成 Class
return NSClassFromString(serviceImpl);
}
return nil;
}

其它

本文重点在 BeeHive 的模块和服务时如何完成解耦,如何完成交互协作,关于 BeeHive 的其它细节不会再赘述。

关于如何使用,官方文档介绍已比较详细,感兴趣可直接去看看 BeeHive

总结

作为业务模块化框架,BeeHive 并不是唯一的解决方案,但以 BeeHive 框架为代表的基于面向协议思想的 服务注册 方案,符合开闭原则,让使用者针对接口编程,而不是针对实现编程。且服务注册方案对于代码自动补全和编译时检查都有效,调用简单方便,对业务开发人员来说,工作体感会更好。

BeeHive 劣势在于确实没有做到完全无依赖,客观存在维护公共协议的成本。但只能说仁者见仁,智者见智。

参考

《手机天猫解耦之路》

《BeeHive,一次iOS模块化解耦实践 - InfoQ》

《打造完备的iOS组件化方案:如何面向接口进行模块解耦》

attribute详解及应用》

《Mach-O 文件格式探索》

《BeeHive 1.6.0 源码阅读探讨》

《有赞移动 iOS 组件化(模块化)架构设计实践》