好久没有写文章了,感觉自己快要生锈了,最近忙完公司的新项目。趁下一个版本还没有出来,抽空看看有什么新的框架,学习一波。
看看现在的MVC、MVP、MVVM等设计模式,无非就是为了解耦、组件化等优点。项目一大,各种跨模块跳转就变得很平常了,所以有时候想抽一个模块出来,但是一动牵扯全身,连测试一个小功能都要运行整个项目。
其实我关注阿里的BeeHive也有一段时间了,也第一时间运行了demo,发现功力不够,理解不了,可能是当时急躁吧。现在想着闲着,为什么不挑战一下呢。
我结合官方文档以及其他作者写的文章看看框架的原理,可能理解的不对或者写的不好,如果有错误欢迎更正,毕竟还是小白,一起共勉吧。
至于对框架的陈述,看官方文档就可以了,这里不再累述。
一个非常重要的提示:我写的非常啰嗦,逻辑经常跳转,只做自己学习笔记
运行demo
下载工程->打开终端->输入cd 工程地址->pod install->打开xcworkspace工程->运行。
看到主界面,当然是随便点击,能点击的都点击。
发现没有什么实际的功能展示。只有4个tab和两个页面有一个按钮点击,做跳转页面。
只能看代码了。
查看目录结构
查看TestAppDelegate
我觉得看一个程序,看主入口是最好的。可以知道框架实现的第一步。
在application:didFinishLaunchingWithOptions:方法中看到以下代码
1 | [BHContext shareInstance].application = application; |
从以上代码初步得到BHContext
是一个管理整个应用级别的上下文类。以至于要赋值application和launchOptions给BHContext
。由于BHContext
是使用单例,所以整个应用期间获取的都是同一个对象。
moduleConfigName和serviceConfigName分别对应的是模块配置的文件名和服务协议配置的文件名。这两个文件已经在框架里面了,不需要再次创建。这个两个文件有什么作用呢,这边先提示一下,做静态注册的。也就是访问plist文件获取模块数组、协议数组数据,然后进行注册。当然也会有动态注册,后面会提到。
BeeHive
暂且理解为框架使用的主要类
enableException是否启动异常,用来捕捉异常错误的BHTimeProfiler
记录事件
继续往下看。。。
1 | id<HomeServiceProtocol> homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)]; |
看到代码,第一感觉是要创建home控制器,但是为什么这样创建呢?以前看过依赖注入,感觉有点相似。可以大概理解为:利用BeeHive
绑定HomeServiceProtocol
协议,从而获取实现HomeServiceProtocol协议的home控制器
。也就是说要具备一下条件:
- 创建HomeServiceProtocol协议
- home控制器必须实现HomeServiceProtocol协议
- BeeHive注册(HomeServiceProtocol协议和实现HomeServiceProtocol的homeVc)服务
- 通过createService:方法来获取homeVc
满足以上条件就可以创建home控制器。
1 | if ([homeVc isKindOfClass:[UIViewController class]]) { |
通过这段代码,证明我是正确的,前面的代码确实是要创建home控制器。
到此TestAppDelegate的代码已看完。那么就要进行第二步了,第二部怎么走呢?不是提高homeVc吗?那就从homeVc入手。
看homeVc=BHViewController
BeeHiveService宏定义
1 | @BeeHiveService(HomeServiceProtocol,BHViewController) |
进入里面看到
1 |
|
也就是说这个宏定义是实现API注册的。对服务和需要实现这个服务的类进行绑定注册。
那么,在代码中和官网文档中找到:
1 | [[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]]; |
那么这个宏定义就不难理解了
需要实现HomeServiceProtocol
1 | @interface BHViewController ()<HomeServiceProtocol> |
进入HomeServiceProtocol
里面查看,发现协议除了实现NSObject协议之外还要实现框架的基础BHServiceProtocol
协议
而在HomeServiceProtocol
中,有一个创建控制器的方法。那么这里就是添加一些业务需要的方法了。
我尝试添加一个方法:
1 | - (void)aFunctionForTest; |
在BHViewController
文件中就有一个警告,叫我要实现aFunctionForTest
方法,因为我协议没有添加@optional
,默认是必须实现的。
接着继续看->
init方法
1 | -(instancetype)init { |
从代码上看,是要创建四个控制器,那么作者已经做了一个对比。第一个v1
创建方式就是使用传统的方式,需导入demoTableViewController的头文件。而其他三个控制器v4、v2、s2
则是通服务来获取,不需要导入这几个控制器的头文件。那么就达到解耦的第一步。当然协议的头文件还是要导入。创建的协议文件的导入生命全部放在BeeHive.h
里面了。
其他方法
1 | -(void)registerViewController:(UIViewController *)vc title:(NSString *)title iconName:(NSString *)iconName |
这个方法是把创建的代码全部放在一起,传递不同的参数即可。用数组保存4个控制器。看到这里,我发现HomeServiceProtocol
里面也有这个方法,是不是有什么关联呢?
实践:我把协议的registerViewController: title: iconName:注释掉,运行程序,照样运行。所以暂时没发现作用。要留到最后看看才知道了😆😆😆
1 | - (void)click:(UIButton *)btn { |
我在这个方法里面打个断点,试着把程序的点击了,然而没有进入,所以程序的点击跟这个方法是没有关系的。不知道作者写这个方法是用在什么地方,只能后面查看到才说明,断点会一直保留的。
到此,homeVc基本查看完了。那么就要接续下一步了。这边有几个提示:
- 提到的基础服务:BHServiceProtocol
- 用户跟踪服务UserTrackServiceProtocol
- 贸易服务TradeServiceProtocol
既然跟服务都有关系的,基础服务就显得额外重要了,就从BHServiceProtocol
入手。
BHServiceProtocol
1 | @optional |
从方法名称上看,第一是是否需要单例,第二个是单例实现声明。
官方文档解释的是:
1 | + (BOOL)singleton{ |
如果返回时YES,那么获取的对象就是单例的。相反就是多例的。
我把singleton进行全局收索,发现BHServiceManager类中createService:方法有写到singleton,代码如下:
为了更好的理解,我就在代码中添加注释来说明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/// 创建服务
- (id)createService:(Protocol *)service
{
id implInstance = nil;
// 如果没有包含该服务,抛出一个异常。异常也说明了,这个服务没有注册
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 = NSStringFromProtocol(service);
// 获取该服务的实例,如果有直接返回
id protocolImpl = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];
if (protocolImpl) {
return protocolImpl;
}
// 获取服务对应的类
Class implClass = [self serviceImplClass:service];
// 判断类是否实现了singleton方法
if ([[implClass class] respondsToSelector:@selector(singleton)]) {
// 调用singleton方法,获取BOOL值,YES就是需要实现单例了
if ([[implClass class] singleton]) {
// 判断类是否显示shareInstance方法
if ([[implClass class] respondsToSelector:@selector(shareInstance)])
// 创建单例
implInstance = [[implClass class] shareInstance];
else
// 普通创建,多例
implInstance = [[implClass alloc] init];
// 保存到上下文中
[[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
return implInstance;
}
}
// 没有实现singleton方法,直接创建
return [[implClass alloc] init];
}
从代码上看出,要实现该类是单例的话,需要实现两个方法。
- (BOOL)singleton;
- (id)shareInstance;
那么,基础服务已经看完。
接着,就按照顺序,把UserTrackServiceProtocol
也看了
UserTrackServiceProtocol
😆😆,里面没东西看。找对应的BHUserTrackViewController
BHUserTrackViewController
1 | // 注册服务 |
没了,那么继续下一个TradeServiceProtocol
。
TradeServiceProtocol
1 | // 声明一个属性 |
BHTradeViewController
打开看看,里面有代码😎😎。
突然看到没有使用@BeeHiveService进行注册?黑人问号???
他是怎么做到的?刚才createService:方法已经说明了,如果没有注册会抛出一个异常,然而并没有,那就很奇怪了。
吓得我赶紧把TradeServiceProtocol
全局搜索了一遍,获取了一个关键信息,那就是在TradeModule
类中找到了注册TradeServiceProtocol的代码
1 | - (void)modSetUp:(BHContext *)context |
这时就猜想了,这个TradeModule
跟BHTradeViewController
肯定有一腿。或者还跟TradeServiceProtocol
搞三角恋。至于他们是什么关系,这里暂不解析,后面再分析。
好了,回归到原点,在BHTradeViewController中添加了UILabel,UIButton控件,并且点击跳转到相应页面。并且传递参数itemId。
也就是说如果A跳转到B,需要传递数据给B,那么在B的服务上声明属性或者方法即可。至于回调数据使用block、delegate、NSNotification
方式即可。
比如我在TradeServiceProtocol
1 | // 声明一个block |
那么在A那里就可以设置相关属性,即可获取到回调的数据啦。
到这里已经把相关服务和类看完了。然而因为查看BHTradeViewController
,让我又发现一个新的知识,那就是TradeModule
,接下来就是分析TradeModule
了。
TradeModule
从目录中,不难发现每个模块都有一个类是对应Modelue。
- 贸易:TradeModule
- 首页:HomeModule
- 用户跟踪:UserTrackModule
- 商铺:ShopModule
实现BHModuleProtocol
首先看到类TradeModule需要实现BHModuleProtocol
协议,这里就会有两个疑问?
- 为什么要实现这个协议?
- 这个协议的作用是什么?
先保留着这些疑问继续看代码。
load方法
1 | + (void)load |
load的方法相比很少人注意到。可能平时开发也很好用到。load方法是什么意思?又是什么时候调用的呢?这里拓展一下传送门
其实归纳一句话就是主程序main运行时,就会调用load方法。
registerDynamicModule:
也就是说,程序一开始就进行了注册,那么registerDynamicModule:的实现是怎样的呢?
分析:
从方法的名称可以的到是对动态模块进行注册,而并不是像之前提到的是registerService:对服务进行注册。那么就会有一个疑问,提到动态,是不是也会有静态模块注册呢?是的,一开始文章提到的两个plist文件,就是做静态注册的,在查看TestAppDelegate那里。
一层层进入代码,最后到达addModuleFromObject: shouldTriggerInitEvent:方法里面,那就分析代码
1 | /** |
在这方法,我先不深究里面的子方法,不然绕不回来的。总的来说,确实证明之前的疑问:
- registerDynamicModule:方法是对模块类进行注册的
- 模块类TradeModule需要实现BHModuleProtocol协议
- 为什么实现BHModuleProtocol协议,因为协议里有很多方法,暂时接触到有basicModuleLevel、modulePriority两个方法,所以需要实现协议的方法来传递一些数据。
现在继续查看TradeModule的代码。
modInit:方法
1 | -(void)modInit:(BHContext *)context |
这个方式是模块初始化,什么时候调用呢?全局搜索一下找到信息:
- 找到static NSString *kSetupSelector = @”modSetUp:”;
- 搜索kSetupSelector,找到BHMSetupEvent
- 搜索BHMSetupEvent,找到刚才注册模块里的代码,其中里面的
1 | // 处理BHMSetupEvent事件 |
也就是说load方法执行的时候,就执行modInit:方法了,那么根据线索:
1 | // 如果开启触发事件 |
就可以判断协议的几个方法:
1 | - (void)modSetUp:(BHContext *)context; |
在load执行的时候就执行了。而且顺序分别是:modSetUp:->modInit:->modSplash:
注意:modSplash:是异步执行的
使用场景:modSetUp:可以对模块里的所有服务进行注册。modInit:对服务进行相关赋值需要的数据。modSplash:暂时不清楚如何使用
到此TradeModule分析完了吗?算是吧,另外的方法
1 | - (void)modSetUp:(BHContext *)context |
上面已经分析的。但是呢..😎😎
有句话我很好奇:service.itemId = @"我是单例"
;
1 | id<TradeServiceProtocol> service = [[BeeHive shareInstance] createService:@protocol(TradeServiceProtocol)]; |
我第一印象是service是单例吗?之前不是说要实现1
2
3
4
5@optional
+ (BOOL)singleton;
+ (id)shareInstance;
这些代码才是单例吗?TradeModule没有这些代码啊,难道是其他地方实现了而且我不知道的?带着疑问,我把获取TradeServiceProtocol服务相关的搜索了一遍。然后把他们的获取的对象全部打印一下地址,如果是一样的,那就是单例,实现方法还不知道。如果是不一样,那可能是作者笔误了。
打印:
1 | <BHTradeViewController: 0x7fd53cd0f7d0> |
确实是不一样,难道真是作者笔误了?然而你们也被我的印象误导了,当然这是一个好的猜想,也验证了猜想是错误的。
或者进入之前提到的createService:方法里面,看看是否进入以下代码就知道是否是单例了。
1 | // 判断类是否实现了singleton方法 |
service.itemId = @"我是单例"
只是简单的传值罢了,可能这个值让人有点误会,如果改成 service.itemId = @"我是参数"
那就很好理解了。
到这里把上面的问题解答一下:之前说TradeModule、TradeServiceProtocol、BHTradeViewController是不是搞三角关系?他们的关系是这样的:BHTradeViewController需要实现TradeServiceProtocol,TradeServiceProtocol是BHTradeViewController对外的接口。而TradeModule只是相当于一个容器,把他们放在自己这里而已,把这个模块的所有服务啊,进行注册的控制器全部放在这里,统一进行管理罢了。
TradeModule基本分析完了,那么既然看完了这个模块的,顺便把之前提高的其他模块的模块类也看了,互相对比一下
HomeModule
1、基本跟TradeModule一样,需要实现==BHModuleProtocol==基础协议。
2、
1 | // 在初始化方法中,BHContext的env属性是枚举类型,里面有: |
UserTrackModule
1、需要实现BHModuleProtocol
基础协议。
2、BH_EXPORT_MODULE(NO)
BH_EXPORT_MODULE是个宏定义,可以进入里面
1 | #define BH_EXPORT_MODULE(isAsync) \ |
也就是说是动态注册,实际实现两个方法(+(void)load
、-(BOOL)async
) 。当然也可以使用静态注册:通过BeeHive.plist文件中注册符合BHModuleProtocol协议的模块类
BOOL 该模块是否是异步加载,在启动之后第一屏内容展示之前异步执行模块的初始化方法。在想如果不是每个tab的模块,其他子模块暂时不需要的,基本需要异步加载,优化启动时间。
ShopModule
1、需要实现BHModuleProtocol
基础协议。
2、@BeeHiveMod(ShopModule)
这个宏定义实际跟之前的不一样,我也不是很能理解,这里有一个大神的文章可以借鉴一下BeeHive——一个优雅但还在完善中的解耦框架
里面有提到@BeeHiveMod这个宏是怎么运作的,是Annotation方式注册。其他也提到的方式读取plist文件=静态注册,load方式=动态注册。
BHModuleProtocol
终于到BHModuleProtocol了,二话不说看代码:
1 | //如果不去设置Level默认是Normal |
事件绑定对应的枚举是:
1 | typedef NS_ENUM(NSInteger, BHModuleEventType) |
枚举事件类型主要分成三种:
- 系统事件
- 应用事件
- 自定义事件
至于怎么触发这些事件呢,其实前面已经把
- BHMSetupEvent = 0,
- BHMInitEvent,
- BHMSplashEvent
这三个事件触发已经说了。那么接下来就说说其他事件,从哪方面入手呢?既然是提到模块协议BHModuleProtocol
,那想必也有一个对应的模块管理类,那就是BHModuleManager
。
BHModuleManager
BHModuleManager.h
头文件有两个枚举声明(BHModuleLevel, BHModuleEventType)BHModuleLevel表示模块的级别;BHModuleEventType表示对应事件的枚举元数。
1 | /// 单例 |
BHModuleManager.m
属性
1 | @interface BHModuleManager() |
方法
读取plist文件,加载本地模块loadLocalModules,必须按照指定的格式,如下图:
1 | #pragma mark - public |
到这里总结一下。可能看到代码和注释知道每个方法是干什么的,但是看完可能有点乱有点绕,对使用者或许你不需要知道底层是如何实现的,只要用就行了,但是怎么用呢?BHModuleManager
的接口已经非常清楚了,你使用的步骤总体来说就是两步:注册、调用
。这两个步骤几乎贯穿整个框架,无论你触发什么事件、需要什么内容,一定要先注册。而注册只是单纯的初始化和添加到内存,并不会主动触发事件,所以要触发事件还需要你主动。
接下来看看处理事件的几个私有方法:
1 | #pragma mark - module protocol |
除了两个特别的方法(BHMInitEvent、BHMTearDownEvent
),其他都是runtime方法调取的。这里要说明一下BHMTearDownEvent
是没有在BHAppDelegate调用的,那么就是说这个方法暂时不会系统调用。这个事件做什么用处的呢?有时候我们注册了某个模块事件,但是模块事件使用完毕,想释放掉模块的事件,就需要用到这个方法了。只要在模块类中实现modTearDown:方法,可以操作相关的卸载工作。另外目前框架还没有释放真个模块的API,期待作者后期补上。
BHServiceManager
其实BHServiceManager跟BHModuleManager类似,只是注册的东西不一样而已,BHModuleManager对整个模块的类进行BHServiceManager注册。而已BHServiceManager只对某个类进行注册。
1 | /// 是否需要抛出异常 |
BHContext
1 | typedef enum |
BHContext作为一个全局对象,所有系统事件处理,都可以在这里获取。写到这里BeeHive基本看完。当然其他功能我就不做深究,我想通过这次粗略的阅读,为以后的实践做准备。可能下次就是一个小demo的实践了。