iOS底层原理35组件化二组件间
本文主要讲组件化之间是如何通讯的 组件化通讯方案目前主流的主要有以下三种方式: 1、URL路由 2、target-action 3、protocol匹配 URL路由目前iOS上大部分路由工具都是基于URL匹配的,或者是根据命名约定,用runtime方法进行动态调用 这些动态化的方案的优点是实现简单,缺点是需要维护字符串表,或者依赖于命名约定,无法在编译时暴露出所有问题,需要在运行时才能发现错误。 URL路由方式主要是以蘑菇街为代表的的MGJRouter 其实现思路是: App启动时实例化各组件模块,然后这些组件向ModuleManager注册Url,有些时候不需要实例化,使用class注册 当组件A需要调用组件B时,向ModuleManager传递URL,参数跟随URL以GET方式传递,类似openURL。然后由ModuleManager负责调度组件B,最后完成任务。 //1、注册某个URLMGJRouter.registerURLPattern("app://home"){(info)inprint("info:\(info)")}//2、调用路由MGJRouter.openURL("app://home") URL路由的优点 极高的动态性,适合经常开展运营活动的app,例如电商 方便地统一管理多平台的路由规则 易于适配URLScheme URl路由的缺点 传参方式有限,并且无法利用编译器进行参数类型检查,因此所有的参数都是通过字符串转换而来 只适用于界面模块,不适用于通用模块 参数的格式不明确,是个灵活的dictionary,也需要有个地方可以查参数格式。 不支持storyboard 依赖于字符串硬编码,难以管理,蘑菇街做了个后台专门管理。 无法保证所使用的的模块一定存在 解耦能力有限,url的”注册”、”实现”、”使用”必须用相同的字符规则,一旦任何一方做出修改都会导致其他方的代码失效,并且重构难度大 除了CTMediator,还有以下这些三方框架 routable-ios JLRoutes HHRouter target-action这个方案是基于OC的runtime、category特性动态获取模块,例如通过NSClassFromString获取类并创建实例,通过performSelector+NSInvocation动态调用方法 其主要的代表框架是casatwy的CTMediator 其实现思路是: 1、利用分类为路由添加新接口,在接口中通过字符串获取对应的类 2、通过runtime创建实例,动态调用实例的方法 //*******1、分类定义新接口extensionCTMediator{ objcfuncA_showHome()-UIViewController?{letparams=[kCTMediatorParamsKeySwiftTargetModuleName:"CJLBase_Example"]ifletvc=self.performTarget("A",action:"Extension_HomeViewController",params:params,shouldCacheTarget:false)as?UIViewController{returnvc}returnnil}}//*******2、模块提供者提供target-action的调用方式(对外需要加上public关键字)classTarget_A:NSObject{objcfuncAction_Extension_HomeViewController(_params:[String:Any])-UIViewController{lethome=HomeViewController()returnhome}}//*******3、使用ifletvc=CTMediator.sharedInstance().A_showHome(){self.navigationController?.pushViewController(vc,animated:true)}其模块间的引用关系如下图所示 图示 优点 利用分类可以明确声明接口,进行编译检查 实现方式轻量 缺点 需要在mediator和target中重新添加每一个接口,模块化时代码较为繁琐 在category中仍然引入了字符串硬编码,内部使用字典传参,一定程度上也存在和URL路由相同的问题 无法保证使用的模块一定存在,target在修改后,使用者只能在运行时才能发现错误 可能会创建过多的target类 CTMediator源码分析 通过分类中调用的performTarget来到CTMediator中的具体实现,即performTarget:action:params:shouldCacheTarget:,主要是通过传入的name,找到对应的target和action -(id)performTarget:(NSString*)targetNameaction:(NSString*)actionNameparams:(NSDictionary*)paramsshouldCacheTarget:(BOOL)shouldCacheTarget{if(targetName==nil actionName==nil){returnnil;}//在swift中使用时,需要传入对应项目的target名称,否则会找不到视图控制器NSString*swiftModuleName=params[kCTMediatorParamsKeySwiftTargetModuleName];//generatetarget生成targetNSString*targetClassString=nil;if(swiftModuleName.length0){//swift中target文件名拼接targetClassString=[NSStringstringWithFormat: "%.Target_%",swiftModuleName,targetName];}else{//OC中target文件名拼接targetClassString=[NSStringstringWithFormat:"Target_%",targetName];}//缓存中查找targetNSObject*target=[selfsafeFetchCachedTarget:targetClassString];//缓存中没有targetif(target==nil){//通过字符串获取对应的类ClasstargetClass=NSClassFromString(targetClassString);//创建实例target=[[targetClassalloc]init];}//generateaction生成action方法名称NSString*actionString=[NSStringstringWithFormat:"Action_%:",actionName];//通过方法名字符串获取对应的selSELaction=NSSelectorFromString(actionString);if(target==nil){//这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的[selfNoTargetActionResponseWithTargetString:targetClassStringselectorString:actionStringoriginParams:params];returnnil;}//是否需要缓存if(shouldCacheTarget){[selfsafeSetCachedTarget:targetkey:targetClassString];}//是否响应selif([targetrespondsToSelector:action]){//动态调用方法return[selfsafePerformAction:actiontarget:targetparams:params];}else{//这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理SELaction=NSSelectorFromString("notFound:");if([targetrespondsToSelector:action]){return[selfsafePerformAction:actiontarget:targetparams:params];}else{//这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。[selfNoTargetActionResponseWithTargetString:targetClassStringselectorString:actionStringoriginParams:params];synchronized(self){[self.cachedTargetremoveObjectForKey:targetClassString];}returnnil;}}}进入safePerformAction:target:params:实现,主要是通过invocation进行参数传递+消息转发 -(id)safePerformAction:(SEL)actiontarget:(NSObject*)targetparams:(NSDictionary*)params{//获取方法签名NSMethodSignature*methodSig=[targetmethodSignatureForSelector:action];if(methodSig==nil){returnnil;}//获取方法签名中的返回类型,然后根据返回值完成参数传递constchar*retType=[methodSigmethodReturnType];//void类型if(strcmp(retType, encode(void))==0){...}//...省略其他类型的判断}protocolclassprotocol匹配的实现思路是: 1、将protocol和对应的类进行字典匹配 2、通过用protocol获取class,在动态创建实例 protocol比较典型的三方框架就是阿里的BeeHive。BeeHive借鉴了SpringService、ApacheDSO的架构理念,采用AOP+扩展App生命周期API形式,将业务功能、基础功能模块以模块方式以解决大型应用中的复杂问题,并让模块之间以Service形式调用,将复杂问题切分,以AOP方式模块化服务。 BeeHive核心思想 1、各个模块间调用从直接调用对应模块,变成调用Service的形式,避免了直接依赖。 2、App生命周期的分发,将耦合在AppDelegate中逻辑拆分,每个模块以微应用的形式独立存在。 示例如下(本想用swift写的,但是有点问题,暂时用OC写): //********1、注册[[BeeHiveshareInstance]registerService: protocol(HomeServiceProtocol)service:[BHViewControllerclass]];//********2、使用#import"BHService.h"idHomeServiceProtocolhomeVc=[[BeeHiveshareInstance]createService:protocol(HomeServiceProtocol)];优点 1、利用接口调用,实现了参数传递时的类型安全 2、直接使用模块的protocol接口,无需再重复封装 缺点 1、用框架来创建所有对象,创建方式不同,即不支持外部传入参数 2、用OCruntime创建对象,不支持swift 3、只做了protocol和class的匹配,不支持更复杂的创建方式和依赖注入 4、无法保证所使用的protocol一定存在对应的模块,也无法直接判断某个protocol是否能用于获取模块 除了BeeHive,还有Swinject BeeHive模块注册在BeeHive主要是通过BHModuleManager来管理各个模块的。BHModuleManager中只会管理已经被注册过的模块。 BeeHive提供了三种不同的调用形式,静态plist,动态注册,annotation。Module、Service之间没有关联,每个业务模块可以单独实现Module或者Service的功能。 1、Annotation方式注册这种方式主要是通过BeeHiveMod宏进行Annotation标记 //*****使用BeeHiveMod(ShopModule)//*****BeeHiveMod的宏定义#defineBeeHiveMod(name)\classBeeHive;char*k##name##_modBeeHiveDATA(BeehiveMods)=""#name"";//*****BeeHiveDATA的宏定义#defineBeeHiveDATA(sectname)__attribute((used,section("__DATA,"#sectname"")))//*****全部转换出来后为下面的格式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找到存储的数据段,取出放入数组中 NSArrayNSString**BHReadConfiguration(char*sectionName,conststructmach_header*mhp){NSMutableArray*configs=[NSMutableArrayarray];unsignedlongsize=0;#ifndef__LP64__//找到之前存储的数据段(Module找BeehiveMods段和Service找BeehiveServices段)的一片内存uintptr_t*memory=(uintptr_t*)getsectiondata(mhp,SEG_DATA,sectionName,size);#elseconststructmach_header_64*mhp64=(conststructmach_header_64*)mhp;uintptr_t*memory=(uintptr_t*)getsectiondata(mhp64,SEG_DATA,sectionName,size);#endifunsignedlongcounter=size/sizeof(void*);//把特殊段里面的数据都转换成字符串存入数组中for(intidx=0;idxcounter;++idx){char*string=(char*)memory[idx];NSString*str=[NSStringstringWithUTF8String:string];if(!str)continue;BHLog( "config=%",str);if(str)[configsaddObject:str];}returnconfigs;}2、读取本地Pilst文件 首先,需要设置好路径 [BHContextshareInstance].moduleConfigName= "BeeHive.bundle/BeeHive";//可选,默认为BeeHive.bundle/BeeHive.plist创建plist文件,Plist文件的格式也是数组中包含多个字典。字典里面有两个Key,一个是 "moduleLevel",另一个是"moduleClass"。注意根的数组的名字叫“moduleClasses”。Moduleplist图示 进入loadLocalModules方法,主要是从Plist里面取出数组,然后把数组加入到BHModuleInfos数组里面。 //初始化context时,加载Modules和Services-(void)setContext:(BHContext*)context{_context=context;staticdispatch_once_tonceToken;dispatch_once(onceToken,^{[selfloadStaticServices];[selfloadStaticModules];});}??//加载modules-(void)loadStaticModules{//读取本地plist文件里面的Module,并注册到BHModuleManager的BHModuleInfos数组中[[BHModuleManagersharedManager]loadLocalModules];//注册所有modules,在内部根据优先级进行排序[[BHModuleManagersharedManager]registedAllModules];}??-(void)loadLocalModules{//plist文件路径NSString*plistPath=[[NSBundlemainBundle]pathForResource:[BHContextshareInstance].moduleConfigNameofType: "plist"];//判断文件是否存在if(![[NSFileManagerdefaultManager]fileExistsAtPath:plistPath]){return;}//读取整个文件["moduleClasses":数组]NSDictionary*moduleList=[[NSDictionaryalloc]initWithContentsOfFile:plistPath];//通过moduleClasseskey读取数组[["moduleClass":"aaa","moduleLevel":"bbb"],[...]]NSArrayNSDictionary**modulesArray=[moduleListobjectForKey:kModuleArrayKey];NSMutableDictionaryNSString*,NSNumber**moduleInfoByClass={}.mutableCopy;//遍历数组[self.BHModuleInfosenumerateObjectsUsingBlock:^(NSDictionary*_Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop){[moduleInfoByClasssetObject:1forKey:[objobjectForKey:kModuleInfoNameKey]];}];[modulesArrayenumerateObjectsUsingBlock:^(NSDictionary*_Nonnullobj,NSUIntegeridx,BOOL*_Nonnullstop){if(!moduleInfoByClass[[objobjectForKey:kModuleInfoNameKey]]){//存储到BHModuleInfos中[self.BHModuleInfosaddObject:obj];}}];}3、load方法注册 该方法注册Module就是在Load方法里面注册Module的类 +(void)load{[BeeHiveregisterDynamicModule:[selfclass]];} 进入registerDynamicModule实现 +(void)registerDynamicModule:(Class)moduleClass{[[BHModuleManagersharedManager]registerDynamicModule:moduleClass];}??-(void)registerDynamicModule:(Class)moduleClass{[selfregisterDynamicModule:moduleClassshouldTriggerInitEvent:NO];}??-(void)registerDynamicModule:(Class)moduleClassshouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent{[selfaddModuleFromObject:moduleClassshouldTriggerInitEvent:shouldTriggerInitEvent];} 其底层还是同第一种方式一样,最终会走到addModuleFromObject:shouldTriggerInitEvent:方法中 load方法,还可以使用BH_EXPORT_MODULE宏代替 #defineBH_EXPORT_MODULE(isAsync)\+(void)load{[BeeHiveregisterDynamicModule:[selfclass]];}\-(BOOL)async{return[[NSStringstringWithUTF8String:#isAsync]boolValue];} BH_EXPORT_MODULE宏里面可以传入一个参数,代表是否异步加载Module模块,如果是YES就是异步加载,如果是NO就是同步加载。 2、BeeHive模块事件BeeHive会给每个模块提供生命周期事件,用于与BeeHive宿主环境进行必要信息交互,感知模块生命周期的变化。 BeeHive各个模块会收到一些事件。在BHModuleManager中,所有的事件被定义成了BHModuleEventType枚举。如下所示,其中有2个事件很特殊,一个是BHMInitEvent,一个是BHMTearDownEvent typedefNS_ENUM(NSInteger,BHModuleEventType){//设置Module模块BHMSetupEvent=0,//用于初始化Module模块,例如环境判断,根据不同环境进行不同初始化BHMInitEvent,//用于拆除Module模块BHMTearDownEvent,BHMSplashEvent,BHMQuickActionEvent,BHMWillResignActiveEvent,BHMDidEnterBackgroundEvent,BHMWillEnterForegroundEvent,BHMDidBe |
转载请注明地址:http://www.tanhuaa.com/jsth/7829.html
- 上一篇文章: 春天诗会20蓝蓝潘洗尘
- 下一篇文章: 精品重温用上DATEDIF,您永不再