更好的閱讀體驗,請到個人博客閱讀: 阿里組件化框架BeeHive解析
本文是基于BeeHive版本1.6.0進行分析。
BeeHive核心思想涉及兩個部分:
- 各個模塊間從直接調用對應模塊,變成以Service的形式,避免了直接依賴。
- App生命周期的分發,將耦合在AppDelegate中的邏輯拆分,每個模塊以微應用的形式獨立存在。
Core+Plugin的形式可以讓一個應用主流程部分得到集中管理,不同模塊以plugin形式存在,便于橫向的擴展和移植。
本文會按照以下順序進行介紹:
- [BeeHive概覽]
- [BeeHive模塊生命周期事件]
- [系統事件]
- [通用事件]
- [業務自定義事件]
- [BeeHive模塊注冊]
- [Annotation方式注冊]
- [讀取本地Plist方式注冊]
- [Load方法注冊]
- [BeeHive模塊間調用]
- [Annotation方式注冊]
- [讀取本地Plist方式注冊]
- [API注冊]
- [上下文環境Context]
BeeHive概覽
BeeHive的架構圖如下所示:
圖中的BHContext,包含BeeHive的配置文件,提供全局統一上下文信息。
圖中的BHCore包含:
- BeeHive,提供組件庫對外接口
- BHModuleManager和BHModuleProtocol,注冊和創建Module邏輯
- BHServiceManager和BHServiceProtocol,注冊和創建Service邏輯
- BHRouter
Module、Service注冊和調用邏輯只和核心模塊相關,Module之間沒有直接的關聯關系。
對于Module和Service的注冊,BeeHive提供了三種不同的形式:靜態plist,動態注冊,annotation。Module、Service之間沒有關聯,每個業務模塊可以單獨實現Module或者Service的功能。
圖中包含了主要的BeeHive啟動過程以及Module注冊的時序邏輯。Module的事件分發源于BHAppDelegate中的triggerEvent。
加載Module:
- BeeHive.plist中配置的module和service是在 AppDelegate中調用
[[BeeHive shareInstance] setContext:[BHContext shareInstance]];
時加載。 - Module的實現中
+load
內部調用[BeeHive registerDynamicModule:[self class]];
動態加載。 - Module的實現中使用注解:
@BeeHiveMod(XXModule)
BHAppDelegate中除了回調系統的事件,還將App生命周期進行擴展,增加ModuleSetup,ModuleInit,ModuleSplash,此外開發人員還可以自行擴展。
擴展周期過程中,同時加入Module分析量化功能,每個模塊Init的耗時均可計算出來,為性能優化做到數據上的支持。一個App的業務增多過程中,通過分析定位Module的Init耗時可以確定需要優化的Module。
Module遵循BHModuleProtocol后,能夠捕獲App狀態的回調,并擁有App生命周期內的全局上下文,通過context可獲取配置參數,模塊資源以及服務資源。
以BeeHive作為底層框架的App,除了解耦帶來的便利,開發人員在開發新App過程中涉及相同功能的Module,無需重復造輪子,直接移植Module,開發一個App如同拼裝積木,能組合需要的功能業務。
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
BHModuleInfos和BHModulesByEvent中id<BHModuleProtocol>
均按照moduleInstances的moduleLevel和modulePriority進行了排序。
上圖中包含注冊靜態plist中service的相關邏輯,App啟動時通過BeeHive的setContext:
來觸發plist中service的注冊。service的注冊并沒有創建對應的service實例,只是在BHServiceManager中建立Service協議和實現該協議的類之間的映射關系。
上圖中是動態注冊service的邏輯,這是在App啟動事件分發時觸發。Module根據需求動態注冊某個服務,通常,注冊service的代碼在module的modInit:
或者modSetup:
中。
業務組件可以通過createService:
直接調用服務。Service的調用和實現,核心是BHServiceManager。可以單獨創建Services Interface Pod,統一放置要用的Services,這樣的業務依賴就從網狀式變成中心式,業務方只需依賴Services一個。
Service可以按需加載,BeeHive邏輯是將基礎服務注冊在plist中,業務型服務允許Service不先注冊,直到業務需要時才被動態注冊。
Service支持兩種不同模式:
- 單例: 對于全局統一且無狀態服務,建議使用這種創建形式,這樣有利于Service的統一管理以及減少不必要內存消耗。
- 多實例: 每次調用服務都重新創建新的服務,對于涉及狀態以及狀態變化的服務最適合使用多實例方式。
在多線程環境下遇到了Service讀寫問題,已通過Lock來已避免Array crash問題。
不過Service還存在如下問題:
- Service依賴關系,導致底層依賴的Service沒有被創建時就被調用。
- 規劃Service、Module創建順序,使得App達到秒開,優化性能體驗。
前者依賴問題計劃通過調度機制來解決,后者還需要將AppDelegate更多業務剝離以及實踐才可。
BeeHive使用createService:
和createService:withServiceName:
來創建實現了協議的對象,并且緩存該對象。
BeeHive模塊生命周期事件
BeeHive會給每個模塊提供生命周期事件,用于與BeeHive宿主環境進行必要信息交互,感知模塊生命周期的變化。
事件分為三種類型:
- 系統事件
- 通用事件
- 業務自定義事件
在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
};
系統事件
系統事件通常是Application生命周期事件,例如WillResignActiveEvent, DidEnterBackgroundEvent, WillEnterForegroundEvent, DidBecomeActiveEvent, WillTerminateEvent。
一般做法是使用BHAppDelegate來接管系統事件,如下所示:
//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";//可選,默認為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的實現文件中,實現了一系列的系統調用事件。如下所示:
- (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
這樣所有的系統事件都可以通過BHModuleManager的triggerEvent:
來處理。
在上述事件中,BHMInitEvent和BHMTearDownEvent事件需要做特殊處理。
先看看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事件是觸發各個module啟動時的初始化邏輯。這里從self.BHModulesByEvent
中取出BHMInitEvent事件對應的module數組,遍歷其中的每個module實例,向其發送modInit:
消息。這里會考慮是否異步執行module的初始化。如果moduleInstance重寫了async
方法,那么就會根據該方法的返回值來決定是否異步執行module的初始化。
modInit:
方法由各個module實例各自實現,可以在其中注冊一個外部模塊可以訪問的Service
接口。
-(void)modInit:(BHContext *)context
{
//注冊模塊的接口服務
[[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]];
}
再看看BHMTearDownEvent事件。這個事件中可以處理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具有優先級,且self.BHModulesByEvent
結構中,每種事件類型對應的modules數組中的module元素都已經按照優先級從高到低排列,因此逆序對modules數組中的module元素調用modTearDown:
方法。
通用事件
在系統事件的基礎之上,擴展了應用的通用事件,例如modSetup、modInit等,可以用于編碼實現各插件模塊的設置與初始化。
擴展的通用事件如下:
所有的事件都可以通過調用BHModuleManager的triggerEvent:
來處理。
- (void)triggerEvent:(NSInteger)eventType
{
[self triggerEvent:eventType withCustomParam:nil];
}
- (void)triggerEvent:(NSInteger)eventType
withCustomParam:(NSDictionary *)customParam {
[self handleModuleEvent:eventType forTarget:nil withCustomParam:customParam];
}
- (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)]];
}
}];
}
從上面的代碼可以看出,事件類型分發是在方法handleModuleEvent:forTarget:withCustomParam:
中進行。如之前所述,需要對BHMInitEvent和BHMTearDownEvent做特殊處理。同時,觸發各個module(從self.BHModulesByEvent
中獲取)中的響應事件方法通過performSelector:withObject:
來調用。
注意這里的module都是遵循BHModuleProtocol協議的。
通用事件中,可以在modSetup中設置環境變量,通過context.env可以判斷我們的應用環境狀態來決定我們如何配置我們的應用。如下所示:
-(void)modSetup:(BHContext *)context
{
switch (context.env) {
case BHEnvironmentDev:
//....初始化開發環境
break;
case BHEnvironmentProd:
//....初始化生產環境
default:
break;
}
}
業務自定義事件
如果覺得系統事件、通用事件不足以滿足需要,我們還將事件封裝簡化成BHAppdelgate,你可以通過繼承 BHAppdelegate來擴展自己的事件。通過以下方式來注冊自定義事件:
- (void)registerCustomEvent:(NSInteger)eventType
withModuleInstance:(id)moduleInstance
andSelectorStr:(NSString *)selectorStr {
if (eventType < 1000) {
return;
}
[self registerEvent:eventType withModuleInstance:moduleInstance andSelectorStr:selectorStr];
}
觸發帶參數的事件響應:
- (void)triggerEvent:(NSInteger)eventType
withCustomParam:(NSDictionary *)customParam {
[self handleModuleEvent:eventType forTarget:nil withCustomParam:customParam];
}
BeeHive模塊注冊
使用注解的方式注冊Module和Service時,Module和Service的注冊發生在加載鏡像文件時期。
plist方式注冊Module和Service,是在AppDelegate中設置BeeHive的Context時加載注冊。
Module動態注冊是在+load方法中,也是在加載鏡像時注冊。Service的動態注冊可以Module的modInit:
或者modSetup:
中,或者使用時注冊。
模塊注冊有三種方式:Annotation方式注冊、讀取本地plist方式注冊、Load方法注冊。所謂注冊,就是將Module類告知BHModuleManager來管理。由此可見,在BeeHive中是通過BHModuleManager來管理各個模塊的,BHModuleManager中只會管理已經被注冊過的模塊。
Annotation方式注冊
通過BeeHiveMod宏進行Annotation標記。
@BeeHiveMod(ShopModule)
BeeHiveMod宏定義如下:
#define BeeHiveMod(name) \
class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";
BeeHiveDATA也是一個宏:
#define BeeHiveDATA(sectname) __attribute((used, section("__DATA,"#sectname"")))
在預編譯結束后,BeeHiveMode宏最終會完全展開成下面的樣子:
@class BeeHive; char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"""))) = """ShopModule""";
這里需要注意雙引號的總對數。
關于__attribute的用法,可參考我的另一篇attribute
__attribute第一個參數used,它的作用是告訴編譯器,我聲明的這個符號是需要保留的。被used修飾以后,意味著即使函數沒有被引用,在Release下也不會被優化。如果不加這個修飾,那么Release環境鏈接器會去掉沒有被引用的段。
有時候我們需要指定一個特殊的段,來存放我們想要的數據。這里我們就把數據存在__DATA數據段里面的"BeehiveMods"section中。 Attributes的修飾關鍵字section ("section-name”)
可以達到此要求。
上述代碼中:
@class BeeHive; char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"""))) = """ShopModule""";
去掉__attribute的屬性,相當于:
@class BeeHive; char * kShopModule_mod = """ShopModule""";
只不過是將kShopModule_mod變量存儲在了__DATA段的BeehiveMods section中。
這樣,所有的Module類名的字符串都會放置在__DATA段BeehiveMods section中,那么如何取出這些字符串呢?
這里先介紹一下__attribute__((constructor))
:
constructor:顧名思義,構造器加上這個屬性的函數會在可執行文件(或 shared library)load時被調用,可以理解為在 main() 函數調用前執行:
__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
:這個函數是用來注冊回調,當dyld鏈接符號時,調用此回調函數。在dyld加載鏡像時,會執行注冊過的回調函數;當然,我們也可以使用下面的方法注冊自定義的回調函數,同時也會為所有已經加載的鏡像執行回調:
/*
* 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))
對于每一個已經存在的鏡像,當它被動態鏈接時,都會執行回調void (*func)(const struct mach_header* mh, intptr_t vmaddr_slide)
,傳入文件的mach_header以及一個虛擬內存地址 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
中的數據結構:
/*
* 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 */
};
通過調用BHReadConfiguration函數,我們就可以拿到之前注冊到BeehiveMods特殊段里面的各個Module的類名,該函數返回類名字符串的數組。
然后將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 ;
}
/** 檢測是否已存在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 這個方法如果不實現,Level是Normal: 1 */
BOOL responseBasicLevel = [class instancesRespondToSelector:@selector(basicModuleLevel)];
int levelInt = 1;
if (responseBasicLevel) {
/** basicModuleLevel 這個方法如果實現,Level是Basic: 0 */
levelInt = 0;
}
[moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
if (moduleName) {
[moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
}
[self.BHModuleInfos addObject:moduleInfo];
/** 初始化module實例 */
id<BHModuleProtocol> moduleInstance = [[class alloc] init];
[self.BHModules addObject:moduleInstance];
[moduleInfo setObject:@(YES) forKey:kModuleInfoHasInstantiatedKey];
/** 將module按照優先級排序 */
[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注冊事件 */
[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];
});
}
}
}
所有需要注冊的Module必須遵循BHModuleProtocol協議,否則不會被存儲。
讀取本地Plist方式注冊
首先需要設置本地Plist文件的讀取路徑:
[BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";//可選,默認為BeeHive.bundle/BeeHive.plist
BeeHive的所有配置都可以通過BHContext進行傳遞。
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]];
設置BHContext時就會注冊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方法注冊
// 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方法最終是調用BHModuleManager中的registerDynamicModule:
方法來處理,該方法已在上一節中說明。
+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宏中傳入了參數isAsync,代表是否異步加載Module模塊。如果是YES,則表示需要異步加載,NO則表示同步加載。
回過頭來看看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];
}
重點關注一下registedAllModules方法 :
- (void)registedAllModules {
/** 按照優先級從大到小順序排列 */
[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對象注冊系統事件
[self registerAllSystemEvents];
}
在經歷registedAllModules方法之后,所有注冊的module都生成了對應的實例對象。
注意:
- 所有的Module的對象都要是遵守BHModuleProtocol協議的。
- Module不能在任何其他地方alloc創建出來,即使創建一個新的Module實例出來,它也并不在BHModuleManager的管理下,無法接收BHModuleManager分發的系統事件。
BeeHive模塊間調用
通過處理Event編寫各個業務模塊可以實現插件化編程,各業務模塊之間沒有任何依賴,core與module之間通過event交互,實現了插件隔離。但有時候我們需要模塊間的相互調用某些功能來協同完成功能。目前模塊間的調用使用基于接口的實現Service訪問方式(Java spring框架實現)?;诮涌赟ervice訪問的優點是可以編譯時檢查發現接口的變更,從而及時修正接口問題。缺點是需要依賴接口定義的頭文件,通過模塊增加得越多,維護接口定義的也有一定工作量。
模塊間調用的協議都是通過BHServiceManager來維護的。
BeeHive提供了三種方式來注冊協議,這里和module的注冊方式相同:Annotation方式注冊、讀取本地plist方式注冊、API注冊。
Annotation方式注冊
使用@BeeHiveService
進行Annotation標記。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)
在預編譯結束后會完全展開成如下所示:
@class BeeHive; char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"""))) = "{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}”;
這里類比注冊module時的Annotation解析,也是把數據存在特殊的段內,具體的原理可以參考注冊module的分析。
同理,通過調用函數BHReadConfiguration
讀取之前注冊到特殊段BeehiveServices中的數據,這里是如下所示的json字符串,{協議字符串:實現該協議的class類名字符串},如下所示:
{"HomeServiceProtocol":"BHViewController”}
讀取到數據后,進行service的注冊:
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;
}
//協議是否已經注冊
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];
}
}
在注冊協議前會有兩個檢查registerService:implClass:
:
- 檢查implClass是否遵循了service
- 檢查service協議是否已經注冊
如果兩個條件有一個沒有檢查通過,則會拋出異常。
如果條件通過,則會在allServicesDict中加入鍵值對,{NSStringFromProtocol(service):NSStringFromClass(implClass)}
讀取本地Plist方式注冊
讀取本地的plist文件之前,需要先設置plist文件路徑。
//AppDelegate.m
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService”;
BeeHive的配置都可以通過BHContext進行傳遞。
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">
<array>
<dict>
<key>service</key>
<string>UserTrackServiceProtocol</string>
<key>impl</key>
<string>BHUserTrackViewController</string>
</dict>
</array>
</plist>
注冊plist中service的時機同注冊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";//可選,默認為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];
}
注冊service的具體實現如下:
- (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];
}
注冊完成之后,allServicesDict中的值如下:
{@"HomeServiceProtocol" : @"BHViewController", @"UserTrackServiceProtocol" : @"BHUserTrackViewController"}
注意:NSString *plistPath = [[NSBundle mainBundle] pathForResource:serviceConfigName ofType:@"plist”];
這段代碼,如果主工程Podfile中未使用了use_frameworks!
,則可以正常獲取到plist,如果使用了use_frameworks!
,則得使用其他方式獲取,具體的方案請google。
API注冊
API注冊service使用的api是BeeHive的接口- (void)registerService:(Protocol *)proto service:(Class)serviceClass
, 該接口內部實現也是調用BHServiceManager
的registerService: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:
方法內部或者modSetup:
方法內部,具體可以查看使用文檔或者demo。
注意:BHMSetupEvent和BHMInitEvent事件都會在項目的- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
函數內部觸發。
BHServiceManager中的allServicesDict包含了所有方式注冊的service。
與module注冊相比,service的注冊沒有對實現協議的對象進行初始化。只是將協議和實現協議的對象之間的這種關聯關系存儲和維護,而在module的注冊過程中,對所有注冊的module進行實例的初始化。
因此,在BHServiceManager的公共接口中,有一組createService:
接口用于訪問實現協議的對象。
- (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];
}
從上面的實現中可以看出:
- service對象存在單例和多實例的區別。
- 如果BHServiceProtocol協議對象實現了
singleton
返回YES,且shouldCache入參值是YES,則通過createService:
獲取的對象為單例對象,如果singleton
方法返回的是NO,則每次調用createService:
都會創建一個新的對象。 - 這里的單例存在線程安全問題。如果開發者將BHServiceProtocol協議對象實現了
singleton
返回YES,且shareInstance
方法的實現是返回單例對象,則不管shouldCache的入參值是YES還是NO,createService:
都會是同一個實例。但是,如果開發者將shareInstance
方法的實現只是返回對象(非單例),即使singleton
返回YES,shouldCache入參值是YES,也可能存在多個實例的情況。
上下文環境Context
BeeHive中使用BHContext初始化設置應用的項目信息,并在各模塊間共享整個應用程序的信息。例如:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
[BHContext shareInstance].env = BHEnvironmentDev; //定義應用的運行開發環境
[BHContext shareInstance].application = application;
[BHContext shareInstance].launchOptions = launchOptions;
[BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/CustomModulePlist";//可選,默認為BeeHive.bundle/BeeHive.plist
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/CustomServicePlist";//可選,默認為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
- 應用的運行開發環境
- 應用啟動信息:application,launchOptions
- module和service的plist配置信息
- 緩存的service
- 3D-Touch, OpenURL moduel, Remote Or Local Notifications, User Activity Model等
最后,附一張BeeHive主要類的的類圖關系。
如果覺得本文對你有幫助,就請用微信隨意打賞我吧_