iOS:组件化的三种通讯方案

藏宝库编辑 2024-9-22 14:36:45 45 0 来自 中国
组件化

本文紧张先容组件化常用三种通讯方式.
常⽤的三种组件化通讯方案


  • 组件化通讯方案

    • 组件化最紧张的是兄弟模块的通讯
    • 常⽤的三种方案

      • 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”。

    3.png


  • 进入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生命周期事件! 4.png
一样平常的做法是`AppDelegate`改为`继承自BHAppDelegate````@interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>复制代码```2、应用事件:官方给出的流程图,此中modSetup、modInit等,可以用于编码实现各插件模块的设置与初始化。
5.png

  • 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类:用来举行盘算时间性能方面的Profiler
  • BHWatchDog类:用来开一个线程,监听主线程是否堵塞
参考链接


  • BeeHive —— 一个优雅但还在完满中的解耦框架
  • BeeHive,一次iOS模块化解耦实践
--- end ---
原文地点:https://juejin.cn/post/7067743813099323423
以下文章可以做一个学习参考:
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技能面试要点
设计模式面试要点
UI面试要点
您需要登录后才可以回帖 登录 | 立即注册

Powered by CangBaoKu v1.0 小黑屋藏宝库It社区( 冀ICP备14008649号 )

GMT+8, 2024-10-18 20:22, Processed in 0.182254 second(s), 35 queries.© 2003-2025 cbk Team.

快速回复 返回顶部 返回列表