组件化
本文主要介绍组件化常用三种通讯方式
.
常⽤的三种组件化通讯方案
- 组件化通信方案
- 组件化最重要的是兄弟模块的通讯
- 常⽤的三种方案
- URL Scheme
- Target - Action
- Protocol - Class 匹配
URL Scheme路由
- 使 URL 处理本地的跳转
- 通过中间层进⾏注册 & 调⽤ (load方法里把被调用者注册到中间层)
- 注册表⽆需使用反射
- 非懒加载 / 注册表的维护 / 参数
URL Scheme路由简单示例
通过下面简单示例 引入URL 路由
//MTMediator.h --- start
typedef void(^MTMediatorProcessBlock)(NSDictionary *params);
+ (void)registerScheme:(NSString *)scheme processBlock:(MTMediatorProcessBlock)processBlock;
+ (void)openUrl:(NSString *)url params:(NSDictionary *)params;
//MTMediator.h --- end
//MTMediator.m --- start
+ (NSMutableDictionary *)mediatorCache{
static NSMutableDictionary *cacheScheme;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
cacheScheme = @{}.mutableCopy;
});
return cacheScheme;
}
+ (void)registerScheme:(NSString *)scheme processBlock:(MTMediatorProcessBlock)processBlock{
if (scheme.length > 0 && processBlock) {
[[[self class] mediatorCache] setObject:processBlock forKey:scheme];
}
}
+ (void)openUrl:(NSString *)url params:(NSDictionary *)params{
MTMediatorProcessBlock block = [[[self class] mediatorCache] objectForKey:url];
if (block) {
block(params);
}
}
//MTMediator.m --- end
//注册 --- start
+ (void)load {
[MTMediator registerScheme:@"detail://" processBlock:^(NSDictionary * _Nonnull params) {
NSString *url = (NSString *)[params objectForKey:@"url"];
UINavigationController *navigationController = (UINavigationController *)[params objectForKey:@"controller"];
MTDetailViewController *controller = [[MTDetailViewController alloc] initWithUrlString:url];
// controller.title = [NSString stringWithFormat:@"%@", @(indexPath.row)];
[navigationController pushViewController:controller animated:YES];
}];
}
//注册 --- end
//调用 --- start
//URL Scheme
[MTMediator openUrl:@"detail://" params:@{@"url":item.articleUrl,@"controller":self.navigationController}];
//调用 --- end
复制代码
说明:
- 参考了系统URL Scheme机制
- 参数传递通过dictionary,对调用者不透明
目前iOS上大部分路由工具都是基于URL匹配的,或者是根据命名约定,用runtime方法进行动态调用
这些动态化的方案的优点是实现简单,缺点是需要维护字符串表,或者依赖于命名约定,无法在编译时暴露出所有问题,需要在运行时才能发现错误。
MGJRouter
URL路由方式主要是以蘑菇街为代表的的MGJRouter
其实现思路是:
- App启动时实例化各组件模块,然后这些组件向
ModuleManager
注册Url
,有些时候不需要实例化,使用class注册 - 当组件A需要调用组件B时,向
ModuleManager
传递URL,参数跟随URL以GET方式传递,类似openURL。然后由ModuleManager负责调度组件B,最后完成任务。
// 1、注册某个URL
MGJRouter.registerURLPattern("app://home") { (info) in
print("info: (info)")
}
//2、调用路由
MGJRouter.openURL("app://home")
复制代码
URL 路由的优点
- 极高的动态性,适合经常开展运营活动的app,例如电商
- 方便地统一管理多平台的路由规则
- 易于适配URL Scheme
URl 路由的缺点
- 传参方式有限,并且无法利用编译器进行参数类型检查,因此所有的参数都是通过字符串转换而来
- 只适用于界面模块,不适用于通用模块
- 参数的格式不明确,是个灵活的 dictionary,也需要有个地方可以查参数格式。
- 不支持storyboard
- 依赖于字符串硬编码,难以管理,蘑菇街做了个后台专门管理。
- 无法保证所使用的的模块一定存在
- 解耦能力有限,url 的”注册”、”实现”、”使用”必须用相同的字符规则,一旦任何一方做出修改都会导致其他方的代码失效,并且重构难度大
除了MGJRouter
,还有以下这些三方框架
Target - Action
- 抽离业务逻辑
- 通过中间层进行调⽤
- 中间层使⽤ runtime 反射
- 中间层代码优化
Target - Action简单示例
简单示例引入
//MTMediator.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface MTMediator : NSObject
//target action
+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;
@end
NS_ASSUME_NONNULL_END
//MTMediator.m
#import "MTMediator.h"
@implementation MTMediator
+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl{
Class detailVC = NSClassFromString(@"MTDetailViewController");
UIViewController *controller = [[detailVC alloc] performSelector:NSSelectorFromString(@"initWithUrlString:") withObject:detailUrl];
return controller;
}
@end
//调用
//Target - Action
UIViewController *vc = [MTMediator detailViewControllerWithUrl:item.articleUrl];
vc.title = @"详情啊";
[self.navigationController pushViewController:vc animated:YES];
复制代码
说明:
- 硬编码方式(直接调用,不利于维护和扩展)
- perform 最多能传递2个参数,可以传入字典避免参数过多
- (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
- initWithUrlString:方法必须实现 否则找不到sel崩溃
- 业务逻辑柔合在Mediator中,可以各个模块写各自的MTMediator扩展
CTMediator
三方框架其主要的代表框架是casatwy的CTMediator
这个方案是基于OC的runtime、category特性动态获取模块,例如通过NSClassFromString
获取类并创建实例,通过performSelector + NSInvocation
动态调用方法
其实现思路是:
- 1、利用分类为路由添加新接口,在接口中通过字符串获取对应的类
- 2、通过runtime创建实例,动态调用实例的方法
CTMediator简单使用:
//******* 1、分类定义新接口
extension CTMediator{
@objc func A_showHome()->UIViewController?{
//在swift中使用时,需要传入对应项目的target名称,否则会找不到视图控制器
let params = [
kCTMediatorParamsKeySwiftTargetModuleName: "CJLBase_Example"
]
//CTMediator提供的performTarget:action:params:shouldCacheTarget:方法 通过传入name,找到对应的targer和action
if let vc = self.performTarget("A", action: "Extension_HomeViewController", params: params, shouldCacheTarget: false) as? UIViewController{
return vc
}
return nil
}
}
//******* 2、模块提供者提供target-action的调用方式(对外需要加上public关键字)
class Target_A: NSObject {
@objc func Action_Extension_HomeViewController(_ params: [String: Any])->UIViewController{
let home = HomeViewController()
return home
}
}
//******* 3、使用
if let vc = CTMediator.sharedInstance().A_showHome() {
self.navigationController?.pushViewController(vc, animated: true)
}
复制代码
其模块间的引用关系如下图所示:
优点
- 利用
分类
可以明确声明接口,进行编译检查 - 实现方式
轻量
缺点
- 需要在
mediator
和target
中重新添加每一个接口,模块化时代码较为繁琐 - 在
category
中仍然引入了字符串硬编码
,内部使用字典传参,一定程度上也存在和 URL 路由相同的问题 - 无法保证使用的模块一定存在,target在修改后,使用者只能在运行时才能发现错误
- 可能会创建过多的 target 类
CTMediator源码分析
- 通过上面CTMediator简单示例的分类所调用的
performTarget
来到CTMediator
中的具体实现,即performTarget:action:params:shouldCacheTarget:
,主要是通过传入的name,找到对应的target
和action
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
if (targetName == nil || actionName == nil) {
return nil;
}
//在swift中使用时,需要传入对应项目的target名称,否则会找不到视图控制器
NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
// generate target 生成target
NSString *targetClassString = nil;
if (swiftModuleName.length > 0) {
//swift中target文件名拼接
targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
} else {
//OC中target文件名拼接
targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
}
//缓存中查找target
NSObject *target = [self safeFetchCachedTarget:targetClassString];
//缓存中没有target
if (target == nil) {
//通过字符串获取对应的类
Class targetClass = NSClassFromString(targetClassString);
//创建实例
target = [[targetClass alloc] init];
}
// generate action 生成action方法名称
NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
//通过方法名字符串获取对应的sel
SEL action = NSSelectorFromString(actionString);
if (target == nil) {
// 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
return nil;
}
//是否需要缓存
if (shouldCacheTarget) {
[self safeSetCachedTarget:target key:targetClassString];
}
//是否响应sel
if ([target respondsToSelector:action]) {
//动态调用方法
return [self safePerformAction:action target:target params:params];
} else {
// 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
SEL action = NSSelectorFromString(@"notFound:");
if ([target respondsToSelector:action]) {
return [self safePerformAction:action target:target params:params];
} else {
// 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
[self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
@synchronized (self) {
[self.cachedTarget removeObjectForKey:targetClassString];
}
return nil;
}
}
}
复制代码
- 进入
safePerformAction:target:params:
实现,主要是通过invocation
进行参数传递+消息转发
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
//获取方法签名
NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
if(methodSig == nil) {
return nil;
}
//获取方法签名中的返回类型,然后根据返回值完成参数传递
const char* retType = [methodSig methodReturnType];
//void类型
if (strcmp(retType, @encode(void)) == 0) {
...
}
//...省略其他类型的判断
}
复制代码
更多关于casatwy的CTMediator介绍 可以参见此篇文章解读
Protocol - Class
- 增加 Protocol Wrapper层 (中间件先注册Protocol和Class对应关系,将
protocol
和对应的类
进行字典匹配
) - 中间件返回 Protocol 对应的 Class,然后
动态创建实例
- 解决硬编码的问题
Protocol - Class简单示例
//具体的Protocol
//MTMediator.h --- start
@protocol MTDetailViewControllerProtocol <NSObject>
+ (__kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;
@end
@interface MTMediator : NSObject
+ (void)registerProtol:(Protocol *)protocol class:(Class)cls;
+ (Class)classForProtocol:(Protocol *)protocol;
@end
//MTMediator.h --- end
//MTMediator.m --- start
+ (void)registerProtol:(Protocol *)protocol class:(Class)cls{
if (protocol && cls) {
[[[self class] mediatorCache] setObject:cls forKey:NSStringFromProtocol(protocol)];
}
}
+ (Class)classForProtocol:(Protocol *)protocol{
return [[[self class] mediatorCache] objectForKey:NSStringFromProtocol(protocol)];
}
//MTMediator.m --- end
//被调用
//MTDetailViewController.h --- start
@protocol MTDetailViewControllerProtocol;
@interface MTDetailViewController : UIViewController<MTDetailViewControllerProtocol>
@end
//MTDetailViewController.h --- end
//MTDetailViewController.m --- start
+ (void)load {
[MTMediator registerProtol: @protocol(MTDetailViewControllerProtocol) class:[self class]];
}
#pragma mark - MTDetailViewControllerProtocol
+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl{
return [[MTDetailViewController alloc]initWithUrlString:detailUrl];
}
//MTDetailViewController.m --- end
//调用
Class cls = [MTMediator classForProtocol: @protocol(MTDetailViewControllerProtocol)];
if ([cls respondsToSelector: @selector(detailViewControllerWithUrl:)]) {
[self.navigationController pushViewController:[cls detailViewControllerWithUrl:item.articleUrl] animated:YES];
}
复制代码
说明:
- 被调用者先在中间件注册Protocol和Class对应关系,对外只暴漏Protocol
BeeHive
protocol比较典型的三方框架就是阿里的BeeHive。BeeHive
借鉴了Spring Service、Apache DSO的架构理念,采用AOP+扩展App生命周期API
形式,将业务功能
、基础功能
模块以模块方式以解决大型应用中的复杂问题,并让模块之间以Service形式调用
,将复杂问题切分,以AOP方式模块化服务。
BeeHive 核心思想
- 1、各个模块间调用从直接调用对应模块,变成调用
Service
的形式,避免了直接依赖。 - 2、App生命周期的分发,将耦合在
AppDelegate
中逻辑拆分,每个模块以微应用的形式独立存在。
示例如下:
//******** 1、注册
[[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]];
//******** 2、使用
#import "BHService.h"
id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
复制代码
优点
- 1、利用接口调用,实现了参数传递时的类型安全
- 2、直接使用模块的protocol接口,无需再重复封装
缺点
- 1、用框架来创建所有对象,创建方式不同,即不支持外部传入参数
- 2、用
OC runtime
创建对象,不支持swift - 3、只做了
protocol
和class
的匹配,不支持更复杂的创建方式 和依赖注入 - 4、无法保证所使用的protocol 一定存在对应的模块,也无法直接判断某个protocol是否能用于获取模块
除了BeeHive
,还有Swinject
BeeHive 模块注册
在BeeHive
主要是通过BHModuleManager
来管理各个模块的。BHModuleManager
中只会管理已经被注册过的模块。
BeeHive提供了三种不同的注册形式,annotation
,静态plist
,动态注册
。Module、Service之间没有关联,每个业务模块可以单独实现Module或者Service的功能。
Annotation方式注册
这种方式主要是通过BeeHiveMod
宏进行Annotation
标记
//***** 使用
BeeHiveMod(ShopModule)
//***** BeeHiveMod的宏定义
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";
//***** BeeHiveDATA的宏定义
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))
//***** 全部转换出来后为下面的格式 以name是ShopModule为例
char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";
复制代码
这里针对__attribute
需要说明以下几点
- 第一个参数
used
:用来修饰函数,被used修饰以后,意味着即使函数没有被引用,在Release下也不会被优化。如果不加这个修饰,那么Release环境链接器下会去掉没有被引用的段。 - 通过使用
__attribute__((section("name")))
来指明哪个段。数据则用__attribute__((used))
来标记,防止链接器会优化删除未被使用的段,然后将模块注入到__DATA
中
此时Module已经被存储到Mach-O文件的特殊段中,那么如何取呢?
-
进入
BHReadConfiguration
方法,主要是通过Mach-O
找到存储的数据段,取出放入数组中NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp) { NSMutableArray *configs = [NSMutableArray array]; unsigned long size = 0; #ifndef __LP64__ // 找到之前存储的数据段(Module找BeehiveMods段 和 Service找BeehiveServices段)的一片内存 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){ char *string = (char*)memory[idx]; NSString *str = [NSString stringWithUTF8String:string]; if(!str)continue; BHLog(@"config = %@", str); if(str) [configs addObject:str]; } return configs; } 复制代码
-
注册的
dyld_callback
回调如下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]; } } } //register services 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)]; } } } } } __attribute__((constructor)) void initProphet() { //_dyld_register_func_for_add_image函数是用来注册dyld加载镜像时的回调函数,在dyld加载镜像时,会执行注册过的回调函数 _dyld_register_func_for_add_image(dyld_callback); } 复制代码
读取本地Pilst文件
-
首先,需要设置好路径
[BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可选,默认为BeeHive.bundle/BeeHive.plist 复制代码
-
创建plist文件,
Plist
文件的格式也是数组中包含多个字典。字典里面有两个Key,一个是@"moduleLevel"
,另一个是@"moduleClass"
。注意根
的数组的名字叫@“moduleClasses”
。
-
进入
loadLocalModules
方法,主要是从Plist
里面取出数组,然后把数组加入到BHModuleInfos
数组里面。//初始化context时,加载Modules和Services -(void)setContext:(BHContext *)context { _context = context; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self loadStaticServices]; [self loadStaticModules]; }); } 👇 //加载modules - (void)loadStaticModules { // 读取本地plist文件里面的Module,并注册到BHModuleManager的BHModuleInfos数组中 [[BHModuleManager sharedManager] loadLocalModules]; //注册所有modules,在内部根据优先级进行排序 [[BHModuleManager sharedManager] registedAllModules]; } 👇 - (void)loadLocalModules { //plist文件路径 NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"]; //判断文件是否存在 if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) { return; } //读取整个文件[@"moduleClasses" : 数组] NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath]; //通过moduleClasses key读取 数组 [[@"moduleClass":"aaa", @"moduleLevel": @"bbb"], [...]] NSArray<NSDictionary *> *modulesArray = [moduleList objectForKey:kModuleArrayKey]; NSMutableDictionary<NSString *, NSNumber *> *moduleInfoByClass = @{}.mutableCopy; //遍历数组 [self.BHModuleInfos enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [moduleInfoByClass setObject:@1 forKey:[obj objectForKey:kModuleInfoNameKey]]; }]; [modulesArray enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { if (!moduleInfoByClass[[obj objectForKey:kModuleInfoNameKey]]) { //存储到 BHModuleInfos 中 [self.BHModuleInfos addObject:obj]; } }]; } 复制代码
load方法注册
该方法注册Module
就是在Load
方法里面注册Module的类
+ (void)load
{
[BeeHive registerDynamicModule:[self class]];
}
复制代码
-
进入
registerDynamicModule
实现+ (void)registerDynamicModule:(Class)moduleClass { [[BHModuleManager sharedManager] registerDynamicModule:moduleClass]; } 👇 - (void)registerDynamicModule:(Class)moduleClass { [self registerDynamicModule:moduleClass shouldTriggerInitEvent:NO]; } 👇 - (void)registerDynamicModule:(Class)moduleClass shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent { [self addModuleFromObject:moduleClass shouldTriggerInitEvent:shouldTriggerInitEvent]; } 复制代码
-
和Annotation方式注册的
dyld_callback
回调一样,最终会走到addModuleFromObject:shouldTriggerInitEvent:
方法中- (void)addModuleFromObject:(id)object shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent { Class class; NSString *moduleName = nil; if (object) { class = object; 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)]) { 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.BHModuleInfos addObject:moduleInfo]; 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) { 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; } }]; [self registerEventsByModuleInstance:moduleInstance]; if (shouldTriggerInitEvent) { [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]; }); } } } 复制代码
load方法,还可以使用BH_EXPORT_MODULE
宏代替
#define BH_EXPORT_MODULE(isAsync) \
+ (void)load { [BeeHive registerDynamicModule:[self class]]; } \
-(BOOL)async { return [[NSString stringWithUTF8String:#isAsync] boolValue];}
复制代码
BH_EXPORT_MODULE
宏里面可以传入一个参数,代表是否异步加载Module模块
,如果是YES
就是异步加载
,如果是NO
就是同步加载
。
BeeHive 模块事件
BeeHive会给每个模块提供生命周期事件,用于与BeeHive宿主环境进行必要信息交互,感知模块生命周期的变化。
BeeHive各个模块会收到一些事件。在BHModuleManager
中,所有的事件被定义成了BHModuleEventType
枚举。如下所示,其中有2个事件很特殊,一个是BHMInitEvent
,一个是BHMTearDownEvent
typedef NS_ENUM(NSInteger, BHModuleEventType)
{
//设置Module模块
BHMSetupEvent = 0,
//用于初始化Module模块,例如环境判断,根据不同环境进行不同初始化
BHMInitEvent,
//用于拆除Module模块
BHMTearDownEvent,
BHMSplashEvent,
BHMQuickActionEvent,
BHMWillResignActiveEvent,
BHMDidEnterBackgroundEvent,
BHMWillEnterForegroundEvent,
BHMDidBecomeActiveEvent,
BHMWillTerminateEvent,
BHMUnmountEvent,
BHMOpenURLEvent,
BHMDidReceiveMemoryWarningEvent,
BHMDidFailToRegisterForRemoteNotificationsEvent,
BHMDidRegisterForRemoteNotificationsEvent,
BHMDidReceiveRemoteNotificationEvent,
BHMDidReceiveLocalNotificationEvent,
BHMWillPresentNotificationEvent,
BHMDidReceiveNotificationResponseEvent,
BHMWillContinueUserActivityEvent,
BHMContinueUserActivityEvent,
BHMDidFailToContinueUserActivityEvent,
BHMDidUpdateUserActivityEvent,
BHMHandleWatchKitExtensionRequestEvent,
BHMDidCustomEvent = 1000
};
复制代码
主要分为三种
- 1、
系统事件
:主要是指Application生命周期事件
!
一般的做法是`AppDelegate`改为`继承自BHAppDelegate`
```
@interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>
复制代码
```
2、应用事件
:官方给出的流程图,其中modSetup
、modInit
等,可以用于编码实现各插件模块的设置与初始化。
- 3、
自定义事件
以上所有的事件都可以通过调用BHModuleManager
的triggerEvent:
来处理。
- (void)triggerEvent:(NSInteger)eventType
{
[self triggerEvent:eventType withCustomParam:nil];
}
👇
- (void)triggerEvent:(NSInteger)eventType
withCustomParam:(NSDictionary *)customParam {
[self handleModuleEvent:eventType forTarget:nil withCustomParam:customParam];
}
👇
#pragma mark - module protocol
- (void)handleModuleEvent:(NSInteger)eventType
forTarget:(id<BHModuleProtocol>)target
withCustomParam:(NSDictionary *)customParam
{
switch (eventType) {
//初始化事件
case BHMInitEvent:
//special
[self handleModulesInitEventForTarget:nil withCustomParam :customParam];
break;
//析构事件
case BHMTearDownEvent:
//special
[self handleModulesTearDownEventForTarget:nil withCustomParam:customParam];
break;
//其他3类事件
default: {
NSString *selectorStr = [self.BHSelectorByEvent objectForKey:@(eventType)];
[self handleModuleEvent:eventType forTarget:nil withSeletorStr:selectorStr andCustomParam:customParam];
}
break;
}
}
复制代码
从上面的代码中可以发现,除去BHMInitEvent
初始化事件和BHMTearDownEvent
拆除Module事件这两个特殊事件以外,所有的事件都是调用的handleModuleEvent:forTarget:withSeletorStr:andCustomParam:
方法,其内部实现主要是遍历 moduleInstances
实例数组,调用performSelector:withObject:
方法实现对应方法调用
- (void)handleModuleEvent:(NSInteger)eventType
forTarget:(id<BHModuleProtocol>)target
withSeletorStr:(NSString *)selectorStr
andCustomParam:(NSDictionary *)customParam
{
BHContext *context = [BHContext shareInstance].copy;
context.customParam = customParam;
context.customEvent = eventType;
if (!selectorStr.length) {
selectorStr = [self.BHSelectorByEvent objectForKey:@(eventType)];
}
SEL seletor = NSSelectorFromString(selectorStr);
if (!seletor) {
selectorStr = [self.BHSelectorByEvent objectForKey:@(eventType)];
seletor = NSSelectorFromString(selectorStr);
}
NSArray<id<BHModuleProtocol>> *moduleInstances;
if (target) {
moduleInstances = @[target];
} else {
moduleInstances = [self.BHModulesByEvent objectForKey:@(eventType)];
}
//遍历 moduleInstances 实例数组,调用performSelector:withObject:方法实现对应方法调用
[moduleInstances enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
if ([moduleInstance respondsToSelector:seletor]) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
//进行方法调用
[moduleInstance performSelector:seletor withObject:context];
#pragma clang diagnostic pop
[[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- %@", [moduleInstance class], NSStringFromSelector(seletor)]];
}
}];
}
复制代码
注意:这里所有的
Module
必须是遵循BHModuleProtocol
的,否则无法接收到这些事件的消息。
BeeHive Protocol注册
在BeeHive中是通过BHServiceManager
来管理各个Protocol
的。BHServiceManager
中只会管理已经被注册过的Protocol
。
注册Protocol
的方式总共有三种,和注册Module
是一样一一对应的
Annotation方式注册
//****** 1、通过BeeHiveService宏进行Annotation标记
BeeHiveService(HomeServiceProtocol,BHViewController)
//****** 2、宏定义
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ ""#servicename"" : ""#impl""}";
//****** 3、转换后的格式,也是将其存储到特殊的段
char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ """HomeServiceProtocol""" : """BHViewController"""}";
复制代码
读取本地plist文件
-
首先同Module一样,需要先设置好路径
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService"; 复制代码
设置plist文件
-
同样也是在
setContext
时注册services
//加载services -(void)loadStaticServices { [BHServiceManager sharedManager].enableException = self.enableException; [[BHServiceManager sharedManager] registerLocalServices]; } 👇 - (void)registerLocalServices { NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName; //获取plist文件路径 NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"]; if (!plistPath) { return; } NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath]; [self.lock lock]; //遍历并存储到allServicesDict中 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]; } 复制代码
load方法注册
在Load方法里面注册Protocol
协议,主要是调用BeeHive
里面的registerService:service:
完成protocol
的注册
+ (void)load
{
[[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}
👇
- (void)registerService:(Protocol *)proto service:(Class) serviceClass
{
[[BHServiceManager sharedManager] registerService:proto implClass:serviceClass];
}
复制代码
到此,三种方式注册就完成了
Protocol的获取
Protocol
与Module
的区别在于,Protocol
比Module
多了一个方法,可以返回Protocol实例对象
- (id)createService:(Protocol *)proto;
{
return [[BHServiceManager sharedManager] createService:proto];
}
👇
- (id)createService:(Protocol *)service
{
return [self createService:service withServiceName:nil];
}
👇
- (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName {
return [self createService:service withServiceName:serviceName shouldCache:YES];
}
👇
- (id)createService:(Protocol *)service withServiceName:(NSString *)serviceName shouldCache:(BOOL)shouldCache {
if (!serviceName.length) {
serviceName = NSStringFromProtocol(service);
}
id implInstance = nil;
//判断protocol是否已经注册过
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;
}
}
//获取类后,然后响应下层的方法
Class implClass = [self serviceImplClass:service];
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];
}
复制代码
createService
会先检查Protocol协议是否是注册过的。然后接着取出字典里面对应的Class,如果实现了shareInstance
方法,那么就创建一个单例对象
,如果没有,那么就创建一个实例对象
。如果还实现了singleton,就能进一步的把implInstance
和serviceStr
对应的加到BHContext
的servicesByName
字典里面缓存
起来。这样就可以随着上下文传递了
-
进入
serviceImplClass
实现,从这里可以看出 protocol和类是通过字典
绑定的,protocol
作为key
,serviceImp
(类的名字)作为value
- (Class)serviceImplClass:(Protocol *)service { //通过字典将 协议 和 类 绑定,其中协议作为key,serviceImp(类的名字)作为value NSString *serviceImpl = [[self servicesDict] objectForKey:NSStringFromProtocol(service)]; if (serviceImpl.length > 0) { return NSClassFromString(serviceImpl); } return nil; } 复制代码
Module & Protocol
这里简单总结下:
对于
Module
:数组存储对于
Protocol
:通过字典将protocol
与类进行绑定,key
为protocol
,value
为serviceImp
即类名
BeeHive辅助类
-
BHContext
类:是一个单例,其内部有两个NSMutableDictionary
的属性,分别是modulesByName
和servicesByName
。这个类主要用来保存上下文信息的。例如在application:didFinishLaunchingWithOptions:
的时候,就可以初始化大量的上下文信息//保存信息 [BHContext shareInstance].application = application; [BHContext shareInstance].launchOptions = launchOptions; [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可选,默认为BeeHive.bundle/BeeHive.plist [BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService"; 复制代码
BHConfig
类:是一个单例,其内部有一个NSMutableDictionary
类型的config
属性,该属性维护了一些动态的环境变量,作为BHContext
的补充存在BHTimeProfiler
类:用来进行计算时间性能方面的ProfilerBHWatchDog
类:用来开一个线程,监听主线程是否堵塞
参考链接
--- end ---
原文地址:https://juejin.cn/post/7067743813099323423
以下文章可以做一个学习参考:
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点