iOS:組件化的三種通訊方案

image.png

組件化

本文主要介紹組件化常用三種通訊方式.

常?的三種組件化通訊方案

  • 組件化通信方案
    • 組件化最重要的是兄弟模塊的通訊
    • 常?的三種方案
      • URL Scheme
      • Target - Action
      • Protocol - Class 匹配

URL Scheme路由

  • 使 URL 處理本地的跳轉(zhuǎn)
  • 通過(guò)中間層進(jìn)?注冊(cè) & 調(diào)? (load方法里把被調(diào)用者注冊(cè)到中間層)
  • 注冊(cè)表?需使用反射
  • 非懶加載 / 注冊(cè)表的維護(hù) / 參數(shù)

URL Scheme路由簡(jiǎn)單示例

通過(guò)下面簡(jiǎn)單示例 引入U(xiǎn)RL 路由

//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

//注冊(cè) --- 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];
    }];
}
//注冊(cè) --- end

//調(diào)用 --- start
//URL Scheme
[MTMediator openUrl:@"detail://" params:@{@"url":item.articleUrl,@"controller":self.navigationController}];
//調(diào)用 --- end
復(fù)制代碼

說(shuō)明:

  • 參考了系統(tǒng)URL Scheme機(jī)制
  • 參數(shù)傳遞通過(guò)dictionary,對(duì)調(diào)用者不透明

目前iOS上大部分路由工具都是基于URL匹配的,或者是根據(jù)命名約定,用runtime方法進(jìn)行動(dòng)態(tài)調(diào)用

這些動(dòng)態(tài)化的方案的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,缺點(diǎn)是需要維護(hù)字符串表,或者依賴于命名約定,無(wú)法在編譯時(shí)暴露出所有問(wèn)題,需要在運(yùn)行時(shí)才能發(fā)現(xiàn)錯(cuò)誤。

MGJRouter

URL路由方式主要是以蘑菇街為代表的的MGJRouter

其實(shí)現(xiàn)思路是:

  • App啟動(dòng)時(shí)實(shí)例化各組件模塊,然后這些組件向ModuleManager注冊(cè)Url,有些時(shí)候不需要實(shí)例化,使用class注冊(cè)
  • 當(dāng)組件A需要調(diào)用組件B時(shí),向ModuleManager傳遞URL,參數(shù)跟隨URL以GET方式傳遞,類似openURL。然后由ModuleManager負(fù)責(zé)調(diào)度組件B,最后完成任務(wù)。
// 1、注冊(cè)某個(gè)URL
MGJRouter.registerURLPattern("app://home") { (info) in
    print("info: (info)")
}

//2、調(diào)用路由
MGJRouter.openURL("app://home")
復(fù)制代碼

URL 路由的優(yōu)點(diǎn)

  • 極高的動(dòng)態(tài)性,適合經(jīng)常開(kāi)展運(yùn)營(yíng)活動(dòng)的app,例如電商
  • 方便地統(tǒng)一管理多平臺(tái)的路由規(guī)則
  • 易于適配URL Scheme

URl 路由的缺點(diǎn)

  • 傳參方式有限,并且無(wú)法利用編譯器進(jìn)行參數(shù)類型檢查,因此所有的參數(shù)都是通過(guò)字符串轉(zhuǎn)換而來(lái)
  • 只適用于界面模塊,不適用于通用模塊
  • 參數(shù)的格式不明確,是個(gè)靈活的 dictionary,也需要有個(gè)地方可以查參數(shù)格式。
  • 不支持storyboard
  • 依賴于字符串硬編碼,難以管理,蘑菇街做了個(gè)后臺(tái)專門(mén)管理。
  • 無(wú)法保證所使用的的模塊一定存在
  • 解耦能力有限,url 的”注冊(cè)”、”實(shí)現(xiàn)”、”使用”必須用相同的字符規(guī)則,一旦任何一方做出修改都會(huì)導(dǎo)致其他方的代碼失效,并且重構(gòu)難度大

除了MGJRouter,還有以下這些三方框架

Target - Action

  • 抽離業(yè)務(wù)邏輯
  • 通過(guò)中間層進(jìn)行調(diào)?
  • 中間層使? runtime 反射
  • 中間層代碼優(yōu)化

Target - Action簡(jiǎn)單示例

簡(jiǎn)單示例引入

//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

//調(diào)用 
//Target - Action
 UIViewController *vc = [MTMediator detailViewControllerWithUrl:item.articleUrl];
 vc.title = @"詳情啊";
 [self.navigationController pushViewController:vc animated:YES];

復(fù)制代碼

說(shuō)明:

  • 硬編碼方式(直接調(diào)用,不利于維護(hù)和擴(kuò)展)
  • perform 最多能傳遞2個(gè)參數(shù),可以傳入字典避免參數(shù)過(guò)多
    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
  • initWithUrlString:方法必須實(shí)現(xiàn) 否則找不到sel崩潰
  • 業(yè)務(wù)邏輯柔合在Mediator中,可以各個(gè)模塊寫(xiě)各自的MTMediator擴(kuò)展

CTMediator

三方框架其主要的代表框架是casatwy的CTMediator

這個(gè)方案是基于OC的runtime、category特性動(dòng)態(tài)獲取模塊,例如通過(guò)NSClassFromString獲取類并創(chuàng)建實(shí)例,通過(guò)performSelector + NSInvocation動(dòng)態(tài)調(diào)用方法

其實(shí)現(xiàn)思路是:

  • 1、利用分類為路由添加新接口,在接口中通過(guò)字符串獲取對(duì)應(yīng)的類
  • 2、通過(guò)runtime創(chuàng)建實(shí)例,動(dòng)態(tài)調(diào)用實(shí)例的方法

CTMediator簡(jiǎn)單使用:

//******* 1、分類定義新接口
extension CTMediator{
    @objc func A_showHome()->UIViewController?{

        //在swift中使用時(shí),需要傳入對(duì)應(yīng)項(xiàng)目的target名稱,否則會(huì)找不到視圖控制器
        let params = [
            kCTMediatorParamsKeySwiftTargetModuleName: "CJLBase_Example"
        ]
        //CTMediator提供的performTarget:action:params:shouldCacheTarget:方法 通過(guò)傳入name,找到對(duì)應(yīng)的targer和action
        if let vc = self.performTarget("A", action: "Extension_HomeViewController", params: params, shouldCacheTarget: false) as? UIViewController{
            return vc
        }
        return nil
    }
}

//******* 2、模塊提供者提供target-action的調(diào)用方式(對(duì)外需要加上public關(guān)鍵字)
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)
        }
復(fù)制代碼

其模塊間的引用關(guān)系如下圖所示:

image.png

優(yōu)點(diǎn)

  • 利用 分類 可以明確聲明接口,進(jìn)行編譯檢查
  • 實(shí)現(xiàn)方式輕量

缺點(diǎn)

  • 需要在mediatortarget中重新添加每一個(gè)接口,模塊化時(shí)代碼較為繁瑣
  • category 中仍然引入了字符串硬編碼,內(nèi)部使用字典傳參,一定程度上也存在和 URL 路由相同的問(wèn)題
  • 無(wú)法保證使用的模塊一定存在,target在修改后,使用者只能在運(yùn)行時(shí)才能發(fā)現(xiàn)錯(cuò)誤
  • 可能會(huì)創(chuàng)建過(guò)多的 target 類

CTMediator源碼分析

  • 通過(guò)上面CTMediator簡(jiǎn)單示例的分類所調(diào)用的performTarget來(lái)到CTMediator中的具體實(shí)現(xiàn),即performTarget:action:params:shouldCacheTarget:,主要是通過(guò)傳入的name,找到對(duì)應(yīng)的targetaction
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    if (targetName == nil || actionName == nil) {
        return nil;
    }
    //在swift中使用時(shí),需要傳入對(duì)應(yīng)項(xiàng)目的target名稱,否則會(huì)找不到視圖控制器
    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];
    //緩存中沒(méi)有target
    if (target == nil) {
        //通過(guò)字符串獲取對(duì)應(yīng)的類
        Class targetClass = NSClassFromString(targetClassString);
        //創(chuàng)建實(shí)例
        target = [[targetClass alloc] init];
    }

    // generate action 生成action方法名稱
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    //通過(guò)方法名字符串獲取對(duì)應(yīng)的sel
    SEL action = NSSelectorFromString(actionString);

    if (target == nil) {
        // 這里是處理無(wú)響應(yīng)請(qǐng)求的地方之一,這個(gè)demo做得比較簡(jiǎn)單,如果沒(méi)有可以響應(yīng)的target,就直接return了。實(shí)際開(kāi)發(fā)過(guò)程中是可以事先給一個(gè)固定的target專門(mén)用于在這個(gè)時(shí)候頂上,然后處理這種請(qǐng)求的
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    //是否需要緩存
    if (shouldCacheTarget) {
        [self safeSetCachedTarget:target key:targetClassString];
    }
    //是否響應(yīng)sel
    if ([target respondsToSelector:action]) {
        //動(dòng)態(tài)調(diào)用方法
        return [self safePerformAction:action target:target params:params];
    } else {
        // 這里是處理無(wú)響應(yīng)請(qǐng)求的地方,如果無(wú)響應(yīng),則嘗試調(diào)用對(duì)應(yīng)target的notFound方法統(tǒng)一處理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 這里也是處理無(wú)響應(yīng)請(qǐng)求的地方,在notFound都沒(méi)有的時(shí)候,這個(gè)demo是直接return了。實(shí)際開(kāi)發(fā)過(guò)程中,可以用前面提到的固定的target頂上的。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            @synchronized (self) {
                [self.cachedTarget removeObjectForKey:targetClassString];
            }
            return nil;
        }
    }
}
復(fù)制代碼
  • 進(jìn)入safePerformAction:target:params:實(shí)現(xiàn),主要是通過(guò)invocation進(jìn)行參數(shù)傳遞+消息轉(zhuǎn)發(fā)
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
    //獲取方法簽名
    NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
    if(methodSig == nil) {
        return nil;
    }
    //獲取方法簽名中的返回類型,然后根據(jù)返回值完成參數(shù)傳遞
    const char* retType = [methodSig methodReturnType];
    //void類型
    if (strcmp(retType, @encode(void)) == 0) {
        ...
    }
    //...省略其他類型的判斷
}
復(fù)制代碼

更多關(guān)于casatwy的CTMediator介紹 可以參見(jiàn)此篇文章解讀

Protocol - Class

  • 增加 Protocol Wrapper層 (中間件先注冊(cè)Protocol和Class對(duì)應(yīng)關(guān)系,將protocol和對(duì)應(yīng)的進(jìn)行字典匹配
  • 中間件返回 Protocol 對(duì)應(yīng)的 Class,然后動(dòng)態(tài)創(chuàng)建實(shí)例
  • 解決硬編碼的問(wèn)題

Protocol - Class簡(jiǎn)單示例

簡(jiǎn)單示例

//具體的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

//被調(diào)用
//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

//調(diào)用
Class cls = [MTMediator classForProtocol: @protocol(MTDetailViewControllerProtocol)];
if ([cls respondsToSelector: @selector(detailViewControllerWithUrl:)]) {
        [self.navigationController pushViewController:[cls detailViewControllerWithUrl:item.articleUrl] animated:YES];
}

復(fù)制代碼

說(shuō)明:

  • 被調(diào)用者先在中間件注冊(cè)Protocol和Class對(duì)應(yīng)關(guān)系,對(duì)外只暴漏Protocol

BeeHive

protocol比較典型的三方框架就是阿里的BeeHiveBeeHive借鑒了Spring Service、Apache DSO的架構(gòu)理念,采用AOP+擴(kuò)展App生命周期API形式,將業(yè)務(wù)功能、基礎(chǔ)功能模塊以模塊方式以解決大型應(yīng)用中的復(fù)雜問(wèn)題,并讓模塊之間以Service形式調(diào)用,將復(fù)雜問(wèn)題切分,以AOP方式模塊化服務(wù)。

BeeHive 核心思想

  • 1、各個(gè)模塊間調(diào)用從直接調(diào)用對(duì)應(yīng)模塊,變成調(diào)用Service的形式,避免了直接依賴。
  • 2、App生命周期的分發(fā),將耦合在AppDelegate中邏輯拆分,每個(gè)模塊以微應(yīng)用的形式獨(dú)立存在。

示例如下:

//******** 1、注冊(cè)
[[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]];

//******** 2、使用
#import "BHService.h"

id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];
復(fù)制代碼

優(yōu)點(diǎn)

  • 1、利用接口調(diào)用,實(shí)現(xiàn)了參數(shù)傳遞時(shí)的類型安全
  • 2、直接使用模塊的protocol接口,無(wú)需再重復(fù)封裝

缺點(diǎn)

  • 1、用框架來(lái)創(chuàng)建所有對(duì)象,創(chuàng)建方式不同,即不支持外部傳入?yún)?shù)
  • 2、用OC runtime創(chuàng)建對(duì)象,不支持swift
  • 3、只做了protocolclass 的匹配,不支持更復(fù)雜的創(chuàng)建方式 和依賴注入
  • 4、無(wú)法保證所使用的protocol 一定存在對(duì)應(yīng)的模塊,也無(wú)法直接判斷某個(gè)protocol是否能用于獲取模塊

除了BeeHive,還有Swinject

BeeHive 模塊注冊(cè)

BeeHive主要是通過(guò)BHModuleManager來(lái)管理各個(gè)模塊的。BHModuleManager中只會(huì)管理已經(jīng)被注冊(cè)過(guò)的模塊。

BeeHive提供了三種不同的注冊(cè)形式,annotation,靜態(tài)plist,動(dòng)態(tài)注冊(cè)。Module、Service之間沒(méi)有關(guān)聯(lián),每個(gè)業(yè)務(wù)模塊可以單獨(dú)實(shí)現(xiàn)Module或者Service的功能。

Annotation方式注冊(cè)

這種方式主要是通過(guò)BeeHiveMod宏進(jìn)行Annotation標(biāo)記

//***** 使用
BeeHiveMod(ShopModule)

//***** BeeHiveMod的宏定義
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";

//***** BeeHiveDATA的宏定義 
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))

//*****  全部轉(zhuǎn)換出來(lái)后為下面的格式 以name是ShopModule為例

char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";
復(fù)制代碼

這里針對(duì)__attribute需要說(shuō)明以下幾點(diǎn)

  • 第一個(gè)參數(shù)used:用來(lái)修飾函數(shù),被used修飾以后,意味著即使函數(shù)沒(méi)有被引用,在Release下也不會(huì)被優(yōu)化。如果不加這個(gè)修飾,那么Release環(huán)境鏈接器下會(huì)去掉沒(méi)有被引用的段。
  • 通過(guò)使用__attribute__((section("name")))來(lái)指明哪個(gè)段。數(shù)據(jù)則用__attribute__((used))來(lái)標(biāo)記,防止鏈接器會(huì)優(yōu)化刪除未被使用的段,然后將模塊注入到__DATA

此時(shí)Module已經(jīng)被存儲(chǔ)到Mach-O文件的特殊段中,那么如何取呢?

  • 進(jìn)入BHReadConfiguration方法,主要是通過(guò)Mach-O找到存儲(chǔ)的數(shù)據(jù)段,取出放入數(shù)組中

    NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp)
    {
    
        NSMutableArray *configs = [NSMutableArray array];
        unsigned long size = 0;
    #ifndef __LP64__
        // 找到之前存儲(chǔ)的數(shù)據(jù)段(Module找BeehiveMods段 和 Service找BeehiveServices段)的一片內(nèi)存
        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*);
        // 把特殊段里面的數(shù)據(jù)都轉(zhuǎn)換成字符串存入數(shù)組中
        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; 
    }
    復(fù)制代碼
    
  • 注冊(cè)的dyld_callback回調(diào)如下

    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函數(shù)是用來(lái)注冊(cè)dyld加載鏡像時(shí)的回調(diào)函數(shù),在dyld加載鏡像時(shí),會(huì)執(zhí)行注冊(cè)過(guò)的回調(diào)函數(shù)
        _dyld_register_func_for_add_image(dyld_callback);
    }
    復(fù)制代碼
    
讀取本地Pilst文件
  • 首先,需要設(shè)置好路徑

    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選,默認(rèn)為BeeHive.bundle/BeeHive.plist
    復(fù)制代碼
    
  • 創(chuàng)建plist文件,Plist文件的格式也是數(shù)組中包含多個(gè)字典。字典里面有兩個(gè)Key,一個(gè)是@"moduleLevel",另一個(gè)是@"moduleClass"。注意的數(shù)組的名字叫@“moduleClasses”。

    image.png

  • 進(jìn)入loadLocalModules方法,主要是從Plist里面取出數(shù)組,然后把數(shù)組加入到BHModuleInfos數(shù)組里面。

    //初始化context時(shí),加載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,并注冊(cè)到BHModuleManager的BHModuleInfos數(shù)組中
        [[BHModuleManager sharedManager] loadLocalModules];
        //注冊(cè)所有modules,在內(nèi)部根據(jù)優(yōu)先級(jí)進(jìn)行排序
        [[BHModuleManager sharedManager] registedAllModules];
    
    }
    ??
    - (void)loadLocalModules
    {
        //plist文件路徑
        NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"];
        //判斷文件是否存在
        if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
            return;
        }
        //讀取整個(gè)文件[@"moduleClasses" : 數(shù)組]
        NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
        //通過(guò)moduleClasses key讀取 數(shù)組 [[@"moduleClass":"aaa", @"moduleLevel": @"bbb"], [...]]
        NSArray<NSDictionary *> *modulesArray = [moduleList objectForKey:kModuleArrayKey];
        NSMutableDictionary<NSString *, NSNumber *> *moduleInfoByClass = @{}.mutableCopy;
        //遍歷數(shù)組
        [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]]) {
                //存儲(chǔ)到 BHModuleInfos 中
                [self.BHModuleInfos addObject:obj];
            }
        }];
    }
    復(fù)制代碼
    
load方法注冊(cè)

該方法注冊(cè)Module就是在Load方法里面注冊(cè)Module的類

+ (void)load
{
    [BeeHive registerDynamicModule:[self class]];
}
復(fù)制代碼
  • 進(jìn)入registerDynamicModule實(shí)現(xiàn)

    + (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];
    }
    復(fù)制代碼
    
  • Annotation方式注冊(cè)的dyld_callback回調(diào)一樣,最終會(huì)走到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];
                  });
              }
          }
      }
    復(fù)制代碼
    

load方法,還可以使用BH_EXPORT_MODULE宏代替

#define BH_EXPORT_MODULE(isAsync) \
+ (void)load { [BeeHive registerDynamicModule:[self class]]; } \
-(BOOL)async { return [[NSString stringWithUTF8String:#isAsync] boolValue];}
復(fù)制代碼

BH_EXPORT_MODULE宏里面可以傳入一個(gè)參數(shù),代表是否異步加載Module模塊,如果是YES就是異步加載,如果是NO就是同步加載

BeeHive 模塊事件

BeeHive會(huì)給每個(gè)模塊提供生命周期事件,用于與BeeHive宿主環(huán)境進(jìn)行必要信息交互,感知模塊生命周期的變化。

BeeHive各個(gè)模塊會(huì)收到一些事件。在BHModuleManager中,所有的事件被定義成了BHModuleEventType枚舉。如下所示,其中有2個(gè)事件很特殊,一個(gè)是BHMInitEvent,一個(gè)是BHMTearDownEvent

typedef NS_ENUM(NSInteger, BHModuleEventType)
{
    //設(shè)置Module模塊
    BHMSetupEvent = 0,
    //用于初始化Module模塊,例如環(huán)境判斷,根據(jù)不同環(huán)境進(jìn)行不同初始化
    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

};
復(fù)制代碼

主要分為三種

  • 1、系統(tǒng)事件:主要是指Application生命周期事件!
    image.png
 一般的做法是`AppDelegate`改為`繼承自BHAppDelegate`

```
@interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>
復(fù)制代碼
```

2、應(yīng)用事件:官方給出的流程圖,其中modSetupmodInit等,可以用于編碼實(shí)現(xiàn)各插件模塊的設(shè)置與初始化。

image.png

  • 3、自定義事件

以上所有的事件都可以通過(guò)調(diào)用BHModuleManagertriggerEvent:來(lái)處理。

- (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;
            //析構(gòu)事件
        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;
    }

}
復(fù)制代碼

從上面的代碼中可以發(fā)現(xiàn),除去BHMInitEvent初始化事件和BHMTearDownEvent拆除Module事件這兩個(gè)特殊事件以外,所有的事件都是調(diào)用的handleModuleEvent:forTarget:withSeletorStr:andCustomParam:方法,其內(nèi)部實(shí)現(xiàn)主要是遍歷 moduleInstances 實(shí)例數(shù)組,調(diào)用performSelector:withObject:方法實(shí)現(xiàn)對(duì)應(yīng)方法調(diào)用

- (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 實(shí)例數(shù)組,調(diào)用performSelector:withObject:方法實(shí)現(xiàn)對(duì)應(yīng)方法調(diào)用
    [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"
            //進(jìn)行方法調(diào)用
            [moduleInstance performSelector:seletor withObject:context];
#pragma clang diagnostic pop

            [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- %@", [moduleInstance class], NSStringFromSelector(seletor)]];

        }
    }];
}
復(fù)制代碼

注意:這里所有的Module必須是遵循BHModuleProtocol的,否則無(wú)法接收到這些事件的消息。

BeeHive Protocol注冊(cè)

在BeeHive中是通過(guò)BHServiceManager來(lái)管理各個(gè)Protocol的。BHServiceManager中只會(huì)管理已經(jīng)被注冊(cè)過(guò)的Protocol。

注冊(cè)Protocol的方式總共有三種,和注冊(cè)Module是一樣一一對(duì)應(yīng)的

Annotation方式注冊(cè)
//****** 1、通過(guò)BeeHiveService宏進(jìn)行Annotation標(biāo)記
BeeHiveService(HomeServiceProtocol,BHViewController)

//****** 2、宏定義
#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ ""#servicename"" : ""#impl""}";

//****** 3、轉(zhuǎn)換后的格式,也是將其存儲(chǔ)到特殊的段
char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ """HomeServiceProtocol""" : """BHViewController"""}";
復(fù)制代碼
讀取本地plist文件
  • 首先同Module一樣,需要先設(shè)置好路徑

    [BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";
    復(fù)制代碼
    
  • 設(shè)置plist文件

image.png
  • 同樣也是在setContext時(shí)注冊(cè)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];
        //遍歷并存儲(chǔ)到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];
    }
    復(fù)制代碼
    
load方法注冊(cè)

在Load方法里面注冊(cè)Protocol協(xié)議,主要是調(diào)用BeeHive里面的registerService:service:完成protocol的注冊(cè)

+ (void)load
{
   [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}
??
- (void)registerService:(Protocol *)proto service:(Class) serviceClass
{
    [[BHServiceManager sharedManager] registerService:proto implClass:serviceClass];
}
復(fù)制代碼

到此,三種方式注冊(cè)就完成了

Protocol的獲取

ProtocolModule的區(qū)別在于,ProtocolModule多了一個(gè)方法,可以返回Protocol實(shí)例對(duì)象

- (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是否已經(jīng)注冊(cè)過(guò)
    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;
        }
    }
    //獲取類后,然后響應(yīng)下層的方法
    Class implClass = [self serviceImplClass:service];
    if ([[implClass class] respondsToSelector:@selector(singleton)]) {
        if ([[implClass class] singleton]) {
            if ([[implClass class] respondsToSelector:@selector(shareInstance)])
                //創(chuàng)建單例對(duì)象
                implInstance = [[implClass class] shareInstance];
            else
                //創(chuàng)建實(shí)例對(duì)象
                implInstance = [[implClass alloc] init];
            if (shouldCache) {
                //緩存
                [[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
                return implInstance;
            } else {
                return implInstance;
            }
        }
    }
    return [[implClass alloc] init];
}
復(fù)制代碼

createService會(huì)先檢查Protocol協(xié)議是否是注冊(cè)過(guò)的。然后接著取出字典里面對(duì)應(yīng)的Class,如果實(shí)現(xiàn)了shareInstance方法,那么就創(chuàng)建一個(gè)單例對(duì)象,如果沒(méi)有,那么就創(chuàng)建一個(gè)實(shí)例對(duì)象。如果還實(shí)現(xiàn)了singleton,就能進(jìn)一步的把implInstanceserviceStr對(duì)應(yīng)的加到BHContextservicesByName字典里面緩存起來(lái)。這樣就可以隨著上下文傳遞了

  • 進(jìn)入serviceImplClass實(shí)現(xiàn),從這里可以看出 protocol和類是通過(guò)字典綁定的,protocol作為key,serviceImp(類的名字)作為value

    - (Class)serviceImplClass:(Protocol *)service
    {
        //通過(guò)字典將 協(xié)議 和 類 綁定,其中協(xié)議作為key,serviceImp(類的名字)作為value
        NSString *serviceImpl = [[self servicesDict] objectForKey:NSStringFromProtocol(service)];
        if (serviceImpl.length > 0) {
            return NSClassFromString(serviceImpl);
        }
        return nil;
    }
    復(fù)制代碼
    

Module & Protocol

這里簡(jiǎn)單總結(jié)下:

  • 對(duì)于Module:數(shù)組存儲(chǔ)

  • 對(duì)于Protocol:通過(guò)字典將protocol與類進(jìn)行綁定,keyprotocol,valueserviceImp即類名

BeeHive輔助類

  • BHContext類:是一個(gè)單例,其內(nèi)部有兩個(gè)NSMutableDictionary的屬性,分別是modulesByNameservicesByName。這個(gè)類主要用來(lái)保存上下文信息的。例如在application:didFinishLaunchingWithOptions:的時(shí)候,就可以初始化大量的上下文信息

    //保存信息
    [BHContext shareInstance].application = application;
    [BHContext shareInstance].launchOptions = launchOptions;
    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選,默認(rèn)為BeeHive.bundle/BeeHive.plist
    [BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";
    復(fù)制代碼
    
  • BHConfig類:是一個(gè)單例,其內(nèi)部有一個(gè)NSMutableDictionary類型的config屬性,該屬性維護(hù)了一些動(dòng)態(tài)的環(huán)境變量,作為BHContext的補(bǔ)充存在

  • BHTimeProfiler類:用來(lái)進(jìn)行計(jì)算時(shí)間性能方面的Profiler

  • BHWatchDog類:用來(lái)開(kāi)一個(gè)線程,監(jiān)聽(tīng)主線程是否堵塞

參考鏈接

--- end ---
原文地址:https://juejin.cn/post/7067743813099323423
以下文章可以做一個(gè)學(xué)習(xí)參考:
GCD面試要點(diǎn)
block面試要點(diǎn)
Runtime面試要點(diǎn)
RunLoop面試要點(diǎn)
內(nèi)存管理面試要點(diǎn)
MVC、MVVM面試要點(diǎn)
網(wǎng)絡(luò)性能優(yōu)化面試要點(diǎn)
網(wǎng)絡(luò)編程面試要點(diǎn)
KVC&KVO面試要點(diǎn)
數(shù)據(jù)存儲(chǔ)面試要點(diǎn)
混編技術(shù)面試要點(diǎn)
設(shè)計(jì)模式面試要點(diǎn)
UI面試要點(diǎn)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容