preface

BeeHive is an open source iOS framework of Alibaba Company. This framework is an implementation scheme of App modular programming framework, which absorbs the concept of Spring framework Service to achieve API decoupling between modules.

The name BeeHive is inspired by honeycombs. Honeycomb is one of the most highly modular engineering structures in the world, and the hexagonal design brings infinite expansion possibilities. So the name was used as the name of the open source project.

In the previous article on iOS components-Routing design, we analyzed how App components can be decoupled by routing. So this article looks at how to decouple using the idea of modularity.

(See the difference between components and modules in this article.)

Note: This article is based on BeeHive V1.2.0 version for analysis.

directory

  • 1. The BeeHive overview
  • 2. Register the BeeHive module
  • 3. The BeeHive module event
  • 4. The BeeHive module is invoked
  • 5. Other helper classes
  • 6. Features that may still be in progress

BeeHive Overview

BeeHive is based on the Service concept of Spring. Although it can decouple the concrete implementation and interface between modules, it cannot avoid the dependency of modules on interface classes.

BeeHive does not use invoke and performSelector: Action withObject: Params methods for now. The main reasons are the difficulty of learning costs and the inability of dynamic invocation implementations to detect interface parameter changes during the compile check phase.

Currently, BeeHive V1.2.0 uses Protocol to achieve decoupling between modules:

1. Each module exists in the form of plug-ins. Each can be independent and decoupled from the other. The specific implementation of each module is separated from the interface call. 3. Each module also has a life cycle and can be managed.

The official architecture diagram is also provided:

Next, we’ll look at how module registration, module events, and module calls are decoupled.

Register the BeeHive module

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

In BeeHive, each module is managed through the BHModuleManager. Only registered modules are managed in the BHModuleManager.

There are three ways to register a Module:

1. Annotation method registration

Annotation is marked by BeeHiveMod macro.


BeeHiveMod(ShopModule)Copy the code

The BeeHiveMod macro is defined as follows:



#define BeeHiveMod(name) \
char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name"";Copy the code

BeeHiveDATA is another macro:


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

Eventually, the BeeHiveMod macro will be fully expanded at the end of the precompilation:



char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"""=)))"""ShopModule""";Copy the code

Note the total logarithm of double quotes.

__attribute((used,section(” SegmentName, sectionName “)))) requires two explanations.

The __attribute first parameter, used, is useful. This keyword is used to modify functions. The used modifier means that the function is not optimized under Release even if it is not referenced. Without this modifier, unreferenced segments are removed from the Release environment linker. See the official GUN documentation for a detailed description.

Static variables are placed in a separate section in the order in which they are declared. We specify which segment by using __attribute__((section(“name”))). The data is tagged with __attribute__((used)) to prevent the linker from optimally removing unused segments.

Now let’s talk a little bit more about what section does.

The file generated after the compiler compiles the source code is called an object file, which is already a compiled executable file format from the point of view of file structure, but has not gone through the linking process. Executable files are mainly PE(Portable Executable) under Windows and ELF(Executable Linkable Format) under Linux. They are also variations of the Common File Format (COFF) format. Source code is compiled into two main segments: program instructions and program data. Code segment belongs to program instruction, data segment and. BSS segment belong to data segment.

For a specific example, see the figure above. The data section stores initialized global static variables and local static variables. The. Rodata section stores read-only data, typically const variables and string constants. The. BSS section holds uninitialized global variables and local static variables. The code snippet is in the.text section.

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

There are, of course, other Attributes modifier keywords, as described in the official documentation

Back to the code:


char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"""=)))"""ShopModule""";Copy the code

Equivalent to:



char * kShopModule_mod = """ShopModule""";Copy the code

All it does is put the kShopModule_mod string in a special section.

Module is stored in a special section like this. How do you get it out?



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;
    // find a piece of memory for the previously stored data segment (Module find beehive mode segment and Service find BeehiveServices segment)
    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__) */

    // Convert the data in a particular section to a string and store it in an array
    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;
}Copy the code

Dl_info is a data structure within a 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;Copy the code

The data in this data structure is passed by default


extern int dladdr(const void *, Dl_info *);Copy the code

The dladdr function retrieves data from Dl_info.

Dli_fname: indicates the path name, for example

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

Dli_fbase: The Base address of a shared object (such as CoreFoundation above)

Dli_saddr: indicates the address of the symbol. Dli_sname: indicates the name of the symbol, which is the function information in the fourth column below


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:] + 1586420Copy the code

By calling the static BHReadConfiguration function, we can retrieve the class names of each Module registered in the BeehiveMods special section as strings in the data.



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

This is a singleton array containing an array of strings corresponding to the names of the modules previously placed in the special section.

Once you have this array, you can register all modules.



- (void)registedAnnotationModules
{

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

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

}Copy the code

Finally, you need to add all registered Modules to the 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]; // If basicModuleLevel is not implemented by default, Default is Normal Level BOOL responseBasicLevel = [class instancesRespondToSelector: @ the selector (basicModuleLevel)]; // Level is BHModuleNormal, which is 1 int levelInt = 1; // If basicModuleLevel is implemented, then Level is BHModuleBasic if (responseBasicLevel) {// Level is Basic, BHModuleBasic is 0 levelInt = 0; } / / @ "moduleLevel" as the Key, Level of Value [moduleInfo setObject: @ (levelInt) forKey: kModuleInfoLevelKey]; If (moduleName) {/ / @ "moduleClass" as the Key, moduleName to Value [moduleInfo setObject: moduleName forKey: kModuleInfoNameKey]; } [self.BHModules addObject:moduleInfo]; }}Copy the code

Some notes have been added to the above code. BHModules is an NSMutableArray, which stores a dictionary with two keys, one is @”moduleLevel” and the other is @”moduleClass”. When storing registered modules, check the Level. It should also be noted that all modules to be registered must comply with the BHModuleProtocol or they cannot be stored.

2. Read the local Pilst file

Before reading the local Plist file, you need to set the path.


    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive";/ / optional, default is extracted. The bundle/BeeHive plistCopy the code

All BeeHive configurations can be written to BHContext and passed.

The Plist file is also formatted as a dictionary in an array. There are two keys in the dictionary, one is @”moduleLevel” and the other is @”moduleClass”. Notice that the name of the root array is @” 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];

}Copy the code

Take the array from the Plist and add the array to the BHModules array.

3. Load method registration

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


+ (void)load
{
    [BeeHive registerDynamicModule:[self class]];
}Copy the code

Register Module by calling registerDynamicModule in BeeHive.



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

BeeHive registerDynamicModule: registerDynamicModule:



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

}Copy the code

Finally, the addModuleFromObject: method in the BHModuleManager is called, which we’ve already analyzed.

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 [[NSString stringWithUTF8String:#isAsync] boolValue]; }Copy the code

The BH_EXPORT_MODULE macro can pass in a parameter that indicates whether the Module is loaded asynchronously, if YES, or synchronously if NO.

There are three ways to register. Finally, BeeHive operates on the classes of these modules.

When BeeHive initializes setContext:, it loads Modules and Services, respectively. Modules.

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

    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        [self loadStaticServices];
        [self loadStaticModules];
    });
}Copy the code

Take a look at what’s going on in the loadStaticModules method.



- (void)loadStaticModules
{
    // Read the Module in the local plist file and register it in the BHModules array of the BHModuleManager
    [[BHModuleManager sharedManager] loadLocalModules];

    // Read the markup data in the special section and register it in the BHModules array of the BHModuleManager
    [[BHModuleManager sharedManager] registedAnnotationModules];

    [[BHModuleManager sharedManager] registedAllModules];

}Copy the code

We’ve only seen two ways here, but the BHModules array actually includes modules registered with the Load method. So the BHModules array is actually a Module with three registrations added to it.

The last step, registedAllModules, is key.


- (void)registedAllModules
{

    // Sort by priority from highest to lowest
    [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)) {

            // Initialize all modules
            id<BHModuleProtocol> moduleInstance = [[moduleClass alloc] init]; [tmpArray addObject:moduleInstance]; }}]; [self.BHModules removeAllObjects];

    [self.BHModules addObjectsFromArray:tmpArray];

}Copy the code

Before registedAllModules, the BHModules array is loaded with dictionaries, and after registedAllModules, the BHModules array is loaded with modules instances.

The registedAllModules method sorts all Module instances in order of Level priority and then initializes all Module instances in that order and stores them in an array. The final BHModules array contains all Module instance objects.

Note that there are two additional points to note:

  1. Restricts all Module objects to comply with the BHModuleProtocol. Why BHModuleProtocol is followed is explained in the next section.
  2. Module cannot be alloc created anywhere else, and even if a new Module instance is created, it will not be managed by the BHModuleManager and will not receive system events distributed by the BHModuleManager, so creating it would make no sense.

BeeHive module event

BeeHive provides life cycle events for each module to interact with the BeeHive host environment and sense changes in the module’s life cycle.

BeeHive modules receive events. In the BHModuleManager, all events are defined as the BHModuleEventType enumeration.



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

};Copy the code

The BHModuleEventType enumeration is divided into three types: system events, application events, and service – defined events.

1. System events.

The picture above is an official presentation of the basic workflow of a system event.

System events are typically Application life cycle events, such as DidBecomeActive, WillEnterBackground, and so on.

The general approach is to take the BHAppDelegate over the 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;
}Copy the code

This way all system events can be handled by calling BHModuleManager triggerEvent:.

There are two special events in the BHModuleManager, BHMInitEvent and BHMTearDownEvent.

Let’s start with bhminit events.



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

An Init event is an event that initializes the Module. Iterate through the BHModules array, calling the modInit: method on each Module instance in turn. There are problems with asynchronous loading. If the moduleInstance overrides the async method, the value returned by the method will be used to determine whether the async method is loaded asynchronously.

ModInit: Methods do a lot of things. For example, judging the environment, different initialization methods depending on the environment.

- (void)modInit:(BHContext *)context
{
    switch (context.env) {
        case BHEnvironmentDev:
            / /... Initialize the development environment
            break;
        case BHEnvironmentProd:
            / /... Initialize the production environment
        default:
            break; }}Copy the code

For example, register some protocols during initialization:

- (void)modInit:(BHContext *)context
{
  [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service: [BHUserTrackViewController class]].
}Copy the code

Anyway, we can do some of the initialization stuff here.

So BHMTearDownEvent event. This event dismantles the 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]]; }}}Copy the code

Module has a priority Level, so it needs to be removed from the lower priority, that is, array reverse loop. Send the modTearDown: event to each Module instance.

2. Application events

The official application event workflow looks like this:

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

All events can be handled by calling 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; }}Copy the code

As you can see from the above code, all events are called handleModuleEvent: methods, except for two special events, the BHMInitEvent initialization event and the BHMTearDownEvent Removing Module event. In switch-case, except for system events and customEvent in default, all events are BHMTearDownEvent events.


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

HandleModuleEvent: the implementation of the methods is to traverse BHModules array, call performSelector: withObject: method corresponding method call.

Note that all modules here must be BHModuleProtocol compliant or they will not receive messages for these events.

3. User-defined service events

If system events and generic events are not enough, we’ve also simplified event encapsulation to BHAppdelgate, which you can extend your own events by inheriting BHAppdelegate.

The type of the custom event is BHMDidCustomEvent = 1000.

There is a tiggerCustomEvent in BeeHive: the method is used to handle these events, especially custom events.


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

    [[BHModuleManager sharedManager] triggerEvent:eventType];
}Copy the code

This method only passes custom events through to the BHModuleManager for processing and does nothing for other events.

Call the BeeHive module

In BeeHive, the BHServiceManager manages each Protocol. BHServiceManager manages only registered protocols.

There are three ways to register Protocol, which correspond to Module registration:

1. Annotation method registration

Annotation is marked through the BeeHiveService macro.


BeeHiveService(HomeServiceProtocol,BHViewController)Copy the code

The BeeHiveService macro is defined as follows:



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

BeeHiveDATA is another macro:


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

The final BeeHiveService macro will be fully expanded by the end of the precompilation:


char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"""=)))"{ \"""HomeServiceProtocol""\" : \"""BHViewController""\"}";Copy the code

Here is an analogy to the registration Module, which also stores data in a special section. The specific principle has been analyzed above and will not be described here.

Similarly, by calling static BHReadConfiguration, we can retrieve the Class dictionary strings for each Protocol that was registered in the BeehiveServices special section.


    "{ \"HomeServiceProtocol\" : \"BHViewController\"}"Copy the code

The array is just a bunch of Json strings like this.


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

This is a singleton array containing an array of strings that correspond to the Class dictionary of the Protocol used in the special section.

Once you have this array, you can register all protocols.



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

            }
        }
    }

}Copy the code

Since the “Services” array contains Json strings, we first convert it to a dictionary and then retrieve protocol and className. Last call registerService: the implClass: method.



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

    // Whether impClass complies with 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 Indicates whether the Protocol has been registered
    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];
}Copy the code

In registered registerService: before the implClass: there will be two checks, one is to check whether impClass followed Protocol agreement, 2 it is to check whether the Protocol agreement is already registered. If there is a problem with either check, an exception is thrown.

If all checks are done, add a key-value pair with Key @”service” and Value the name of Protocol, and a key-value pair with Key @” impl “and Value the name of Class. Finally, store the dictionary in the allServices array.

When storing the allServices array, lock it. The lock here is NSRecursiveLock. Prevent thread safety problems caused by recursion.

2. Read the local Pilst file

Before reading the local Plist file, you need to set the path.


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

All BeeHive configurations can be written to BHContext and passed.

The Plist file is also formatted as a dictionary in an array. There are two keys in the dictionary, one is @”service” and the other is @”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];
}Copy the code

Take the array from Plist and add the array to allServices.

3. Load method registration

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



+ (void)load
{
   [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service: [BHUserTrackViewController class]].
}Copy the code

Register Module by calling registerService:service: in BeeHive.



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

Inside the BeeHive registerService: service: the implementation of the BHServiceManager registration method registerService: or call the implClass:. This method has been analyzed above and will not be repeated.

At this point, the three ways to register the Protocol are complete.

In the previous Module registration analysis, we know that BeeHive calls the loadStaticServices method in setContext:.

- (void)loadStaticServices
{
    // Whether to enable exception detection
    [BHServiceManager sharedManager].enableException = self.enableException;

    // Read the Protocol from the local plist file and register it with BHServiceManager's allServices array
    [[BHServiceManager sharedManager] registerLocalServices];

    // Read the marker data in the special section and register it with BHServiceManager's allServices array
    [[BHServiceManager sharedManager] registerAnnotationServices];

}Copy the code

Here we have only seen two ways, but in fact the allServices array also contains the Protocol registered through the Load method. So the allServices array is actually a Protocol that contains three registration methods added in.

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

But Protocol has one more method than Module, and returns methods that correspond to the Protocol instance object.

BeeHive has a method that returns an instance object corresponding to the Protocol.


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

- (id)createService:(Protocol *)proto;
{
    return [[BHServiceManager sharedManager] createService:proto];
}Copy the code

The BHServiceManager createService: method is called. CreateService: The method is implemented as follows:


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

    // Protocol Indicates whether the Protocol has been 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 = [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);

    // Whether cache is required
    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;
}Copy the code

This method also checks first if the Protocol is registered. If shareInstance is implemented, generate a singleton. If not, generate an object. If singleton is implemented, we can further cache implInstance and serviceStr in the servicesByName dictionary of the BHContext. This can then be passed with context.


id<UserTrackServiceProtocol> v4 = [[BeeHive shareInstance] createService:@protocol(UserTrackServiceProtocol)];
if ([v4 isKindOfClass:[UIViewController class{[]])self registerViewController:(UIViewController *)v4 title:Buried point 3 "@" iconName:nil];
}Copy the code

Here is the official example. Calls between modules can be well decoupled in this way.

5. Some other auxiliary classes

There are some auxiliary classes, not mentioned above, here is a summary, together with the analysis.

BHConfig This is also a singleton that holds an NSMutableDictionary of config. The dictionary maintains dynamic environment variables as a complement to BHContext.

BHContext is also a singleton with two NSMutableDictionary dictionaries, one modulesByName and the other servicesByName. BHContext is basically used to hold various contexts.


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

@endCopy the code

In application: didFinishLaunchingWithOptions: when you can initialize a lot of context information.


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

A BHTimeProfiler is a Profiler for computing time performance.

BHWatchDog is a way to start a thread, set up a handler, and execute a handler every once in a while.

Six. Functions that may still be in progress

BeeHive can realize plug-in programming by processing events to write each business module. There is no dependence between each business module. Core and Module interact with each other through events to realize plug-in isolation. However, sometimes it is necessary to call some functions between modules to complete the function cooperatively.

1. The functions need to be improved

There are usually three forms of interface access:

  1. Interface Based Implementation of Service Access (Java Spring Framework implementation)
  2. Export Method based on function call convention (PHP extension, ReactNative extension mechanism)
  3. URL Route Pattern based on Cross-application Implementation (Access between iPhone Apps)

BeeHive currently only implements the first approach, and the latter two approaches need to be improved.

2. Decoupling is not complete enough

The advantage of interface-based Service access is that interface changes can be detected by compile-time inspection and interface problems can be corrected in time. The disadvantage is that you need to rely on the header file of the interface definition, and the more you add through modules, the more work it takes to maintain the interface definition.

3. The design idea can be further improved and optimized

BHServiceManager maintains an array of dictionaries with keys @”service” and values for Protocol names, and key-value pairs with keys @” impl “and values for Class names. Instead of designing it this way, why not just use NSMutableDictionary, Key using Protocol, and Value as Class? Reduce the manual loop when searching.

At the end

BeeHive as ali open source a set of modules between the decoupling scheme, the idea is still worth our learning. The current version is V1.2.0, I believe that in the later version of the iteration update, the function will be more perfect, the practice will be more elegant, worth looking forward to!