BeeHive: an elegant but still decoupled framework

BeeHive: an elegant but still decoupled framework

Preface

BeeHive is a Alibaba open source iOS framework, this framework is a framework for the realization of App modular programming a solution to absorb the idea of the Spring framework Service to achieve decoupling between modules API.

The name BeeHive comes from the beehive. Honeycomb is a highly modular engineering structure in the world. So the name is used as the name of the open source project.

BeeHive: an elegant but still decoupled framework

In the previous article iOS component – routing design ideas, we analyzed the App components can be separated by way of decoupling. Then this article will take a look at how to use modular thinking how to decouple the.

(see here there will be a lot of people have doubts, then look at the difference between this article components and modules)

Description: This article is based on the BeeHive version of the v1.2.0 analysis.

Catalog

  • 1.BeeHive overview
  • 2.BeeHive module registration
  • 3.BeeHive module event
  • 4.BeeHive module call
  • 5 other auxiliary classes
  • 6 may also be in the perfect function

I. BeeHive overview

Because the BeeHive is based on the Spring Service concept, although it can make the concrete implementation of the module and interface decoupling, but can not avoid the module dependencies on the interface class.

Temporary BeeHive did not adopt invoke and performSelector:action withObject: params method. The main reason is to consider the difficulty of learning costs and dynamic call can not be achieved in the compilation of the interface to detect changes in the parameters of the inspection phase.

At present, all of the BeeHive v1.2.0 is the use of Protocol way to achieve the purpose of decoupling between modules:

1 each module exists in the form of plug-ins. Each can be independent and mutually decoupled.
2 each module specific implementation and interface call separation
3 each module also has a life cycle, also can manage.

The official also gives an architecture diagram:

BeeHive: an elegant but still decoupled framework

Next, we analyze how to register module, module event and module call.

Two. BeeHive module registration

Start from the module registration analysis, to see how BeeHive is to register each module.

In BeeHive is to manage each module through BHModuleManager. BHModuleManager will only manage modules that have been registered.

There are three ways to register Module:

1 Annotation registration

Annotation mark by BeeHiveMod macro.

BeeHiveMod (ShopModule)

The BeeHiveMod macro is defined as follows:

#define BeeHiveMod (name) / char * k##name##_mod BeeHiveDATA (BeehiveMods) = "#name""";

BeeHiveDATA is a macro:

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

Finally, the BeeHiveMod macro will fully expand into the following:

Char * kShopModule_mod __attribute ((used, section ("__DATA," "BeehiveMods" "")) = "" "ShopModule""";

Note the total logarithm of double quotes.

Here __attribute ((used, section (segmentname, SectionName)) need to explain the 2 places.

__attribute first parameter used useful. This keyword is used to modify the function. After being modified by used, it means that even if the function is not referenced, it will not be optimized under Release. If you do not add this modifier, then the Release environment linker will remove the segment that is not referenced. Specific description can look at the official document gun.

Static static variables are placed in a separate section in the order they are declared. We use __attribute__ ((section (“name”)) to indicate which segment. The data is marked with __attribute__ (used) to prevent the linker from optimizing the deleted segments.

Talk about the role of section.

The compiler generates the source code after the file is called the target file, from the file structure, it is already compiled executable file format, but has not been linked to the process. Executable file (Executable) is mainly under the PE (Portable Executable) and Linux ELF (Executable Linkable Format), they are also COFF (Common file format) format variants. The program source code is compiled into two segments: program instructions and program data. The code segment belongs to the program instruction, the data segment and the.Bss segment belong to the data segment.

BeeHive: an elegant but still decoupled framework

Specific examples are shown above, we can see the.Data data segment which is stored in the initialization of the global static variables and local static variables. The.Rodata segment is read – only data, usually const – modified variables and string constants. The.Bss segment is an uninitialized global and local static variable. Code segment in the.Text segment.

Sometimes we need to specify a special section to store the data we want. Here we put the data in the data data segment inside the “BeehiveMods” section.

Of course, there are other Attributes modifier keyword, see the official document details

Back to code:

Char * kShopModule_mod __attribute ((used, section ("__DATA," "BeehiveMods" "")) = "" "ShopModule""";

Is equivalent to:

Char * kShopModule_mod = "" "ShopModule""";

Just put the kShopModule_mod string in a special section.

Module is stored in such a special section, how to take it out?

Static NSArray< NSString *> BHReadConfiguration * (char *section) {NSMutableArray *configs = [NSMutableArray array]; Dl_info info; dladdr (BHReadConfiguration, & info); / / const struct mach_header *mhp __LP64__ #ifndef = _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; / / data segment found before storage (Module BeehiveMods and Service BeehiveServices to find a piece of memory segment) uint32_t = *memory (uint32_t*) getsectiondata (MHP, section, __DATA, & 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, section, __DATA, & size); #endif defined (__LP64__) / * * / / / special section. The data are converted into a string into an array of 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 is a data structure inside a Mach-O.

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

This data structure is the default data through

Extern int dladdr (const void *, Dl_info *);

Dladdr this function to get the data inside the Dl_info.

Dli_fname: path name, such as

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

Dli_fbase: the starting address of the shared object (Base address shared object, such as the CoreFoundation above) of

Dli_saddr: the symbol of the address
dli_sname: the name of the sign, that is, the function information of the following fourth columns

Thread 0: 0 libsystem_kernel.dylib 0x11135810a __semwait_signal + 944741 libsystem_c.dylib 0x1110dab0b + 5189232 sleep QYPerformanceMonitor 0x10dda4f1b -[ViewController tableView:cellForRowAtIndexPath:] UIKit 0x10ed4d4f4 -[UITableView + 79633 _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 1586420

By calling the static function BHReadConfiguration, we can get to register before the special section of the BeehiveMods inside the Module class name, are installed in the data string.

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

This is a single example array, which are installed in the special section before the Module name corresponding to the string array.

After getting this array, you can register all the Module.

- (void) registedAnnotationModules NSString *> {NSArray< *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];}

Finally, we need to add all the registered Module to BHModuleManager.

- (void) addModuleFromObject: (ID) object class NSString {Class; *moduleName = nil; if (object) {class = object; moduleName = NSStringFromClass (class);} else {return}; if ([class conformsToProtocol:@protocol (BHModuleProtocol)] *moduleInfo) {NSMutableDictionary = [NSMutableDictionary dictionary] basicModuleLevel; / / this method if the default does not implement the default is Normal BOOL responseBasicLevel, Level = [class instancesRespondToSelector:@selector (basicModuleLevel)]; / / Level is BHModuleNormal, is 1 int levelInt = 1; / / if the implementation of the basicModuleLevel method, then Level is BHModuleBasic if (responseBasicLevel) {/ / Lev El is Basic, BHModuleBasic is 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];};}}

Some need to note that the code has been added to the notes. BHModules is a NSMutableArray, which is stored in a dictionary, the dictionary has two Key, one is @ moduleLevel, and the other is @ @ moduleClass”. Storage has been registered when the Module to determine the Level. There is also a point to be noted, all the need to register the Module must follow the BHModuleProtocol protocol, otherwise it can not be stored.

2 read the local Pilst file

To read the local Plist file, you need to set the path.

[BHContext @ shareInstance].moduleConfigName = "BeeHive.bundle/BeeHive"; / / optional, the default is BeeHive.bundle/BeeHive.plist

BeeHive all configurations can be written in BHContext for delivery.

BeeHive: an elegant but still decoupled framework

The format of the Plist file is also a dictionary inside the array. There are two Key dictionary, one is @ moduleLevel, and the other is @ @ moduleClass”. Note that the root of the array is called @ moduleClasses”.

- (void) loadLocalModules *plistPath mainBundle] pathForResource:[BHContext {NSString = [[NSBundle 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];}

Remove the array from the Plist, and then add the array to the BHModules array.

3 Load method registration

The last way to register Module is to register the Module class in the Load method.

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

Call BeeHive inside the registerDynamicModule: to complete the registration of Module.

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

BeeHive inside the registerDynamicModule: or call the BHModuleManager registration method registerDynamicModule:

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

Finally, the call to the inside of the addModuleFromObject: BHModuleManager method, the above analysis of the above, no longer repeat.

The Load method can also be done with a macro BH_EXPORT_MODULE.

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

BH_EXPORT_MODULE macro which can be passed into a parameter, on behalf of whether the asynchronous loading Module module, if it is YES asynchronous loading, if the NO is synchronized loading.

The three way to register is done. Finally, BeeHive will also be on the Module Class operation.

First in the BeeHive initialization setContext:, will be loaded Modules and Services. Here to talk about Modules.

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

Look at what’s going on inside the loadStaticModules method.

- (void) loadStaticModules {/ / read the plist file inside the Module, and registered to the BHModules BHModuleManager [[BHModuleManager sharedManager] loadLocalModules] in the array; / / read special marker data inside, and subscribe to the BHModules BHModuleManager [[BHModuleManager sharedManager] array registedAnnotationModules]; [[BHModuleManager sharedManager] registedAllModules];}

Although here we only see two ways, but in fact, the BHModules array will also include the Load method through the registration of Module. So the BHModules array actually contains 3 kinds of registration methods plus Module.

The final step, registedAllModules key.

- (void registedAllModules) {/ / a priority from high to low order [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 (classS Tr (NSStringFromClass); if (moduleClass)) {/ / initialize all Module id< BHModuleProtocol> moduleInstance = [[moduleClass alloc] init]; [tmpArray addObject:moduleInstance];}}]; [self.BHModules removeAllObjects]; addObjectsFromArray:tmpArray] self.BHModules;}

BHModules array in the registedAllModules method, the installation is a dictionary, and then complete the registedAllModules method, which is installed inside a Module instance.

RegistedAllModules method will be in accordance with the priority of Level from large to small to sort, and then in accordance with the order of all the first instance of the Module, stored in the array. The final BHModules array which contains all the Module instance object.

Note that there are two points to be added:

  1. All Module objects are restricted to comply with the BHModuleProtocol protocol. As to why you want to comply with the BHModuleProtocol agreement, the next chapter will be described in detail.
  2. Module is not in any other place alloc created, even create a new instance of the Module, it is not under the management of the BHModuleManager system is unable to receive the BHModuleManager event distribution, created is of no significance.
BeeHive: an elegant but still decoupled framework

Three. BeeHive module event

BeeHive will provide life cycle events for each module, and the necessary information to interact with the BeeHive host environment, the perception of the module life cycle changes.

BeeHive each module will receive some events. In BHModuleManager, all events are defined as BHModuleEventType enumerations.

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

The above BHModuleEventType enumeration is mainly divided into three kinds, one is the system event, the other is the application of the event, the last one is the business custom event.

1 system events.

BeeHive: an elegant but still decoupled framework

The picture above is a basic workflow given by the government.

System events are usually Application life cycle events, such as DidBecomeActive, WillEnterBackground, etc..

The general practice is to take over the original 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 (completionHandler ^) (BOOL)) {[[BHModuleManager sharedManager] triggerEvent:BHMQuickActionEvent]}; #endif - (void) applicationWillResignActive: (UIApplication * Application sharedManager]) {[[BHModuleManager triggerEvent:BHMWillResignActiveEvent];} - (void) applicationDidEnterBackground: (UIApplication *) application sharedManager] {[[BHModuleManager triggerEvent:BHMDidEnterBackgroundEvent];} - (void) applicationWillEnterForeground: (UIApplication *) application sharedManager] {[[BHModuleManager triggerEvent:BHMWillEnterForegroundEvent];} - (void) applicationDidBecomeActive: (UIApplication *) application sharedManager] {[[BHModuleManager triggerEvent:BHMDidBecomeActiveEvent];} - (void) applicationWillTerminate: (UIApplication * application sharedManager]) {[[BHModuleManager triggerEvent:BHMWillTerminateEvent];} - (BOOL) application: (UIApplication * application openURL: (NSURL) *) URL sourceApplication: (NSString *) sourceApplication annotation: (ID) annotation sharedManager] return YES {[[BHModuleManager triggerEvent:BHMOpenURLEvent];}; #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 sharedManager] {[[BHModuleManager triggerEvent:BHMDidReceiveMemoryWarningEvent];} - (void) application: (UIApplication * application) didFailToRegisterForRemoteNotificationsWithError: (NSError * error) {BHModuleManager sharedManager] trigge " REvent:BHMDidFailToRegisterForRemoteNotificationsEvent];} - (void) application: (UIApplication * application) didRegisterForRemoteNotificationsWithDeviceToken: (NSData *) deviceToken sharedManager] triggerEvent: {[[BHModuleManager BHMDidRegisterForRemoteNotificationsEvent];} - (void) application: (UIApplication * application) didReceiveRemoteNotification: (NSDictionary *) userInfo sharedManager] {[[BHModuleManager 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) {[[BHModule Manager sharedManager] triggerEvent:BHMDidFailToContinueUserActivityEvent];}} - (BOOL) application: (UIApplication * application) continueUserActivity: (NSUserActivity * userActivity) (restorationHandler: (void ^) (NSArray * _Nullable) restorationHandler ([UIDevice) {if 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;}

All such system events can be handled by calling BHModuleManager’s triggerEvent.

There are 2 events in BHModuleManager are very special, one is BHMInitEvent, one is BHMTearDownEvent.

Let’s talk about the BHMInitEvent event.

- handleModulesInitEvent enumerateObjectsUsingBlock:^ (void) {[self.BHModules (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}}}; [[BHTimeProfiler shareInstance]]; sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@, modInit:, [moduleInstance% @ "class]]]; if ([moduleInstance respondsToSelector:@selector (async) {BO]) OL async = [moduleInstance async]; if (async) {dispatch_async (dispatch_get_main_queue (BK), ^{();});} else {BK}} {(); else (BK);}}}];

The Init event is an event that initializes the Module module. Traverse the BHModules array, followed by the modInit: method for each Module instance. There will be an asynchronous loading problem. If moduleInstance overrides the async method, then the value returned by this method will be used to determine whether the asynchronous loading.

ModInit: method inside a lot of things. For example, the judgment of the environment, according to the different environment of different methods.

- (void) modInit: (BHContext * context) {switch (context.env case BHEnvironmentDev:) {/ /.... initialization of break development environment; case BHEnvironmentProd: / /... Default: break. Initial production environment;}}

For example, in the initialization of the registration agreement:

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

Anyway, here are a few things to do.

Let’s talk about the BHMTearDownEvent event. This event is the removal of Module.

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

Since Module is a priority Level, so the removal of the need to start from a low priority, that is, the array reverse cycle. ModTearDown: events can be sent to each Module instance.

2 application events

BeeHive: an elegant but still decoupled framework

Official application event workflow:

On the basis of system events, the extension of the application of common events, such as modSetup, modInit, etc., can be used to encode the plug-in module to achieve the establishment and initialization.

All events can be handled by calling BHModuleManager’s 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; BHMDidReceiveRe MoteNotificationEvent: [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; BHMContin UeUserActivityEvent: [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;}}

As can be seen from the above code, except for the BHMInitEvent initialization event and the BHMTearDownEvent removal of the two special events of the Module event, all events are called by the handleModuleEvent: method. The above switch-case inside, remove the system events, and default inside the customEvent, the rest of the event is the application event.

- (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: method is to achieve the traversal of the BHModules array, call the performSelector:withObject: method to achieve the corresponding method call.

Note that all of the Module here must be BHModuleProtocol, otherwise the message cannot be received.

3 custom events

If you feel that the system events, generic events are not enough to meet the needs, we also simplify the event package into BHAppdelgate, you can continue to expand their own events by inheriting BHAppdelegate.

The custom event type is BHMDidCustomEvent = 1000.

In BeeHive, there is a tiggerCustomEvent: method that is used to handle these events, especially for custom events.

- (void) tiggerCustomEvent: (NSInteger) {if (eventType < 1000) {return;} [[BHModuleManager sharedManager] triggerEvent:eventType];}

This method will only pass the custom event to BHModuleManager for processing, all other events will not do any corresponding.

BeeHive: an elegant but still decoupled framework

Four. BeeHive module call

In BeeHive through BHServiceManager to manage the various Protocol. BHServiceManager will only manage the registered Protocol.

There are a total of three ways to register Protocol, and registered Module is the same one correspondence:

1 Annotation registration

Annotation mark by BeeHiveService macro.

BeeHiveService (HomeServiceProtocol, BHViewController)

The BeeHiveService macro is defined as follows:

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

BeeHiveDATA is a macro:

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

Finally, the BeeHiveService macro will fully expand into the following:

Char * kHomeServiceProtocol_service __attribute ((used, section ("__DATA," "BeehiveServices" ")) =" {/ "" HomeServiceProtocol "(" / "" "" BHViewController "")";

Here analogy registration Module, but also the existence of a special section of the data, the specific principles have been analyzed above, here is no longer repeat.

Similarly, by calling the static function BHReadConfiguration, we can get to the special section of the BeehiveServices before the registration of each Protocol protocol corresponding to the Class dictionary string.

"{/" HomeServiceProtocol/ "/" BHViewController/ "}"

The array is stored inside the Json string.

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

This is a single example array, which are installed in the special section of the Protocol protocol before the corresponding Class dictionary string array, which is an array of Json strings.

After getting this array, you can register all of the Protocol protocol.

- (void) registerAnnotationServices NSString *> {NSArray< *services = [BHAnnotation; AnnotationServices]; for (NSString *map in services NSData [map dataUsingEncoding:NSUTF8StringEncoding]) {*jsonData = 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)]; }}}

Since the services array is stored inside the Json string, so first converted into a dictionary, and then take out the protocol and className. Last call registerService:implClass: method.

- (void) registerService: (Protocol *) service implClass: (Class implClass) {NSParameterAssert (service! = Nil); NSParameterAssert (implClass! = Nil); / / impClass Protocol protocol if (compliance with [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 protocol is already registered in if ([self checkValidService:service] & & self.enableException) {@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString st "RingWithFormat:@ protocol has been% @ registed, NSStringFromProtocol (service)] userInfo:nil] NSMutableDictionary *serviceInfo;} = [NSMutableDictionary dictionary]; [serviceInfo forKey:kService]; [serviceInfo setObject:NSStringFromProtocol (service) setObject:NSStringFromClass (implClass) forKey:kImpl]; [self.lock lock]; [self.allServices addObject:serviceInfo]; [self.lock unlock];}

There will be 2 checks before registering registerService:implClass:, one is to check whether the impClass followed the Protocol protocol, and the two is to check whether the Protocol agreement has been registered. If there is a check problem, it will throw an exception.

If the check is over, then join Key as @ service, Value as the Protocol name, and Key as @ impl, Value for the Class name of the two key pairs. Finally put the dictionary into the allServices array.

When storing the allServices array, it is necessary to lock the. Lock here is NSRecursiveLock. Prevent thread security problems caused by recursion.

2 read the local Pilst file

To read the local Plist file, you need to set the path.

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

BeeHive all configurations can be written in BHContext for delivery.

BeeHive: an elegant but still decoupled framework

The format of the Plist file is also a dictionary inside the array. There are two Key dictionary, one is @ service, and the other is @ @ impl”.

- (void) registerLocalServices NSString [BHContext shareInstance].serviceConfigName {*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];}

Remove the array from the Plist, and then add the array to the allServices array.

3 Load method registration

The last way to register Protocol is to register the Protocol protocol in the Load method.

(void) load {[[BeeHive registerService:@protocol (UserTrackServiceProtocol) service:[BHUserTrackViewController class]];}

Call BeeHive inside the registerService:service: to complete the registration of Module.

- (void) registerService: (Protocol *) proto service: (Class) serviceClass {[[BHServiceManager registerService:proto implClass:serviceClass]};}

BeeHive inside the registerService:service: or call the BHServiceManager registration method registerService:implClass. This method has been analyzed above, no longer repeat.

So far, the 3 way to register the Protocol is completed.

Prior to the analysis of the registration of Module, we know that the BeeHive will call the loadStaticServices method in setContext.

- (void) loadStaticServices {/ / whether to open the anomaly detection [BHServiceManager sharedManager].enableException = self.enableException; / / read the plist file inside the Protocol, and registered to the allServices BHServiceManager [[BHServiceManager sharedManager] registerLocalServices] in the array; / / read special marker data inside, and subscribe to the allServices BHServiceManager [[BHServiceManager sharedManager] array registerAnnotationServices];}

Although here we only see two ways, but in fact, the allServices array will also include the Load method through the registration of Protocol. So the allServices array actually contains 3 kinds of registration methods plus Protocol.

There is no procedure for registering the last instance of the Module initialization.

But Protocol than Module more than one way to return to the corresponding Protocol instance object method.

In BeeHive there is such a method, call this method can return a corresponding Protocol instance object.

- (ID) createService: (Protocol *) proto - (ID) createService: (Protocol *) proto; {return [[BHServiceManager sharedManager];}

The essence is called the BHServiceManager createService: method. The createService: method is implemented as follows:

- (ID) createService: (Protocol * service) {ID implInstance = nil; / / Protocol protocol is already registered (if [self checkValidService:service] &!; & self.enableException) {@throw [NSException exceptionWithName: NSInternalInconsistencyException reason:[NSString stringWithFormat:@ protocol does not been% @ registed, NSStringFromProtocol (service)] userInfo:nil] Class implClass serviceImplClass:service] = [self;} 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); / / whether to cache 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;}

This method also checks if the Protocol protocol is registered. And then take out the corresponding dictionary Class, if the shareInstance method, then generate a single example out, if not, then it is easy to generate an object. If you can also achieve the singleton, you can further add the implInstance and serviceStr corresponding to the BHContext servicesByName dictionary cache. This can be passed along with the context.

Id< UserTrackServiceProtocol> V4 = [[BeeHive shareInstance] createService:@protocol (UserTrackServiceProtocol)]; if ([v4 isKindOfClass:[UIViewController class]]) {[self registerViewController: (UIViewController *) V4 title:@ "buried at 3" iconName:nil];}

The above example is given to the official, the call between Module in this way, you can get a good decoupling.

Five. Other auxiliary classes

There are a number of auxiliary classes, not mentioned above, here comes a summary, together with the analysis of the.

BHConfig this is also a single example, which saved a config dictionary NSMutableDictionary. The dictionary maintains some of the dynamic environment variables that exist as a supplement to the BHContext.

BHContext is also a single example, there are 2 NSMutableDictionary dictionaries, one is modulesByName, and the other is servicesByName. BHContext is mainly used to save a variety of context.

@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; *launchOptions @property (NSDictionary); 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 *openURLIte M; //Notifications Remote or Local @property (nonatomic, strong) BHNotificationsItem *notificationsItem; //user Activity Model @property (nonatomic, strong) BHUserActivityItem *userActivityItem; @end

In application:didFinishLaunchingWithOptions:, you can initialize a large amount of context information.

[BHContext shareInstance].application = Application [BHContext; shareInstance].launchOptions = launchOptions; [BHContext = shareInstance].moduleConfigName @ "BeeHive.bundle/BeeHive"; / / optional, the default is BeeHive.bundle/BeeHive.plist [BHContext = shareInstance].serviceConfigName @ BeeHive.bundle/BHService";

BHTimeProfiler is used to calculate the time performance of the Profiler.

BHWatchDog is able to open a thread, set a good handler, every time a handler.

BeeHive: an elegant but still decoupled framework

Six. May also be in the perfect function

BeeHive through the preparation of various business modules to deal with Event plug-ins can be achieved programming, there is no dependence between the various business modules, core and module through the event interaction to achieve the plug-in isolation. But sometimes it is necessary to call each other functions to complete the function.

1 features have yet to be improved

There are three forms of interface access:

  1. Implementation of Service access based on the interface (Java spring framework)
  2. Based on the function call convention to achieve Export Method (PHP extension, ReactNative extension mechanism)
  3. Implementation of URL Route mode based on cross application (iPhone App)

BeeHive is currently the only way to achieve the first, the latter two methods also need to continue to improve.

2 decoupling is not enough

The advantage of interface based Service access is that the interface can be checked at compile time to change the interface, and the interface problem can be corrected in time. The disadvantage is the need to rely on the definition of the interface header files, through the module to increase the number of maintenance interface definition also has a certain amount of work.

3 design ideas can also continue to improve and optimize

BHServiceManager internal maintenance of an array, the array of a dictionary, Key @ @ service, Value for the name of the Protocol, and Key for @ impl, Value for the Class name of the two key pairs. With this design, it is better to use NSMutableDictionary directly, Key using Protocol, Value for Class? Reduce the manual cycle while searching.

BeeHive: an elegant but still decoupled framework

Ending

BeeHive Ali as a set of open source decoupling between modules, the idea is still worth learning. The current version is v1.2.0, I believe in the later version of the iterative update, the function will be more perfect, the practice will be more elegant, it is worth looking forward to!