组件化
本文紧张先容组件化常用三种通讯方式.
常⽤的三种组件化通讯方案
- 组件化通讯方案
- 组件化最紧张的是兄弟模块的通讯
- 常⽤的三种方案
- URL Scheme
- Target - Action
- Protocol - Class 匹配
URL Scheme路由
- 使 URL 处理惩罚本地的跳转
- 通过中间层进⾏注册 & 调⽤ (load方法里把被调用者注册到中间层)
- 注册表⽆需使用反射
- 非懒加载 / 注册表的维护 / 参数
URL Scheme路由简朴示例
通过下面简朴示例 引入URL 路由
//MTMediator.h --- starttypedef void(^MTMediatorProcessBlock)(NSDictionary *params);+ (void)registerSchemeNSString *)scheme processBlockMTMediatorProcessBlock)processBlock;+ (void)openUrlNSString *)url paramsNSDictionary *)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)registerSchemeNSString *)scheme processBlockMTMediatorProcessBlock)processBlock{ if (scheme.length > 0 && processBlock) { [[[self class] mediatorCache] setObject:processBlock forKey:scheme]; }}+ (void)openUrlNSString *)url paramsNSDictionary *)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、注册某个URLMGJRouter.registerURLPattern("app://home") { (info) in print("info: (info)")}//2、调用路由MGJRouter.openURL("app://home")复制代码URL 路由的优点
- 极高的动态性,得当经常开展运营运动的app,比方电商
- 方便地同一管理多平台的路由规则
- 易于适配URL Scheme
URl 路由的缺点
- 传参方式有限,而且无法使用编译器举行参数范例查抄,因此全部的参数都是通过字符串转换而来
- 只实用于界面模块,不实用于通用模块
- 参数的格式不明白,是个机动的 dictionary,也必要有个地方可以查参数格式。
- 不支持storyboard
- 依赖于字符串硬编码,难以管理,蘑菇街做了个背景专门管理。
- 无法包管所使用的的模块肯定存在
- 解耦本领有限,url 的”注册”、”实现”、”使用”必须用雷同的字符规则,一旦任何一方做出修改都会导致其他方的代码失效,而且重构难度大
除了MGJRouter,尚有以下这些三方框架
- routable-ios
- JLRoutes
- HHRouter
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 *)detailViewControllerWithUrlNSString *)detailUrl;@endNS_ASSUME_NONNULL_END//MTMediator.m#import "MTMediator.h"@implementation MTMediator+ ( __kindof UIViewController *)detailViewControllerWithUrlNSString *)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] registerServiceprotocol(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 addObjectbj]; } }];}复制代码
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等,可以用于编码实现各插件模块的设置与初始化。
以上全部的事件都可以通过调用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类:用来举行盘算时间性能方面的Profiler
- BHWatchDog类:用来开一个线程,监听主线程是否堵塞
参考链接
- BeeHive —— 一个优雅但还在完满中的解耦框架
- BeeHive,一次iOS模块化解耦实践
--- end ---
原文地点:https://juejin.cn/post/7067743813099323423
以下文章可以做一个学习参考:
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技能面试要点
设计模式面试要点
UI面试要点 |