阿里組件化框架BeeHive解析

更好的閱讀體驗(yàn)闯传,請(qǐng)到個(gè)人博客閱讀: 阿里組件化框架BeeHive解析


本文是基于BeeHive版本1.6.0進(jìn)行分析。

BeeHive核心思想涉及兩個(gè)部分:

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

Core+Plugin的形式可以讓一個(gè)應(yīng)用主流程部分得到集中管理皮胡,不同模塊以plugin形式存在痴颊,便于橫向的擴(kuò)展和移植。

本文會(huì)按照以下順序進(jìn)行介紹:

  • [BeeHive概覽]
  • [BeeHive模塊生命周期事件]
    • [系統(tǒng)事件]
    • [通用事件]
    • [業(yè)務(wù)自定義事件]
  • [BeeHive模塊注冊(cè)]
    • [Annotation方式注冊(cè)]
    • [讀取本地Plist方式注冊(cè)]
    • [Load方法注冊(cè)]
  • [BeeHive模塊間調(diào)用]
    • [Annotation方式注冊(cè)]
    • [讀取本地Plist方式注冊(cè)]
    • [API注冊(cè)]
  • [上下文環(huán)境Context]

BeeHive概覽

BeeHive的架構(gòu)圖如下所示:

BeeHive架構(gòu)圖

圖中的BHContext屡贺,包含BeeHive的配置文件蠢棱,提供全局統(tǒng)一上下文信息。

圖中的BHCore包含:

  • BeeHive甩栈,提供組件庫(kù)對(duì)外接口
  • BHModuleManager和BHModuleProtocol泻仙,注冊(cè)和創(chuàng)建Module邏輯
  • BHServiceManager和BHServiceProtocol,注冊(cè)和創(chuàng)建Service邏輯
  • BHRouter

Module量没、Service注冊(cè)和調(diào)用邏輯只和核心模塊相關(guān)玉转,Module之間沒(méi)有直接的關(guān)聯(lián)關(guān)系。

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

Module注冊(cè)

圖中包含了主要的BeeHive啟動(dòng)過(guò)程以及Module注冊(cè)的時(shí)序邏輯橘茉。Module的事件分發(fā)源于BHAppDelegate中的triggerEvent

加載Module:

  1. BeeHive.plist中配置的module和service是在 AppDelegate中調(diào)用 [[BeeHive shareInstance] setContext:[BHContext shareInstance]]; 時(shí)加載姨丈。
  2. Module的實(shí)現(xiàn)中 +load內(nèi)部調(diào)用 [BeeHive registerDynamicModule:[self class]]; 動(dòng)態(tài)加載畅卓。
  3. Module的實(shí)現(xiàn)中使用注解: @BeeHiveMod(XXModule)

BHAppDelegate中除了回調(diào)系統(tǒng)的事件,還將App生命周期進(jìn)行擴(kuò)展蟋恬,增加ModuleSetup翁潘,ModuleInitModuleSplash筋现,此外開(kāi)發(fā)人員還可以自行擴(kuò)展唐础。

BeeHive事件擴(kuò)展

擴(kuò)展周期過(guò)程中,同時(shí)加入Module分析量化功能矾飞,每個(gè)模塊Init的耗時(shí)均可計(jì)算出來(lái),為性能優(yōu)化做到數(shù)據(jù)上的支持呀邢。一個(gè)App的業(yè)務(wù)增多過(guò)程中洒沦,通過(guò)分析定位Module的Init耗時(shí)可以確定需要優(yōu)化的Module。

Module遵循BHModuleProtocol后价淌,能夠捕獲App狀態(tài)的回調(diào)申眼,并擁有App生命周期內(nèi)的全局上下文瞒津,通過(guò)context可獲取配置參數(shù),模塊資源以及服務(wù)資源括尸。

以BeeHive作為底層框架的App巷蚪,除了解耦帶來(lái)的便利,開(kāi)發(fā)人員在開(kāi)發(fā)新App過(guò)程中涉及相同功能的Module濒翻,無(wú)需重復(fù)造輪子屁柏,直接移植Module,開(kāi)發(fā)一個(gè)App如同拼裝積木有送,能組合需要的功能業(yè)務(wù)淌喻。

BHModuleManager.m:

//BHModuleInfos: [{moduleClass:String, ModuleLevel:NSNumber, modulePriority:String}]
//BHModules: [id<BHModuleProtocol>]

@interface BHModuleManager()

@property(nonatomic, strong) NSMutableArray *BHModuleDynamicClasses;

@property(nonatomic, strong) NSMutableArray<NSDictionary *> *BHModuleInfos;
@property(nonatomic, strong) NSMutableArray<id<BHModuleProtocol>> *BHModules;

@property(nonatomic, strong) NSMutableDictionary<NSNumber *, NSMutableArray<id<BHModuleProtocol>> *> *BHModulesByEvent;
@property(nonatomic, strong) NSMutableDictionary<NSNumber *, NSString *> *BHSelectorByEvent;

@end

BHModuleInfosBHModulesByEventid<BHModuleProtocol>均按照moduleInstancesmoduleLevelmodulePriority進(jìn)行了排序。

注冊(cè)靜態(tài)plist service

上圖中包含注冊(cè)靜態(tài)plist中service的相關(guān)邏輯雀摘,App啟動(dòng)時(shí)通過(guò)BeeHive的setContext:來(lái)觸發(fā)plist中service的注冊(cè)裸删。service的注冊(cè)并沒(méi)有創(chuàng)建對(duì)應(yīng)的service實(shí)例,只是在BHServiceManager中建立Service協(xié)議實(shí)現(xiàn)該協(xié)議的類之間的映射關(guān)系阵赠。

動(dòng)態(tài)注冊(cè)service

上圖中是動(dòng)態(tài)注冊(cè)service的邏輯涯塔,這是在App啟動(dòng)事件分發(fā)時(shí)觸發(fā)。Module根據(jù)需求動(dòng)態(tài)注冊(cè)某個(gè)服務(wù)清蚀,通常伤塌,注冊(cè)service的代碼在module的modInit:或者modSetup:中。

創(chuàng)建service實(shí)例

業(yè)務(wù)組件可以通過(guò)createService:直接調(diào)用服務(wù)轧铁。Service的調(diào)用和實(shí)現(xiàn)每聪,核心是BHServiceManager〕莘纾可以單獨(dú)創(chuàng)建Services Interface Pod药薯,統(tǒng)一放置要用的Services,這樣的業(yè)務(wù)依賴就從網(wǎng)狀式變成中心式救斑,業(yè)務(wù)方只需依賴Services一個(gè)童本。

Service可以按需加載,BeeHive邏輯是將基礎(chǔ)服務(wù)注冊(cè)在plist中脸候,業(yè)務(wù)型服務(wù)允許Service不先注冊(cè)穷娱,直到業(yè)務(wù)需要時(shí)才被動(dòng)態(tài)注冊(cè)。

Service支持兩種不同模式:

  • 單例: 對(duì)于全局統(tǒng)一且無(wú)狀態(tài)服務(wù)运沦,建議使用這種創(chuàng)建形式泵额,這樣有利于Service的統(tǒng)一管理以及減少不必要內(nèi)存消耗。
  • 多實(shí)例: 每次調(diào)用服務(wù)都重新創(chuàng)建新的服務(wù)携添,對(duì)于涉及狀態(tài)以及狀態(tài)變化的服務(wù)最適合使用多實(shí)例方式嫁盲。

在多線程環(huán)境下遇到了Service讀寫(xiě)問(wèn)題,已通過(guò)Lock來(lái)已避免Array crash問(wèn)題烈掠。
不過(guò)Service還存在如下問(wèn)題:

  • Service依賴關(guān)系,導(dǎo)致底層依賴的Service沒(méi)有被創(chuàng)建時(shí)就被調(diào)用羞秤。
  • 規(guī)劃Service缸托、Module創(chuàng)建順序,使得App達(dá)到秒開(kāi)瘾蛋,優(yōu)化性能體驗(yàn)俐镐。

前者依賴問(wèn)題計(jì)劃通過(guò)調(diào)度機(jī)制來(lái)解決,后者還需要將AppDelegate更多業(yè)務(wù)剝離以及實(shí)踐才可哺哼。

BeeHive使用createService:createService:withServiceName:來(lái)創(chuàng)建實(shí)現(xiàn)了協(xié)議的對(duì)象佩抹,并且緩存該對(duì)象。

BeeHive模塊生命周期事件

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

事件分為三種類型:

  • 系統(tǒng)事件
  • 通用事件
  • 業(yè)務(wù)自定義事件

在BHModuleManager的頭文件中甲葬,Event的類型定義如下:

typedef NS_ENUM(NSInteger, BHModuleEventType)
{
    BHMSetupEvent = 0,
    BHMInitEvent,
    BHMTearDownEvent,
    BHMSplashEvent,
    BHMQuickActionEvent,
    BHMWillResignActiveEvent,
    BHMDidEnterBackgroundEvent,
    BHMWillEnterForegroundEvent,
    BHMDidBecomeActiveEvent,
    BHMWillTerminateEvent,
    BHMUnmountEvent,
    BHMOpenURLEvent,
    BHMDidReceiveMemoryWarningEvent,
    BHMDidFailToRegisterForRemoteNotificationsEvent,
    BHMDidRegisterForRemoteNotificationsEvent,
    BHMDidReceiveRemoteNotificationEvent,
    BHMDidReceiveLocalNotificationEvent,
    BHMWillPresentNotificationEvent,
    BHMDidReceiveNotificationResponseEvent,
    BHMWillContinueUserActivityEvent,
    BHMContinueUserActivityEvent,
    BHMDidFailToContinueUserActivityEvent,
    BHMDidUpdateUserActivityEvent,
    BHMHandleWatchKitExtensionRequestEvent,
    BHMDidCustomEvent = 1000
    
};

系統(tǒng)事件

BeeHive系統(tǒng)事件

系統(tǒng)事件通常是Application生命周期事件廊勃,例如WillResignActiveEvent, DidEnterBackgroundEvent, WillEnterForegroundEvent, DidBecomeActiveEvent, WillTerminateEvent

一般做法是使用BHAppDelegate來(lái)接管系統(tǒng)事件经窖,如下所示:

//TestAppDelegate.h
#import "BeeHive.h"

@interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>

//TestAppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [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";
    
    [BeeHive shareInstance].enableException = YES;
    [[BeeHive shareInstance] setContext:[BHContext shareInstance]];
    [[BHTimeProfiler sharedTimeProfiler] recordEventTime:@"BeeHive::super start launch"];

    
    [super application:application didFinishLaunchingWithOptions:launchOptions];
    
...
    return YES;
}

在BHAppDelegate的實(shí)現(xiàn)文件中,實(shí)現(xiàn)了一系列的系統(tǒng)調(diào)用事件画侣。如下所示:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [[BHModuleManager sharedManager] triggerEvent:BHMSetupEvent];
    [[BHModuleManager sharedManager] triggerEvent:BHMInitEvent];
    
    dispatch_async(dispatch_get_main_queue(), ^{
        [[BHModuleManager sharedManager] triggerEvent:BHMSplashEvent];
    });
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
    if ([UIDevice currentDevice].systemVersion.floatValue >= 10.0f) {
        [UNUserNotificationCenter currentNotificationCenter].delegate = self;
    }
#endif
    
#ifdef DEBUG
    [[BHTimeProfiler sharedTimeProfiler] saveTimeProfileDataIntoFile:@"BeeHiveTimeProfiler"];
#endif
    
    return YES;
}


#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80400 

-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))completionHandler
{
    [[BeeHive shareInstance].context.touchShortcutItem setShortcutItem: shortcutItem];
    [[BeeHive shareInstance].context.touchShortcutItem setScompletionHandler: completionHandler];
    [[BHModuleManager sharedManager] triggerEvent:BHMQuickActionEvent];
}
#endif

- (void)applicationWillResignActive:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMWillResignActiveEvent];
}

- (void)applicationDidEnterBackground:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidEnterBackgroundEvent];
}

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMWillEnterForegroundEvent];
}

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidBecomeActiveEvent];
}

- (void)applicationWillTerminate:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMWillTerminateEvent];
}

- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
{
    [[BeeHive shareInstance].context.openURLItem setOpenURL:url];
    [[BeeHive shareInstance].context.openURLItem setSourceApplication:sourceApplication];
    [[BeeHive shareInstance].context.openURLItem setAnnotation:annotation];
    [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
    return YES;
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80400
- (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<NSString *,id> *)options
{
  
    [[BeeHive shareInstance].context.openURLItem setOpenURL:url];
    [[BeeHive shareInstance].context.openURLItem setOptions:options];
    [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
    return YES;
}
#endif


- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveMemoryWarningEvent];
}

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    [[BeeHive shareInstance].context.notificationsItem setNotificationsError:error];
    [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToRegisterForRemoteNotificationsEvent];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [[BeeHive shareInstance].context.notificationsItem setDeviceToken: deviceToken];
    [[BHModuleManager sharedManager] triggerEvent:BHMDidRegisterForRemoteNotificationsEvent];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    [[BeeHive shareInstance].context.notificationsItem setUserInfo: userInfo];
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    [[BeeHive shareInstance].context.notificationsItem setUserInfo: userInfo];
    [[BeeHive shareInstance].context.notificationsItem setNotificationResultHander: completionHandler];
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification
{
    [[BeeHive shareInstance].context.notificationsItem setLocalNotification: notification];
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveLocalNotificationEvent];
}

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
- (void)application:(UIApplication *)application didUpdateUserActivity:(NSUserActivity *)userActivity
{
    if([UIDevice currentDevice].systemVersion.floatValue >= 8.0f){
        [[BeeHive shareInstance].context.userActivityItem setUserActivity: userActivity];
        [[BHModuleManager sharedManager] triggerEvent:BHMDidUpdateUserActivityEvent];
    }
}

- (void)application:(UIApplication *)application didFailToContinueUserActivityWithType:(NSString *)userActivityType error:(NSError *)error
{
    if([UIDevice currentDevice].systemVersion.floatValue >= 8.0f){
        [[BeeHive shareInstance].context.userActivityItem setUserActivityType: userActivityType];
        [[BeeHive shareInstance].context.userActivityItem setUserActivityError: error];
        [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToContinueUserActivityEvent];
    }
}

- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray * _Nullable))restorationHandler
{
    if([UIDevice currentDevice].systemVersion.floatValue >= 8.0f){
        [[BeeHive shareInstance].context.userActivityItem setUserActivity: userActivity];
        [[BeeHive shareInstance].context.userActivityItem setRestorationHandler: restorationHandler];
        [[BHModuleManager sharedManager] triggerEvent:BHMContinueUserActivityEvent];
    }
    return YES;
}

- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType
{
    if([UIDevice currentDevice].systemVersion.floatValue >= 8.0f){
        [[BeeHive shareInstance].context.userActivityItem setUserActivityType: userActivityType];
        [[BHModuleManager sharedManager] triggerEvent:BHMWillContinueUserActivityEvent];
    }
    return YES;
}
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(nullable NSDictionary *)userInfo reply:(void(^)(NSDictionary * __nullable replyInfo))reply {
    if([UIDevice currentDevice].systemVersion.floatValue >= 8.0f){
        [BeeHive shareInstance].context.watchItem.userInfo = userInfo;
        [BeeHive shareInstance].context.watchItem.replyHandler = reply;
        [[BHModuleManager sharedManager] triggerEvent:BHMHandleWatchKitExtensionRequestEvent];
    }
}
#endif
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 100000
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
    [[BeeHive shareInstance].context.notificationsItem setNotification: notification];
    [[BeeHive shareInstance].context.notificationsItem setNotificationPresentationOptionsHandler: completionHandler];
    [[BeeHive shareInstance].context.notificationsItem setCenter:center];
    [[BHModuleManager sharedManager] triggerEvent:BHMWillPresentNotificationEvent];
};

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
    [[BeeHive shareInstance].context.notificationsItem setNotificationResponse: response];
    [[BeeHive shareInstance].context.notificationsItem setNotificationCompletionHandler:completionHandler];
    [[BeeHive shareInstance].context.notificationsItem setCenter:center];
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveNotificationResponseEvent];
};
#endif

@end

這樣所有的系統(tǒng)事件都可以通過(guò)BHModuleManager的triggerEvent:來(lái)處理冰悠。

在上述事件中,BHMInitEventBHMTearDownEvent事件需要做特殊處理配乱。

先看看BHMInitEvent的處理溉卓。

- (void)handleModulesInitEventForTarget:(id<BHModuleProtocol>)target
                        withCustomParam:(NSDictionary *)customParam
{
    BHContext *context = [BHContext shareInstance].copy;
    context.customParam = customParam;
    context.customEvent = BHMInitEvent;
    
    NSArray<id<BHModuleProtocol>> *moduleInstances;
    if (target) {
        moduleInstances = @[target];
    } else {
        moduleInstances = [self.BHModulesByEvent objectForKey:@(BHMInitEvent)];
    }
    
    [moduleInstances enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
        __weak typeof(&*self) wself = self;
        void ( ^ bk )(void);
        bk = ^(){
            __strong typeof(&*self) sself = wself;
            if (sself) {
                if ([moduleInstance respondsToSelector:@selector(modInit:)]) {
                    [moduleInstance modInit:context];
                }
            }
        };

        [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- modInit:", [moduleInstance class]]];
        
        if ([moduleInstance respondsToSelector:@selector(async)]) {
            BOOL async = [moduleInstance async];
            
            if (async) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    bk();
                });
                
            } else {
                bk();
            }
        } else {
            bk();
        }
    }];
}

BHMInitEvent事件是觸發(fā)各個(gè)module啟動(dòng)時(shí)的初始化邏輯。這里從self.BHModulesByEvent中取出BHMInitEvent事件對(duì)應(yīng)的module數(shù)組搬泥,遍歷其中的每個(gè)module實(shí)例桑寨,向其發(fā)送modInit:消息。這里會(huì)考慮是否異步執(zhí)行module的初始化忿檩。如果moduleInstance重寫(xiě)了async方法尉尾,那么就會(huì)根據(jù)該方法的返回值來(lái)決定是否異步執(zhí)行module的初始化。

modInit:方法由各個(gè)module實(shí)例各自實(shí)現(xiàn)燥透,可以在其中注冊(cè)一個(gè)外部模塊可以訪問(wèn)的Service接口沙咏。

-(void)modInit:(BHContext *)context
{
    //注冊(cè)模塊的接口服務(wù)
    [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}

再看看BHMTearDownEvent事件。這個(gè)事件中可以處理module的清理工作班套。

- (void)handleModulesTearDownEventForTarget:(id<BHModuleProtocol>)target
                            withCustomParam:(NSDictionary *)customParam
{
    BHContext *context = [BHContext shareInstance].copy;
    context.customParam = customParam;
    context.customEvent = BHMTearDownEvent;
    
    NSArray<id<BHModuleProtocol>> *moduleInstances;
    if (target) {
        moduleInstances = @[target];
    } else {
        moduleInstances = [self.BHModulesByEvent objectForKey:@(BHMTearDownEvent)];
    }

    //Reverse Order to unload
    for (int i = (int)moduleInstances.count - 1; i >= 0; i--) {
        id<BHModuleProtocol> moduleInstance = [moduleInstances objectAtIndex:i];
        if (moduleInstance && [moduleInstance respondsToSelector:@selector(modTearDown:)]) {
            [moduleInstance modTearDown:context];
        }
    }
}

由于module具有優(yōu)先級(jí)肢藐,且self.BHModulesByEvent結(jié)構(gòu)中,每種事件類型對(duì)應(yīng)的modules數(shù)組中的module元素都已經(jīng)按照優(yōu)先級(jí)從高到低排列孽尽,因此逆序?qū)odules數(shù)組中的module元素調(diào)用modTearDown:方法窖壕。

通用事件

在系統(tǒng)事件的基礎(chǔ)之上,擴(kuò)展了應(yīng)用的通用事件杉女,例如modSetup瞻讽、modInit等,可以用于編碼實(shí)現(xiàn)各插件模塊的設(shè)置與初始化熏挎。

擴(kuò)展的通用事件如下:

BeeHive通用事件

所有的事件都可以通過(guò)調(diào)用BHModuleManager的triggerEvent:來(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];
}

- (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;
        default: {
            NSString *selectorStr = [self.BHSelectorByEvent objectForKey:@(eventType)];
            [self handleModuleEvent:eventType forTarget:nil withSeletorStr:selectorStr andCustomParam:customParam];
        }
            break;
    }
}

- (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 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)]];
            
        }
    }];
}

從上面的代碼可以看出,事件類型分發(fā)是在方法handleModuleEvent:forTarget:withCustomParam:中進(jìn)行坎拐。如之前所述烦磁,需要對(duì)BHMInitEventBHMTearDownEvent做特殊處理。同時(shí)哼勇,觸發(fā)各個(gè)module(從self.BHModulesByEvent中獲取)中的響應(yīng)事件方法通過(guò)performSelector:withObject:來(lái)調(diào)用都伪。

注意這里的module都是遵循BHModuleProtocol協(xié)議的。

通用事件中积担,可以在modSetup中設(shè)置環(huán)境變量,通過(guò)context.env可以判斷我們的應(yīng)用環(huán)境狀態(tài)來(lái)決定我們?nèi)绾闻渲梦覀兊膽?yīng)用陨晶。如下所示:

-(void)modSetup:(BHContext *)context
{
    switch (context.env) {
        case BHEnvironmentDev:
        //....初始化開(kāi)發(fā)環(huán)境
        break;
        case BHEnvironmentProd:
        //....初始化生產(chǎn)環(huán)境
        default:
        break;
    }
}

業(yè)務(wù)自定義事件

如果覺(jué)得系統(tǒng)事件、通用事件不足以滿足需要帝璧,我們還將事件封裝簡(jiǎn)化成BHAppdelgate先誉,你可以通過(guò)繼承 BHAppdelegate來(lái)擴(kuò)展自己的事件。通過(guò)以下方式來(lái)注冊(cè)自定義事件:

- (void)registerCustomEvent:(NSInteger)eventType
   withModuleInstance:(id)moduleInstance
       andSelectorStr:(NSString *)selectorStr {
    if (eventType < 1000) {
        return;
    }
    [self registerEvent:eventType withModuleInstance:moduleInstance andSelectorStr:selectorStr];
}

觸發(fā)帶參數(shù)的事件響應(yīng):

- (void)triggerEvent:(NSInteger)eventType
     withCustomParam:(NSDictionary *)customParam {
    [self handleModuleEvent:eventType forTarget:nil withCustomParam:customParam];
}

BeeHive模塊注冊(cè)

使用注解的方式注冊(cè)Module和Service時(shí)的烁,Module和Service的注冊(cè)發(fā)生在加載鏡像文件時(shí)期褐耳。

plist方式注冊(cè)Module和Service,是在AppDelegate中設(shè)置BeeHive的Context時(shí)加載注冊(cè)渴庆。

Module動(dòng)態(tài)注冊(cè)是在+load方法中铃芦,也是在加載鏡像時(shí)注冊(cè)。Service的動(dòng)態(tài)注冊(cè)可以Module的modInit:或者modSetup:中襟雷,或者使用時(shí)注冊(cè)刃滓。

模塊注冊(cè)有三種方式:Annotation方式注冊(cè)、讀取本地plist方式注冊(cè)嗤军、Load方法注冊(cè)注盈。所謂注冊(cè),就是將Module類告知BHModuleManager來(lái)管理叙赚。由此可見(jiàn)老客,在BeeHive中是通過(guò)BHModuleManager來(lái)管理各個(gè)模塊的,BHModuleManager中只會(huì)管理已經(jīng)被注冊(cè)過(guò)的模塊震叮。

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也是一個(gè)宏:

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname"")))

在預(yù)編譯結(jié)束后,BeeHiveMode宏最終會(huì)完全展開(kāi)成下面的樣子:

@class BeeHive; char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"""))) = """ShopModule""";

這里需要注意雙引號(hào)的總對(duì)數(shù)苇瓣。

關(guān)于__attribute的用法尉间,可參考我的另一篇attribute

__attribute第一個(gè)參數(shù)used,它的作用是告訴編譯器,我聲明的這個(gè)符號(hào)是需要保留的。被used修飾以后哲嘲,意味著即使函數(shù)沒(méi)有被引用贪薪,在Release下也不會(huì)被優(yōu)化。如果不加這個(gè)修飾眠副,那么Release環(huán)境鏈接器會(huì)去掉沒(méi)有被引用的段画切。

有時(shí)候我們需要指定一個(gè)特殊的段,來(lái)存放我們想要的數(shù)據(jù)囱怕。這里我們就把數(shù)據(jù)存在__DATA數(shù)據(jù)段里面的"BeehiveMods"section中霍弹。 Attributes的修飾關(guān)鍵字section ("section-name”)可以達(dá)到此要求。

上述代碼中:

@class BeeHive; char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"""))) = """ShopModule""";

去掉__attribute的屬性娃弓,相當(dāng)于:

@class BeeHive; char * kShopModule_mod = """ShopModule""";

只不過(guò)是將kShopModule_mod變量存儲(chǔ)在了__DATA段的BeehiveMods section中典格。

這樣,所有的Module類名的字符串都會(huì)放置在__DATA段BeehiveMods section中台丛,那么如何取出這些字符串呢耍缴?

這里先介紹一下__attribute__((constructor))

constructor:顧名思義,構(gòu)造器加上這個(gè)屬性的函數(shù)會(huì)在可執(zhí)行文件(或 shared library)load時(shí)被調(diào)用齐佳,可以理解為在 main() 函數(shù)調(diào)用前執(zhí)行:

__attribute__((constructor))
static void beforeMain(void) {
    NSLog(@"beforeMain");
}
__attribute__((destructor))
static void afterMain(void) {
    NSLog(@"afterMain");
}
int main(int argc, const char * argv[]) {
    NSLog(@"main");
    return 0;
}

// Console:
// "beforeMain" -> "main" -> “afterMain"

_dyld_register_func_for_add_image:這個(gè)函數(shù)是用來(lái)注冊(cè)回調(diào)私恬,當(dāng)dyld鏈接符號(hào)時(shí),調(diào)用此回調(diào)函數(shù)炼吴。在dyld加載鏡像時(shí)本鸣,會(huì)執(zhí)行注冊(cè)過(guò)的回調(diào)函數(shù);當(dāng)然硅蹦,我們也可以使用下面的方法注冊(cè)自定義的回調(diào)函數(shù)荣德,同時(shí)也會(huì)為所有已經(jīng)加載的鏡像執(zhí)行回調(diào):

/*
 * The following functions allow you to install callbacks which will be called   
 * by dyld whenever an image is loaded or unloaded.  During a call to _dyld_register_func_for_add_image()
 * the callback func is called for every existing image.  Later, it is called as each new image
 * is loaded and bound (but initializers not yet run).  The callback registered with
 * _dyld_register_func_for_remove_image() is called after any terminators in an image are run
 * and before the image is un-memory-mapped.
 */
extern void _dyld_register_func_for_add_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)) 
extern void _dyld_register_func_for_remove_image(void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide))

對(duì)于每一個(gè)已經(jīng)存在的鏡像,當(dāng)它被動(dòng)態(tài)鏈接時(shí)童芹,都會(huì)執(zhí)行回調(diào)void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)涮瞻,傳入文件的mach_header以及一個(gè)虛擬內(nèi)存地址 intptr_t。

__attribute__((constructor))
void initProphet() {
    _dyld_register_func_for_add_image(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];
            }
        }
    }
}


NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp)
{
    NSMutableArray *configs = [NSMutableArray array];
    unsigned long size = 0;
#ifndef __LP64__
    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;
}

mach_header是定義在usr/include/mach-o/loader.h中的數(shù)據(jù)結(jié)構(gòu):

/*
 * The 64-bit mach header appears at the very beginning of object files for
 * 64-bit architectures.
 */
struct mach_header_64 {
     uint32_t    magic;        /* mach magic number identifier */
     cpu_type_t    cputype;    /* cpu specifier */
     cpu_subtype_t    cpusubtype;    /* machine specifier */
     uint32_t    filetype;    /* type of file */
     uint32_t    ncmds;        /* number of load commands */
     uint32_t    sizeofcmds;    /* the size of all the load commands */
     uint32_t    flags;        /* flags */
     uint32_t    reserved;    /* reserved */
};

通過(guò)調(diào)用BHReadConfiguration函數(shù)假褪,我們就可以拿到之前注冊(cè)到BeehiveMods特殊段里面的各個(gè)Module的類名署咽,該函數(shù)返回類名字符串的數(shù)組。

然后將Module交由到BHModuleManager管理:

- (void)registerDynamicModule:(Class)moduleClass
{
    [self registerDynamicModule:moduleClass shouldTriggerInitEvent:NO];
}
- (void)registerDynamicModule:(Class)moduleClass
       shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent
{
    [self addModuleFromObject:moduleClass shouldTriggerInitEvent:shouldTriggerInitEvent];
}

- (void)addModuleFromObject:(id)object
     shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent
{
    Class class;
    NSString *moduleName = nil;
    
    if (object) {
        class = object;
        moduleName = NSStringFromClass(class);
    } else {
        return ;
    }
    
    /** 檢測(cè)是否已存在Module類 */
    __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];
        
        /** basicModuleLevel 這個(gè)方法如果不實(shí)現(xiàn),Level是Normal: 1 */
        BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];
        int levelInt = 1;
        
        if (responseBasicLevel) {
            /** basicModuleLevel 這個(gè)方法如果實(shí)現(xiàn)缀遍,Level是Basic: 0 */
            levelInt = 0;
        }
        
        [moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
        if (moduleName) {
            [moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
        }
        [self.BHModuleInfos addObject:moduleInfo];
        
        /** 初始化module實(shí)例 */
        id<BHModuleProtocol> moduleInstance = [[class alloc] init];
        [self.BHModules addObject:moduleInstance];
        [moduleInfo setObject:@(YES) forKey:kModuleInfoHasInstantiatedKey];

        /** 將module按照優(yōu)先級(jí)排序 */
        [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;
            }
        }];

        /** 給module注冊(cè)事件 */
        [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];
            });
        }
    }
}

所有需要注冊(cè)的Module必須遵循BHModuleProtocol協(xié)議慕匠,否則不會(huì)被存儲(chǔ)。

讀取本地Plist方式注冊(cè)

首先需要設(shè)置本地Plist文件的讀取路徑:

[BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選域醇,默認(rèn)為BeeHive.bundle/BeeHive.plist

BeeHive的所有配置都可以通過(guò)BHContext進(jìn)行傳遞台谊。

Plist文件的字段如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
     <key>URLGlobalScheme</key>
     <string>com.alibaba.beehive</string>
     <key>moduleClasses</key>
     <array>
         <dict>
             <key>moduleClass</key>
             <string>HomeModule</string>
             <key>moduleLevel</key>
             <integer>1</integer>
             <key>modulePriority</key>
             <string>600</string>
         </dict>
         <dict>
             <key>moduleClass</key>
             <string>TMTradeAdapter</string>
             <key>moduleLevel</key>
             <integer>1</integer>
             <key>modulePriority</key>
             <string>599</string>
         </dict>
     </array>
</dict>
</plist>

在AppDelegate中 [[BeeHive shareInstance] setContext:[BHContext shareInstance]]; 設(shè)置BHContext時(shí)就會(huì)注冊(cè)plist中的module蓉媳。

- (void)loadLocalModules
{
    
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"];
    if (![[NSFileManager defaultManager] fileExistsAtPath:plistPath]) {
        return;
    }
    
    NSDictionary *moduleList = [[NSDictionary alloc] initWithContentsOfFile:plistPath];
    
    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]]) {
            [self.BHModuleInfos addObject:obj];
        }
    }];
}

將Plist中的module加入到BHModuleInfos中。

Load方法注冊(cè)

// BeeHive
+ (void)load
{
    [BeeHive registerDynamicModule:[self class]];
}

+ (void)registerDynamicModule:(Class)moduleClass
{
    [[BHModuleManager sharedManager] registerDynamicModule:moduleClass];
}

// BHModuleManager
- (void)registerDynamicModule:(Class)moduleClass
{
    [self registerDynamicModule:moduleClass shouldTriggerInitEvent:NO];
}

Load方法最終是調(diào)用BHModuleManager中的registerDynamicModule:方法來(lái)處理锅铅,該方法已在上一節(jié)中說(shuō)明酪呻。

+load的方式可以使用BH_EXPORT_MODULE宏來(lái)替代完成。

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

BH_EXPORT_MODULE宏中傳入了參數(shù)isAsync狠角,代表是否異步加載Module模塊号杠。如果是YES蚪腋,則表示需要異步加載丰歌,NO則表示同步加載。

回過(guò)頭來(lái)看看AppDelegate中setContext方法中的操作:

// AppDelegate
[[BeeHive shareInstance] setContext:[BHContext shareInstance]];

// BeeHive
-(void)setContext:(BHContext *)context {
    _context = context;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self loadStaticServices];
        [self loadStaticModules];
    });
}

- (void)loadStaticModules {
    [[BHModuleManager sharedManager] loadLocalModules];
    [[BHModuleManager sharedManager] registedAllModules];
}

重點(diǎn)關(guān)注一下registedAllModules方法 :

- (void)registedAllModules {
    /** 按照優(yōu)先級(jí)從大到小順序排列 */
    [self.BHModuleInfos sortUsingComparator:^NSComparisonResult(NSDictionary *module1, NSDictionary *module2) {
        NSNumber *module1Level = (NSNumber *)[module1 objectForKey:kModuleInfoLevelKey];
        NSNumber *module2Level =  (NSNumber *)[module2 objectForKey:kModuleInfoLevelKey];
        if (module1Level.integerValue != module2Level.integerValue) {
            return module1Level.integerValue > module2Level.integerValue;
        } else {
            NSNumber *module1Priority = (NSNumber *)[module1 objectForKey:kModuleInfoPriorityKey];
            NSNumber *module2Priority = (NSNumber *)[module2 objectForKey:kModuleInfoPriorityKey];
            return module1Priority.integerValue < module2Priority.integerValue;
        }
    }];
    
    NSMutableArray *tmpArray = [NSMutableArray array];
    
    //module init:初始化所有的module
    [self.BHModuleInfos enumerateObjectsUsingBlock:^(NSDictionary *module, NSUInteger idx, BOOL * _Nonnull stop) {
        
        NSString *classStr = [module objectForKey:kModuleInfoNameKey];
        
        Class moduleClass = NSClassFromString(classStr);
        BOOL hasInstantiated = ((NSNumber *)[module objectForKey:kModuleInfoHasInstantiatedKey]).boolValue;
        if (NSStringFromClass(moduleClass) && !hasInstantiated) {
            id<BHModuleProtocol> moduleInstance = [[moduleClass alloc] init];
            [tmpArray addObject:moduleInstance];
        }
        
    }];
    
//    [self.BHModules removeAllObjects];
    [self.BHModules addObjectsFromArray:tmpArray];
    //給module對(duì)象注冊(cè)系統(tǒng)事件
    [self registerAllSystemEvents];
}

在經(jīng)歷registedAllModules方法之后屉凯,所有注冊(cè)的module都生成了對(duì)應(yīng)的實(shí)例對(duì)象立帖。

注意:

  1. 所有的Module的對(duì)象都要是遵守BHModuleProtocol協(xié)議的。
  2. Module不能在任何其他地方alloc創(chuàng)建出來(lái)悠砚,即使創(chuàng)建一個(gè)新的Module實(shí)例出來(lái)晓勇,它也并不在BHModuleManager的管理下,無(wú)法接收BHModuleManager分發(fā)的系統(tǒng)事件灌旧。

BeeHive模塊間調(diào)用

通過(guò)處理Event編寫(xiě)各個(gè)業(yè)務(wù)模塊可以實(shí)現(xiàn)插件化編程绑咱,各業(yè)務(wù)模塊之間沒(méi)有任何依賴,core與module之間通過(guò)event交互枢泰,實(shí)現(xiàn)了插件隔離描融。但有時(shí)候我們需要模塊間的相互調(diào)用某些功能來(lái)協(xié)同完成功能。目前模塊間的調(diào)用使用基于接口的實(shí)現(xiàn)Service訪問(wèn)方式(Java spring框架實(shí)現(xiàn))衡蚂×耍基于接口Service訪問(wèn)的優(yōu)點(diǎn)是可以編譯時(shí)檢查發(fā)現(xiàn)接口的變更,從而及時(shí)修正接口問(wèn)題毛甲。缺點(diǎn)是需要依賴接口定義的頭文件年叮,通過(guò)模塊增加得越多,維護(hù)接口定義的也有一定工作量玻募。

模塊間調(diào)用的協(xié)議都是通過(guò)BHServiceManager來(lái)維護(hù)的只损。

BeeHive提供了三種方式來(lái)注冊(cè)協(xié)議,這里和module的注冊(cè)方式相同:Annotation方式注冊(cè)七咧、讀取本地plist方式注冊(cè)跃惫、API注冊(cè)。

Annotation方式注冊(cè)

使用@BeeHiveService進(jìn)行Annotation標(biāo)記坑雅。BeeHiveService的宏定義如下:

#define BeeHiveService(servicename,impl) \
class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname"")))

在示例中辈挂,@BeeHiveService(HomeServiceProtocol,BHViewController)在預(yù)編譯結(jié)束后會(huì)完全展開(kāi)成如下所示:

@class BeeHive; char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"""))) = "{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}”;

這里類比注冊(cè)module時(shí)的Annotation解析,也是把數(shù)據(jù)存在特殊的段內(nèi)裹粤,具體的原理可以參考注冊(cè)module的分析终蒂。

同理蜂林,通過(guò)調(diào)用函數(shù)BHReadConfiguration讀取之前注冊(cè)到特殊段BeehiveServices中的數(shù)據(jù),這里是如下所示的json字符串拇泣,{協(xié)議字符串:實(shí)現(xiàn)該協(xié)議的class類名字符串}噪叙,如下所示:

{"HomeServiceProtocol":"BHViewController”}

讀取到數(shù)據(jù)后,進(jìn)行service的注冊(cè):

static void dyld_callback(const struct mach_header *mhp, intptr_t vmaddr_slide)
{
    //register module
...
    
    //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)];
                }
                
            }
        }
    }
}

- (void)registerService:(Protocol *)service implClass:(Class)implClass
{
    NSParameterAssert(service != nil);
    NSParameterAssert(implClass != nil);
    
    if (![implClass conformsToProtocol:service]) {
        if (self.enableException) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
        }
        return;
    }
    
    //協(xié)議是否已經(jīng)注冊(cè)
    if ([self checkValidService:service]) {
        if (self.enableException) {
            @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
        }
        return;
    }
    
    NSString *key = NSStringFromProtocol(service);
    NSString *value = NSStringFromClass(implClass);
    
    if (key.length > 0 && value.length > 0) {
        [self.lock lock];
        [self.allServicesDict addEntriesFromDictionary:@{key:value}];
        [self.lock unlock];
    }
}

在注冊(cè)協(xié)議前會(huì)有兩個(gè)檢查registerService:implClass::

  • 檢查implClass是否遵循了service
  • 檢查service協(xié)議是否已經(jīng)注冊(cè)

如果兩個(gè)條件有一個(gè)沒(méi)有檢查通過(guò)霉翔,則會(huì)拋出異常睁蕾。

如果條件通過(guò),則會(huì)在allServicesDict中加入鍵值對(duì)债朵,{NSStringFromProtocol(service):NSStringFromClass(implClass)}

讀取本地Plist方式注冊(cè)

讀取本地的plist文件之前子眶,需要先設(shè)置plist文件路徑。

//AppDelegate.m
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService”;

BeeHive的配置都可以通過(guò)BHContext進(jìn)行傳遞序芦。

plist中的數(shù)據(jù)格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
    <dict>
        <key>service</key>
        <string>UserTrackServiceProtocol</string>
        <key>impl</key>
        <string>BHUserTrackViewController</string>
    </dict>
</array>
</plist>

注冊(cè)plist中service的時(shí)機(jī)同注冊(cè)plist管理的module類似:

// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    
    [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";
    
    [BeeHive shareInstance].enableException = YES;
    [[BeeHive shareInstance] setContext:[BHContext shareInstance]];
    [[BHTimeProfiler sharedTimeProfiler] recordEventTime:@"BeeHive::super start launch"];
    
    [super application:application didFinishLaunchingWithOptions:launchOptions];
    
...
    
    return YES;
}

// BeeHive.m
-(void)setContext:(BHContext *)context
{
    _context = context;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self loadStaticServices];
        [self loadStaticModules];
    });
}

-(void)loadStaticServices
{
    [BHServiceManager sharedManager].enableException = self.enableException;
    
    [[BHServiceManager sharedManager] registerLocalServices];
    
}

注冊(cè)service的具體實(shí)現(xiàn)如下:

- (void)registerLocalServices
{
    NSString *serviceConfigName = [BHContext shareInstance].serviceConfigName;
    
    NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist"];
    if (!plistPath) {
        return;
    }
    
    NSArray *serviceList = [[NSArray alloc] initWithContentsOfFile:plistPath];
    
    [self.lock lock];
    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];
}

注冊(cè)完成之后,allServicesDict中的值如下:

{@"HomeServiceProtocol" : @"BHViewController", @"UserTrackServiceProtocol" : @"BHUserTrackViewController"}

注意:NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist”]; 這段代碼谚中,如果主工程Podfile中未使用了use_frameworks!渴杆,則可以正常獲取到plist,如果使用了use_frameworks!宪塔,則得使用其他方式獲取磁奖,具體的方案請(qǐng)google。

API注冊(cè)

API注冊(cè)service使用的api是BeeHive的接口- (void)registerService:(Protocol *)proto service:(Class)serviceClass, 該接口內(nèi)部實(shí)現(xiàn)也是調(diào)用BHServiceManagerregisterService:implClass:接口某筐。

// BeeHive.m
- (void)registerService:(Protocol *)proto service:(Class)serviceClass {
    [[BHServiceManager sharedManager] registerService:proto implClass:serviceClass];
}

例如:[[BeeHive shareInstance] registerService:@protocol(TradeServiceProtocol) service:[BHTradeViewController class]];, 該代碼可以放置在module的 modInit:方法內(nèi)部或者modSetup:方法內(nèi)部比搭,具體可以查看使用文檔或者demo。

注意:BHMSetupEventBHMInitEvent事件都會(huì)在項(xiàng)目的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions函數(shù)內(nèi)部觸發(fā)来吩。

BHServiceManager中的allServicesDict包含了所有方式注冊(cè)的service敢辩。

與module注冊(cè)相比,service的注冊(cè)沒(méi)有對(duì)實(shí)現(xiàn)協(xié)議的對(duì)象進(jìn)行初始化弟疆。只是將協(xié)議和實(shí)現(xiàn)協(xié)議的對(duì)象之間的這種關(guān)聯(lián)關(guān)系存儲(chǔ)和維護(hù)戚长,而在module的注冊(cè)過(guò)程中,對(duì)所有注冊(cè)的module進(jìn)行實(shí)例的初始化怠苔。

因此同廉,在BHServiceManager的公共接口中,有一組createService:接口用于訪問(wèn)實(shí)現(xiàn)協(xié)議的對(duì)象柑司。

- (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;
    
    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];
}

從上面的實(shí)現(xiàn)中可以看出:

  • service對(duì)象存在單例和多實(shí)例的區(qū)別迫肖。
  • 如果BHServiceProtocol協(xié)議對(duì)象實(shí)現(xiàn)了singleton返回YES,且shouldCache入?yún)⒅凳荵ES攒驰,則通過(guò)createService:獲取的對(duì)象為單例對(duì)象蟆湖,如果singleton方法返回的是NO,則每次調(diào)用createService:都會(huì)創(chuàng)建一個(gè)新的對(duì)象玻粪。
  • 這里的單例存在線程安全問(wèn)題隅津。如果開(kāi)發(fā)者將BHServiceProtocol協(xié)議對(duì)象實(shí)現(xiàn)了singleton返回YES诬垂,且shareInstance方法的實(shí)現(xiàn)是返回單例對(duì)象,則不管shouldCache的入?yún)⒅凳荵ES還是NO伦仍,createService:都會(huì)是同一個(gè)實(shí)例结窘。但是,如果開(kāi)發(fā)者將shareInstance方法的實(shí)現(xiàn)只是返回對(duì)象(非單例)充蓝,即使singleton返回YES隧枫,shouldCache入?yún)⒅凳荵ES,也可能存在多個(gè)實(shí)例的情況谓苟。

上下文環(huán)境Context

BeeHive中使用BHContext初始化設(shè)置應(yīng)用的項(xiàng)目信息官脓,并在各模塊間共享整個(gè)應(yīng)用程序的信息。例如:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [BHContext shareInstance].env = BHEnvironmentDev; //定義應(yīng)用的運(yùn)行開(kāi)發(fā)環(huán)境
    [BHContext shareInstance].application = application;
    [BHContext shareInstance].launchOptions = launchOptions;
    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/CustomModulePlist";//可選娜谊,默認(rèn)為BeeHive.bundle/BeeHive.plist
    [BHContext shareInstance].serviceConfigName =  @"BeeHive.bundle/CustomServicePlist";//可選确买,默認(rèn)為BeeHive.bundle/BHService.plist
    [[BeeHive shareInstance] setContext:[BHContext shareInstance]];

    [super application:application didFinishLaunchingWithOptions:launchOptions];

...

    return YES;
}

//BHContext.h

@interface BHContext : NSObject <NSCopying>

//global env
@property(nonatomic, assign) BHEnvironmentType env;

//global config
@property(nonatomic, strong) BHConfig *config;

//application appkey
@property(nonatomic, strong) NSString *appkey;
//customEvent>=1000
@property(nonatomic, assign) NSInteger customEvent;

@property(nonatomic, strong) UIApplication *application;

@property(nonatomic, strong) NSDictionary *launchOptions;

@property(nonatomic, strong) NSString *moduleConfigName;

@property(nonatomic, strong) NSString *serviceConfigName;

//3D-Touch model
#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400
@property (nonatomic, strong) BHShortcutItem *touchShortcutItem;
#endif

//OpenURL model
@property (nonatomic, strong) BHOpenURLItem *openURLItem;

//Notifications Remote or Local
@property (nonatomic, strong) BHNotificationsItem *notificationsItem;

//user Activity Model
@property (nonatomic, strong) BHUserActivityItem *userActivityItem;

//watch Model
@property (nonatomic, strong) BHWatchItem *watchItem;

//custom param
@property (nonatomic, copy) NSDictionary *customParam;

@end
  • 應(yīng)用的運(yùn)行開(kāi)發(fā)環(huán)境
  • 應(yīng)用啟動(dòng)信息:application,launchOptions
  • module和service的plist配置信息
  • 緩存的service
  • 3D-Touch, OpenURL moduel, Remote Or Local Notifications, User Activity Model等

最后纱皆,附一張BeeHive主要類的的類圖關(guān)系。

BeeHive類圖

如果覺(jué)得本文對(duì)你有幫助芭商,就請(qǐng)用微信隨意打賞我吧_

wechat_appreciate_qrcode.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末派草,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子铛楣,更是在濱河造成了極大的恐慌近迁,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,839評(píng)論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件簸州,死亡現(xiàn)場(chǎng)離奇詭異鉴竭,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)岸浑,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)搏存,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人矢洲,你說(shuō)我怎么就攤上這事璧眠。” “怎么了读虏?”我有些...
    開(kāi)封第一講書(shū)人閱讀 153,116評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵责静,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我盖桥,道長(zhǎng)灾螃,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,371評(píng)論 1 279
  • 正文 為了忘掉前任揩徊,我火速辦了婚禮腰鬼,結(jié)果婚禮上藐握,老公的妹妹穿的比我還像新娘。我一直安慰自己垃喊,他們只是感情好猾普,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,384評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著本谜,像睡著了一般初家。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上乌助,一...
    開(kāi)封第一講書(shū)人閱讀 49,111評(píng)論 1 285
  • 那天溜在,我揣著相機(jī)與錄音,去河邊找鬼他托。 笑死掖肋,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赏参。 我是一名探鬼主播志笼,決...
    沈念sama閱讀 38,416評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼把篓!你這毒婦竟也來(lái)了纫溃?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,053評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤韧掩,失蹤者是張志新(化名)和其女友劉穎紊浩,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體疗锐,經(jīng)...
    沈念sama閱讀 43,558評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡坊谁,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,007評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了滑臊。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片口芍。...
    茶點(diǎn)故事閱讀 38,117評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖简珠,靈堂內(nèi)的尸體忽然破棺而出阶界,到底是詐尸還是另有隱情,我是刑警寧澤聋庵,帶...
    沈念sama閱讀 33,756評(píng)論 4 324
  • 正文 年R本政府宣布膘融,位于F島的核電站,受9級(jí)特大地震影響祭玉,放射性物質(zhì)發(fā)生泄漏氧映。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,324評(píng)論 3 307
  • 文/蒙蒙 一脱货、第九天 我趴在偏房一處隱蔽的房頂上張望岛都。 院中可真熱鬧律姨,春花似錦、人聲如沸臼疫。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,315評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)烫堤。三九已至荣赶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間鸽斟,已是汗流浹背磁滚。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,539評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工厦凤, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留翁脆,地道東北人尚镰。 一個(gè)月前我還...
    沈念sama閱讀 45,578評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像立倍,于是被迫代替她去往敵國(guó)和親灭红。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,877評(píng)論 2 345