BeeHive —— 一個優雅但還在完善中的解耦框架

前言

BeeHive是阿里巴巴公司開源的一個iOS框架,這個框架是App模塊化編程的框架一種實現方案,吸取了Spring框架Service的理念來實現模塊間的API解耦。javascript

BeeHive這個名字靈感來源於蜂窩。蜂窩是世界上高度模塊化的工程結構,六邊形的設計能帶來無限擴張的可能。因此就用了這個名字做爲開源項目的名字。html

在前一篇文章iOS 組件化 —— 路由設計思路分析中,咱們分析了App組件之間能夠經過路由來解除耦合。那麼這篇文章就來看看利用模塊化的思想如何解除耦合的。java

(看到這裏必定會不少人有疑問,那就看看這篇文章組件和模塊的區別)spring

說明:本文是基於BeeHive v1.2.0版本進行解析的。編程

目錄

  • 1.BeeHive概述
  • 2.BeeHive模塊註冊
  • 3.BeeHive模塊事件
  • 4.BeeHive模塊調用
  • 5.其餘的一些輔助類
  • 6.可能還在完善中的功能

一. BeeHive概述

因爲BeeHive是基於Spring的Service理念,雖然可使模塊間的具體實現與接口解耦,但沒法避免模塊對接口類的依賴關係。json

暫時BeeHive沒有采用invoke和performSelector:action withObject: params的方法。主要緣由仍是考慮學習成本難度以及動態調用實現沒法在編譯檢查階段檢測接口參數變動等問題。vim

目前BeeHive v1.2.0 所有是利用Protocol的方式,實現了模塊間解耦的目的:數組

1.各個模塊以插件的形式存在。每一個均可獨立,相互解耦。
2.各個模塊具體實現與接口調用分離
3.各個模塊也有生命週期,也能夠進行管理。緩存

官方也給出了一個架構圖:安全

接下來就依次分析模塊註冊,模塊事件,模塊調用是如何實現解耦的。

二. BeeHive模塊註冊

先從模塊的註冊開始分析,來看看BeeHive是如何給各個模塊進行註冊的。

在BeeHive中是經過BHModuleManager來管理各個模塊的。BHModuleManager中只會管理已經被註冊過的模塊。

註冊Module的方式總共有三種:

1. Annotation方式註冊

經過BeeHiveMod宏進行Annotation標記。

BeeHiveMod(ShopModule)複製代碼

BeeHiveMod宏定義以下:

#define BeeHiveMod(name) \
char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";複製代碼

BeeHiveDATA又是一個宏:

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))複製代碼

最終BeeHiveMod宏會在預編譯結束會徹底展開成下面的樣子:

char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";複製代碼

注意雙引號的總對數。

到這裏__attribute((used,section("segmentname,sectionname")))就須要先說明2個地方。

__attribute第一個參數used頗有用。這個關鍵字是用來修飾函數的。被used修飾之後,意味着即便函數沒有被引用,在Release下也不會被優化。若是不加這個修飾,那麼Release環境連接器下會去掉沒有被引用的段。具體的描述能夠看這個gun的官方文檔

Static靜態變量會按照他們申明的順序,放到一個單獨的段中。咱們經過使用__attribute__((section("name")))來指明哪一個段。數據則用__attribute__((used))來標記,防止連接器會優化刪除未被使用的段。

再來具體說說section的做用。

編譯器編譯源代碼後生成的文件叫目標文件,從文件結構上來講,它已是編譯後可執行的文件格式,只是尚未通過連接的過程。可執行文件(Executable)主要是Windows下的PE(Portable Executable)和Linux的ELF(Executable Linkable Format),它們也都是COFF(Common file format)格式的變種。程序源程序代碼被編譯以後會主要分紅兩個段:程序指令和程序數據。代碼段屬於程序指令,數據段和.bss段屬於數據段。

具體的例子見上圖,可見.data數據段裏面保存的都是初始化過的全局靜態變量和局部靜態變量。.rodata段存放的是隻讀數據,通常都是const修飾的變量和字符串常量。.bss段存放的是未初始化的全局變量和局部靜態變量。代碼段就在.text段。

有時候咱們須要指定一個特殊的段,來存放咱們想要的數據。這裏咱們就把數據存在data數據段裏面的"BeehiveMods"段中。

固然還有其餘的Attributes的修飾關鍵字,詳情見官方文檔

回到代碼上來:

char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";複製代碼

也就至關於:

char * kShopModule_mod = """ShopModule""";複製代碼

只不過是把kShopModule_mod字符串放到了特殊的段裏面。

Module被這樣存到了特殊的段中,那怎麼取出來的呢?

static NSArray<NSString *>* BHReadConfiguration(char *section)
{
    NSMutableArray *configs = [NSMutableArray array];

    Dl_info info;
    dladdr(BHReadConfiguration, &info);

#ifndef __LP64__
    // const struct mach_header *mhp = _dyld_get_image_header(0); // both works as below line
    const struct mach_header *mhp = (struct mach_header*)info.dli_fbase;
    unsigned long size = 0;
    // 找到以前存儲的數據段(Module找BeehiveMods段 和 Service找BeehiveServices段)的一片內存
    uint32_t *memory = (uint32_t*)getsectiondata(mhp, "__DATA", section, & size);
#else /* defined(__LP64__) */
    const struct mach_header_64 *mhp = (struct mach_header_64*)info.dli_fbase;
    unsigned long size = 0;
    uint64_t *memory = (uint64_t*)getsectiondata(mhp, "__DATA", section, & size);
#endif /* defined(__LP64__) */

    // 把特殊段裏面的數據都轉換成字符串存入數組中
    for(int idx = 0; idx < size/sizeof(void*); ++idx){
        char *string = (char*)memory[idx];

        NSString *str = [NSString stringWithUTF8String:string];
        if(!str)continue;

        BHLog(@"config = %@", str);
        if(str) [configs addObject:str];
    }

    return configs;
}複製代碼

Dl_info是一個Mach-O裏面的一個數據結構。

typedef struct dl_info {
        const char      *dli_fname;     /* Pathname of shared object */
        void            *dli_fbase;     /* Base address of shared object */
        const char      *dli_sname;     /* Name of nearest symbol */
        void            *dli_saddr;     /* Address of nearest symbol */
} Dl_info;複製代碼

這個數據結構的數據默認就是經過

extern int dladdr(const void *, Dl_info *);複製代碼

dladdr這個函數來獲取Dl_info裏面的數據。

dli_fname:路徑名,例如

/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation複製代碼

dli_fbase:共享對象的的起始地址(Base address of shared object,好比上面的 CoreFoundation)

dli_saddr :符號的地址
dli_sname:符號的名字,即下面的第四列的函數信息

Thread 0:
0     libsystem_kernel.dylib          0x11135810a __semwait_signal + 94474
1     libsystem_c.dylib               0x1110dab0b sleep + 518923
2     QYPerformanceMonitor            0x10dda4f1b -[ViewController tableView:cellForRowAtIndexPath:] + 7963
3     UIKit                           0x10ed4d4f4 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 1586420複製代碼

經過調用這個static函數BHReadConfiguration,咱們就能夠拿到以前註冊到BeehiveMods特殊段裏面的各個Module的類名,都用字符串裝在數據裏。

+ (NSArray<NSString *> *)AnnotationModules
{
    static NSArray<NSString *> *mods = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        mods = BHReadConfiguration(BeehiveModSectName);
    });
    return mods;
}複製代碼

這是一個單例數組,裏面裝的都是以前放在特殊段裏面的Module名字對應的字符串數組。

拿到這個數組之後,就能夠註冊全部的Module了。

- (void)registedAnnotationModules
{

    NSArray<NSString *>*mods = [BHAnnotation AnnotationModules];
    for (NSString *modName in mods) {
        Class cls;
        if (modName) {
            cls = NSClassFromString(modName);

            if (cls) {
                [self registerDynamicModule:cls];
            }
        }
    }
}


- (void)registerDynamicModule:(Class)moduleClass
{
    [self addModuleFromObject:moduleClass];

}複製代碼

最後還須要把全部已經註冊的Module添加到BHModuleManager裏面。

- (void)addModuleFromObject:(id)object
{
    Class class;
    NSString *moduleName = nil;

    if (object) {
        class = object;
        moduleName = NSStringFromClass(class);
    } else {
        return ;
    }

    if ([class conformsToProtocol:@protocol(BHModuleProtocol)]) {
        NSMutableDictionary *moduleInfo = [NSMutableDictionary dictionary];

        // basicModuleLevel 這個方法若是默認不實現,Level默認是Normal
        BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];

        // Level是BHModuleNormal,就是1
        int levelInt = 1;

        // 若是實現了basicModuleLevel方法,那麼Level就是BHModuleBasic
        if (responseBasicLevel) {
            // Level是Basic,BHModuleBasic就是0
            levelInt = 0;
        }

        // @"moduleLevel" 爲Key,Level爲Value
        [moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
        if (moduleName) {
            // @"moduleClass"爲Key,moduleName爲Value
            [moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
        }

        [self.BHModules addObject:moduleInfo];
    }
}複製代碼

一些須要說明已經在上述代碼裏面添加了註釋。BHModules是一個NSMutableArray,裏面存的都是一個個的字典,字典裏面有兩個Key,一個是@"moduleLevel",另外一個是@"moduleClass"。存儲已經註冊的Module的時候都要判斷Level。還有一點須要說明的,全部須要註冊的Module必須遵循BHModuleProtocol協議,不然不能被存儲。

2. 讀取本地Pilst文件

要讀取本地的Plist文件以前,須要先設置好路徑。

[BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選,默認爲BeeHive.bundle/BeeHive.plist複製代碼

BeeHive全部的配置均可以寫在BHContext進行傳遞。

Plist文件的格式也要是數組裏麪包一個個的字典。字典裏面有兩個Key,一個是@"moduleLevel",另外一個是@"moduleClass"。注意根的數組的名字叫@「moduleClasses」。

- (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 *modulesArray = [moduleList objectForKey:kModuleArrayKey];

    [self.BHModules addObjectsFromArray:modulesArray];

}複製代碼

從Plist裏面取出數組,而後把數組加入到BHModules數組裏面。

3. Load方法註冊

最後一種註冊Module的方法就是在Load方法裏面註冊Module的類。

+ (void)load
{
    [BeeHive registerDynamicModule:[self class]];
}複製代碼

調用BeeHive裏面的registerDynamicModule:完成Module的註冊。

+ (void)registerDynamicModule:(Class)moduleClass
{
    [[BHModuleManager sharedManager] registerDynamicModule:moduleClass];
}複製代碼

BeeHive裏面的registerDynamicModule:的實現仍是調用的BHModuleManager的註冊方法registerDynamicModule:

- (void)registerDynamicModule:(Class)moduleClass
{
    [self addModuleFromObject:moduleClass];

}複製代碼

最後仍是調用到了BHModuleManager裏面的addModuleFromObject:方法,這個方法上面分析過了,再也不贅述。

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還會對這些Module的Class進行一下操做。

首先在BeeHive初始化setContext:的時候,會分別加載Modules和Services。這裏先談Modules。

-(void)setContext:(BHContext *)context
{
    _context = context;

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self loadStaticServices];
        [self loadStaticModules];
    });
}複製代碼

看看loadStaticModules方法裏面作了什麼事情。

- (void)loadStaticModules
{
    // 讀取本地plist文件裏面的Module,並註冊到BHModuleManager的BHModules數組中
    [[BHModuleManager sharedManager] loadLocalModules];

    // 讀取特殊段裏面的標記數據,並註冊到BHModuleManager的BHModules數組中
    [[BHModuleManager sharedManager] registedAnnotationModules];

    [[BHModuleManager sharedManager] registedAllModules];

}複製代碼

這裏雖然咱們只看到了兩種方式,可是實際上BHModules數組裏面還會包括經過Load方法註冊進來的Module。那麼BHModules數組其實是包含了3種註冊方式加進來的Module。

最後一步,registedAllModules比較關鍵。

- (void)registedAllModules
{

    // 根絕優先級從大到小進行排序
    [self.BHModules sortUsingComparator:^NSComparisonResult(NSDictionary *module1, NSDictionary *module2) {
      NSNumber *module1Level = (NSNumber *)[module1 objectForKey:kModuleInfoLevelKey];
      NSNumber *module2Level =  (NSNumber *)[module2 objectForKey:kModuleInfoLevelKey];

        return [module1Level intValue] > [module2Level intValue];
    }];

    NSMutableArray *tmpArray = [NSMutableArray array];

    //module init
    [self.BHModules enumerateObjectsUsingBlock:^(NSDictionary *module, NSUInteger idx, BOOL * _Nonnull stop) {

        NSString *classStr = [module objectForKey:kModuleInfoNameKey];

        Class moduleClass = NSClassFromString(classStr);

        if (NSStringFromClass(moduleClass)) {

            // 初始化全部的Module
            id<BHModuleProtocol> moduleInstance = [[moduleClass alloc] init];
            [tmpArray addObject:moduleInstance];
        }

    }];

    [self.BHModules removeAllObjects];

    [self.BHModules addObjectsFromArray:tmpArray];

}複製代碼

BHModules數組在進行registedAllModules方法以前,裝的都是一個個的字典,再執行完registedAllModules方法以後,裏面裝的就都是一個個的Module的實例了。

registedAllModules方法會先按照Level的優先級從大到小進行排序,而後再按照這個順序依次初始化全部的Module的實例,存入數組中。最終BHModules數組裏面裝的是全部的Module實例對象。

注意,這裏有兩點須要額外說明:

  1. 限制住了全部的Module的對象都要是遵照BHModuleProtocol協議的。至於爲什麼要遵照BHModuleProtocol協議,下一章節會有詳細說明。
  2. Module不能在任何其餘地方alloc建立出來,即便建立一個新的Module實例出來,它也並不在BHModuleManager的管理下,是沒法接收BHModuleManager分發的系統事件,建立出來是沒有任何意義的。

三. BeeHive模塊事件

BeeHive會給每一個模塊提供生命週期事件,用於與BeeHive宿主環境進行必要信息交互,感知模塊生命週期的變化。

BeeHive各個模塊會收到一些事件。在BHModuleManager中,全部的事件被定義成了BHModuleEventType枚舉。

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

};複製代碼

上面BHModuleEventType枚舉主要分爲三種,一種是系統事件,另一種是應用事件,最後一種是業務自定義事件。

1. 系統事件。

上圖是官方給出的一個系統事件基本工做流。

系統事件一般是Application生命週期事件,例如DidBecomeActive、WillEnterBackground等。

通常作法是把BHAppDelegate接管原來的AppDelegate。

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

    return YES;
}


#if __IPHONE_OS_VERSION_MAX_ALLOWED > 80400 

-(void)application:(UIApplication *)application performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem completionHandler:(void (^)(BOOL))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
{
    [[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
{
    [[BHModuleManager sharedManager] triggerEvent:BHMOpenURLEvent];
    return YES;
}
#endif


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

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidFailToRegisterForRemoteNotificationsEvent];
}

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidRegisterForRemoteNotificationsEvent];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
}

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
    [[BHModuleManager sharedManager] triggerEvent:BHMDidReceiveRemoteNotificationEvent];
}

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)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){
        [[BHModuleManager sharedManager] triggerEvent:BHMDidUpdateUserActivityEvent];
    }
}

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

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

- (BOOL)application:(UIApplication *)application willContinueUserActivityWithType:(NSString *)userActivityType
{
    if([UIDevice currentDevice].systemVersion.floatValue > 8.0f){
        [[BHModuleManager sharedManager] triggerEvent:BHMWillContinueUserActivityEvent];
    }
    return YES;
}複製代碼

這樣全部的系統事件均可以經過調用BHModuleManager的triggerEvent:來處理。

在BHModuleManager中有2個事件很特殊,一個是BHMInitEvent,一個是BHMTearDownEvent。

先來講說BHMInitEvent事件。

- (void)handleModulesInitEvent
{

    [self.BHModules enumerateObjectsUsingBlock:^(id<BHModuleProtocol> moduleInstance, NSUInteger idx, BOOL * _Nonnull stop) {
        __weak typeof(&*self) wself = self;
        void ( ^ bk )();
        bk = ^(){
            __strong typeof(&*self) sself = wself;
            if (sself) {
                if ([moduleInstance respondsToSelector:@selector(modInit:)]) {
                    [moduleInstance modInit:[BHContext shareInstance]];
                }
            }
        };

        [[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();
        }
    }];
}複製代碼

Init事件就是初始化Module模塊的事件。遍歷BHModules數組,依次對每一個Module實例調用modInit:方法。這裏會有異步加載的問題。若是moduleInstance重寫了async方法,那麼就會根據這個方法返回的值來進行是否異步加載的判斷。

modInit:方法裏面幹不少事情。好比說對環境的判斷,根據環境的不一樣初始化不一樣的方法。

-(void)modInit:(BHContext *)context
{
    switch (context.env) {
        case BHEnvironmentDev:
            //....初始化開發環境
            break;
        case BHEnvironmentProd:
            //....初始化生產環境
        default:
            break;
    }
}複製代碼

再好比在初始化的時候註冊一些協議:

-(void)modInit:(BHContext *)context
{
  [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}複製代碼

總之這裏能夠幹一些初始化須要作的事情。

再來講說BHMTearDownEvent事件。這個事件是拆除Module的。

- (void)handleModulesTearDownEvent
{
    //Reverse Order to unload
    for (int i = (int)self.BHModules.count - 1; i >= 0; i--) {
        id<BHModuleProtocol> moduleInstance = [self.BHModules objectAtIndex:i];
        if (moduleInstance && [moduleInstance respondsToSelector:@selector(modTearDown:)]) {
            [moduleInstance modTearDown:[BHContext shareInstance]];
        }
    }
}複製代碼

因爲Module是有優先級Level,因此拆除的時候須要從低優先級開始拆,即數組逆序循環。對每一個Module實例發送modTearDown:事件便可。

2. 應用事件

官方給出的應用事件工做流如上:

在系統事件的基礎之上,擴展了應用的通用事件,例如modSetup、modInit等,能夠用於編碼實現各插件模塊的設置與初始化。

全部的事件均可以經過調用BHModuleManager的triggerEvent:來處理。

- (void)triggerEvent:(BHModuleEventType)eventType
{
    switch (eventType) {
        case BHMSetupEvent:
            [self handleModuleEvent:kSetupSelector];
            break;
        case BHMInitEvent:
            //special
            [self handleModulesInitEvent];
            break;
        case BHMTearDownEvent:
            //special
            [self handleModulesTearDownEvent];
            break;
        case BHMSplashEvent:
            [self handleModuleEvent:kSplashSeletor];
            break;
        case BHMWillResignActiveEvent:
            [self handleModuleEvent:kWillResignActiveSelector];
            break;
        case BHMDidEnterBackgroundEvent:
            [self handleModuleEvent:kDidEnterBackgroundSelector];
            break;
        case BHMWillEnterForegroundEvent:
            [self handleModuleEvent:kWillEnterForegroundSelector];
            break;
        case BHMDidBecomeActiveEvent:
            [self handleModuleEvent:kDidBecomeActiveSelector];
            break;
        case BHMWillTerminateEvent:
            [self handleModuleEvent:kWillTerminateSelector];
            break;
        case BHMUnmountEvent:
            [self handleModuleEvent:kUnmountEventSelector];
            break;
        case BHMOpenURLEvent:
            [self handleModuleEvent:kOpenURLSelector];
            break;
        case BHMDidReceiveMemoryWarningEvent:
            [self handleModuleEvent:kDidReceiveMemoryWarningSelector];
            break;

        case BHMDidReceiveRemoteNotificationEvent:
            [self handleModuleEvent:kDidReceiveRemoteNotificationsSelector];
            break;

        case BHMDidFailToRegisterForRemoteNotificationsEvent:
            [self handleModuleEvent:kFailToRegisterForRemoteNotificationsSelector];
            break;
        case BHMDidRegisterForRemoteNotificationsEvent:
            [self handleModuleEvent:kDidRegisterForRemoteNotificationsSelector];
            break;

        case BHMDidReceiveLocalNotificationEvent:
            [self handleModuleEvent:kDidReceiveLocalNotificationsSelector];
            break;

        case BHMWillContinueUserActivityEvent:
            [self handleModuleEvent:kWillContinueUserActivitySelector];
            break;

        case BHMContinueUserActivityEvent:
            [self handleModuleEvent:kContinueUserActivitySelector];
            break;

        case BHMDidFailToContinueUserActivityEvent:
            [self handleModuleEvent:kFailToContinueUserActivitySelector];
            break;

        case BHMDidUpdateUserActivityEvent:
            [self handleModuleEvent:kDidUpdateContinueUserActivitySelector];
            break;

        case BHMQuickActionEvent:
            [self handleModuleEvent:kQuickActionSelector];
            break;

        default:
            [BHContext shareInstance].customEvent = eventType;
            [self handleModuleEvent:kAppCustomSelector];
            break;
    }
}複製代碼

從上述代碼能夠看出,除去BHMInitEvent初始化事件和BHMTearDownEvent拆除Module事件這兩個特殊事件之外,全部的事件都是調用的handleModuleEvent:方法。上述的switch-case裏面,除去系統事件之外的,和default裏面的customEvent之外,剩下的事件都是BHMTearDownEvent事件。

- (void)handleModuleEvent:(NSString *)selectorStr
{
    SEL seletor = NSSelectorFromString(selectorStr);
    [self.BHModules 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:[BHContext shareInstance]];
#pragma clang diagnostic pop

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

        }
    }];
}複製代碼

handleModuleEvent:方法的實現就是遍歷BHModules數組,調用performSelector:withObject:方法實現對應方法調用。

注意這裏全部的Module必須是遵循BHModuleProtocol的,不然沒法接收到這些事件的消息。

3. 業務自定義事件

若是以爲系統事件、通用事件不足以知足須要,咱們還將事件封裝簡化成BHAppdelgate,你能夠經過繼承 BHAppdelegate來擴展本身的事件。

自定義的事件的type就是BHMDidCustomEvent = 1000 。

在BeeHive裏面有一個tiggerCustomEvent:方法就是用來處理這些事件的,尤爲是處理自定義事件的。

- (void)tiggerCustomEvent:(NSInteger)eventType
{
    if(eventType < 1000) {
        return;
    }

    [[BHModuleManager sharedManager] triggerEvent:eventType];
}複製代碼

這個方法只會把自定義事件透傳給BHModuleManager進行處理,其餘一切的事件都不會作任何相應。

四. BeeHive模塊調用

在BeeHive中是經過BHServiceManager來管理各個Protocol的。BHServiceManager中只會管理已經被註冊過的Protocol。

註冊Protocol的方式總共有三種,和註冊Module是同樣一一對應的:

1. Annotation方式註冊

經過BeeHiveService宏進行Annotation標記。

BeeHiveService(HomeServiceProtocol,BHViewController)複製代碼

BeeHiveService宏定義以下:

#define BeeHiveService(servicename,impl) \
char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ \""#servicename"\" : \""#impl"\"}";複製代碼

BeeHiveDATA又是一個宏:

#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname" ")))複製代碼

最終BeeHiveService宏會在預編譯結束會徹底展開成下面的樣子:

char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}";複製代碼

這裏類比註冊Module,也是把數據存在特殊的段內,具體原理上面已經分析過了,這裏再也不贅述。

同理,經過調用static函數BHReadConfiguration,咱們就能夠拿到以前註冊到BeehiveServices特殊段裏面的各個Protocol協議對應Class字典的字符串。

"{ \"HomeServiceProtocol\" : \"BHViewController\"}"複製代碼

數組裏面存的都是這樣的一些Json字符串。

+ (NSArray<NSString *> *)AnnotationServices
{
    static NSArray<NSString *> *services = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        services = BHReadConfiguration(BeehiveServiceSectName);
    });
    return services;
}複製代碼

這是一個單例數組,裏面裝的都是以前放在特殊段裏面的Protocol協議對應Class字典的字符串數組,即爲Json字符串數組。

拿到這個數組之後,就能夠註冊全部的Protocol協議了。

- (void)registerAnnotationServices
{
    NSArray<NSString *>*services = [BHAnnotation AnnotationServices];

    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) {
                    [self registerService:NSProtocolFromString(protocol) implClass:NSClassFromString(clsName)];
                }

            }
        }
    }

}複製代碼

因爲services數組裏面存的都是Json字符串,因此先轉換成字典,而後再依次取出protocol和className。最後調用registerService:implClass:方法。

- (void)registerService:(Protocol *)service implClass:(Class)implClass
{
    NSParameterAssert(service != nil);
    NSParameterAssert(implClass != nil);

    // impClass 是否遵循了 Protocol 協議
    if (![implClass conformsToProtocol:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ module does not comply with %@ protocol", NSStringFromClass(implClass), NSStringFromProtocol(service)] userInfo:nil];
    }

    // Protocol 協議是否已經註冊過了
    if ([self checkValidService:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol has been registed", NSStringFromProtocol(service)] userInfo:nil];
    }

    NSMutableDictionary *serviceInfo = [NSMutableDictionary dictionary];
    [serviceInfo setObject:NSStringFromProtocol(service) forKey:kService];
    [serviceInfo setObject:NSStringFromClass(implClass) forKey:kImpl];

    [self.lock lock];
    [self.allServices addObject:serviceInfo];
    [self.lock unlock];
}複製代碼

在註冊registerService:implClass:以前會有2個檢查,一是檢查impClass 是否遵循了 Protocol 協議,二是檢查Protocol 協議是否已經註冊過了。若是有一個檢查出現問題,都會拋出異常。

若是檢查都過了,那麼就加入Key爲@"service"的,Value爲Protocol的名字,和Key爲@「impl」的,Value爲Class名字的兩個鍵值對。最後把這個字典存入allServices數組中。

在存儲allServices數組的時候,是要加鎖的。這裏的lock是NSRecursiveLock。防止出現遞歸引發的線程安全問題。

2. 讀取本地Pilst文件

要讀取本地的Plist文件以前,須要先設置好路徑。

[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";複製代碼

BeeHive全部的配置均可以寫在BHContext進行傳遞。

Plist文件的格式也要是數組裏麪包一個個的字典。字典裏面有兩個Key,一個是@"service",另外一個是@"impl"。

- (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];
    [self.allServices addObjectsFromArray:serviceList];
    [self.lock unlock];
}複製代碼

從Plist裏面取出數組,而後把數組加入到allServices數組裏面。

3. Load方法註冊

最後一種註冊Protocol的方法就是在Load方法裏面註冊Protocol協議。

+ (void)load
{
   [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}複製代碼

調用BeeHive裏面的registerService:service:完成Module的註冊。

- (void)registerService:(Protocol *)proto service:(Class) serviceClass
{
    [[BHServiceManager sharedManager] registerService:proto implClass:serviceClass];
}複製代碼

BeeHive裏面的registerService:service:的實現仍是調用的BHServiceManager的註冊方法registerService:implClass:。這個方法上面分析過了,再也不贅述。

至此,3種註冊Protocol的方式就完成了。

在以前分析註冊Module的時候,咱們知道在BeeHive在setContext:的時候會調用loadStaticServices方法。

-(void)loadStaticServices
{
    // 是否開啓異常檢測
    [BHServiceManager sharedManager].enableException = self.enableException;

    // 讀取本地plist文件裏面的Protocol,並註冊到BHServiceManager的allServices數組中
    [[BHServiceManager sharedManager] registerLocalServices];

    // 讀取特殊段裏面的標記數據,並註冊到BHServiceManager的allServices數組中
    [[BHServiceManager sharedManager] registerAnnotationServices];

}複製代碼

這裏雖然咱們只看到了兩種方式,可是實際上allServices數組裏面還會包括經過Load方法註冊進來的Protocol。那麼allServices數組其實是包含了3種註冊方式加進來的Protocol。

這裏就沒有註冊Module的最後一步初始化實例的過程。

可是Protocol比Module多一個方法,返回能相應Protocol實例對象的方法。

在BeeHive中有這樣一個方法,調用這個方法就能夠返回一個能相應Protocol的實例對象。

- (id)createService:(Protocol *)proto;

- (id)createService:(Protocol *)proto;
{
    return [[BHServiceManager sharedManager] createService:proto];
}複製代碼

實質是調用了BHServiceManager的createService:方法。createService:方法具體實現以下:

- (id)createService:(Protocol *)service
{
    id implInstance = nil;

    // Protocol 協議是否已經註冊過了
    if (![self checkValidService:service] && self.enableException) {
        @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"%@ protocol does not been registed", NSStringFromProtocol(service)] userInfo:nil];
    }

    Class implClass = [self serviceImplClass:service];

    if ([[implClass class] respondsToSelector:@selector(shareInstance)])
        implInstance = [[implClass class] shareInstance];
    else
        implInstance = [[implClass alloc] init];

    if (![implInstance respondsToSelector:@selector(singleton)]) {
        return implInstance;
    }

    NSString *serviceStr = NSStringFromProtocol(service);

    // 是否須要緩存
    if ([implInstance singleton]) {
        id protocol = [[BHContext shareInstance] getServiceInstanceFromServiceName:serviceStr];

        if (protocol) {
            return protocol;
        } else {
            [[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
        }

    } else {
        [[BHContext shareInstance] addServiceWithImplInstance:implInstance serviceName:serviceStr];
    }

    return implInstance;
}複製代碼

這個方法也會先檢查Protocol協議是不是註冊過的。而後接着取出字典裏面對應的Class,若是實現了shareInstance方法,那麼就生成一個單例出來,若是沒有,那麼就隨便生成一個對象出來。若是還實現了singleton,就能進一步的把implInstance和serviceStr對應的加到BHContext的servicesByName字典裏面緩存起來。這樣就能夠隨着上下文傳遞了。

id<UserTrackServiceProtocol> v4 = [[BeeHive shareInstance] createService:@protocol(UserTrackServiceProtocol)];
if ([v4 isKindOfClass:[UIViewController class]]) {
    [self registerViewController:(UIViewController *)v4 title:@"埋點3" iconName:nil];
}複製代碼

上面是官方給的例子,Module之間的調用就用這種方式,就能夠獲得很好的解耦了。

五. 其餘的一些輔助類

還有一些輔助類,在上面沒有提到的,這裏就來一個彙總,一塊兒分析了。

BHConfig這也是一個單例,裏面保存了一個config的NSMutableDictionary字典。字典維護了一些動態的環境變量,做爲BHContext的補充存在。

BHContext也是一個單例,裏面有2個NSMutableDictionary字典,一個是modulesByName,另外一個是servicesByName。BHContext主要就是用來保存各類上下文環境的。

@interface BHContext : NSObject

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

@end複製代碼

在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";複製代碼

BHTimeProfiler就是用來進行計算時間性能方面的Profiler。

BHWatchDog是能夠開一個線程,設置好handler,每隔一段時間就執行一個handler。

六. 可能還在完善中的功能

BeeHive經過處理Event編寫各個業務模塊能夠實現插件化編程,各業務模塊之間沒有任何依賴,core與module之間經過event交互,實現了插件隔離。但有時候須要模塊間的相互調用某些功能來協同完成功能。

1. 功能還有待完善

一般會有三種形式的接口訪問形式:

  1. 基於接口的實現Service訪問方式(Java spring框架實現)
  2. 基於函數調用約定實現的Export Method(PHP的extension,ReactNative的擴展機制)
  3. 基於跨應用實現的URL Route模式(iPhone App之間的互訪)

BeeHive目前只實現了第一種方式,後兩種方式還須要繼續完善。

2. 解耦還不夠完全

基於接口Service訪問的優勢是能夠編譯時檢查發現接口的變動,從而及時修正接口問題。缺點是須要依賴接口定義的頭文件,經過模塊增長得越多,維護接口定義的也有必定工做量。

3. 設計思路還能夠繼續改進和優化

BHServiceManager內部維護了一個數組,數組中的一個個字典,Key爲@"service"的,Value爲Protocol的名字,和Key爲@「impl」的,Value爲Class名字的兩個鍵值對。與其這樣設計,還不如直接使用NSMutableDictionary,Key使用Protocol,Value爲Class呢?搜索的時候減小了手動循環過程。

結尾

BeeHive做爲阿里開源的一套模塊間的解耦方案,思路仍是很值得咱們學習的。目前版本是v1.2.0,相信在後面的版本迭代更新中,功能會更加的完善,作法會更加的優雅,值得期待!

相關文章
相關標籤/搜索