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

image.png

组件化

本文主要介绍组件化常用三种通讯方式.

常⽤的三种组件化通讯方案

  • 组件化通信方案
    • 组件化最重要的是兄弟模块的通讯
    • 常⽤的三种方案
      • 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)
        }
复制代码

其模块间的引用关系如下图所示:

image.png

优点

  • 利用 分类 可以明确声明接口,进行编译检查
  • 实现方式轻量

缺点

  • 需要在mediatortarget中重新添加每一个接口,模块化时代码较为繁琐
  • category 中仍然引入了字符串硬编码,内部使用字典传参,一定程度上也存在和 URL 路由相同的问题
  • 无法保证使用的模块一定存在,target在修改后,使用者只能在运行时才能发现错误
  • 可能会创建过多的 target 类

CTMediator源码分析

  • 通过上面CTMediator简单示例的分类所调用的performTarget来到CTMediator中的具体实现,即performTarget:action:params:shouldCacheTarget:,主要是通过传入的name,找到对应的targetaction
- (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比较典型的三方框架就是阿里的BeeHiveBeeHive借鉴了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、只做了protocolclass 的匹配,不支持更复杂的创建方式 和依赖注入
  • 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”

    image.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 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生命周期事件!
    image.png
 一般的做法是`AppDelegate`改为`继承自BHAppDelegate`

```
@interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>
复制代码
```

2、应用事件:官方给出的流程图,其中modSetupmodInit等,可以用于编码实现各插件模块的设置与初始化。

image.png

  • 3、自定义事件

以上所有的事件都可以通过调用BHModuleManagertriggerEvent:来处理。

- (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文件

image.png
  • 同样也是在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的获取

ProtocolModule的区别在于,ProtocolModule多了一个方法,可以返回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,就能进一步的把implInstanceserviceStr对应的加到BHContextservicesByName字典里面缓存起来。这样就可以随着上下文传递了

  • 进入serviceImplClass实现,从这里可以看出 protocol和类是通过字典绑定的,protocol作为keyserviceImp(类的名字)作为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与类进行绑定,keyprotocolvalueserviceImp即类名

BeeHive辅助类

  • BHContext类:是一个单例,其内部有两个NSMutableDictionary的属性,分别是modulesByNameservicesByName。这个类主要用来保存上下文信息的。例如在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类:用来开一个线程,监听主线程是否堵塞

参考链接

--- end ---
原文地址:https://juejin.cn/post/7067743813099323423
以下文章可以做一个学习参考:
GCD面试要点
block面试要点
Runtime面试要点
RunLoop面试要点
内存管理面试要点
MVC、MVVM面试要点
网络性能优化面试要点
网络编程面试要点
KVC&KVO面试要点
数据存储面试要点
混编技术面试要点
设计模式面试要点
UI面试要点

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容