componentization

This paper mainly introduces three communication modes of componentization.

Three commonly used componentized communication schemes

  • Componentized communication scheme
    • The most important aspect of componentization is the communication of sibling modules
    • There are three commonly used schemes
      • URL Scheme
      • Target – Action
      • Protocol-class match

The URL Scheme routing

  • Make the URL handle local jumps
  • Register & call through the middle layer (load method registers the called to the middle layer)
  • The registry does not use reflection
  • Non-lazy loading/registry maintenance/parameters

URL Scheme simple example of routing

Use the following simple example to import URL routing

//MTMediator.h --- start
typedef void(^MTMediatorProcessBlock)(NSDictionary *params);

+ (void)registerScheme:(NSString *)scheme processBlock:(MTMediatorProcessBlock)processBlock;

+ (void)openUrl:(NSString *)url params:(NSDictionary *)params;
//MTMediator.h --- end

//MTMediator.m --- start
+ (NSMutableDictionary *)mediatorCache{
    static NSMutableDictionary *cacheScheme;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        cacheScheme = @{}.mutableCopy;
    });

    return cacheScheme;
}

+ (void)registerScheme:(NSString *)scheme processBlock:(MTMediatorProcessBlock)processBlock{
    if (scheme.length > 0 && processBlock) {
        [[[self class] mediatorCache] setObject:processBlock forKey:scheme]; }} + (void)openUrl:(NSString *)url params:(NSDictionary *)params{
    MTMediatorProcessBlock block = [[[self class] mediatorCache] objectForKey:url];
    if(block) { block(params); }}//MTMediator.m --- end

// Register -- start
+ (void)load {
    [MTMediator registerScheme:@"detail://" processBlock:^(NSDictionary * _Nonnull params) {
        NSString *url = (NSString *)[params objectForKey:@"url"];
        UINavigationController *navigationController = (UINavigationController *)[params objectForKey:@"controller"];
        MTDetailViewController *controller = [[MTDetailViewController alloc] initWithUrlString:url];
// controller.title = [NSString stringWithFormat:@"%@", @(indexPath.row)];
        [navigationController pushViewController:controller animated:YES];
    }];
}
// Register -- end

// Call -- start
//URL Scheme
[MTMediator openUrl:@"detail://" params:@{@"url":item.articleUrl,@"controller":self.navigationController}];
// Call -- end
Copy the code

Description:

  • The URL Scheme mechanism is referenced
  • Arguments are passed through the dictionary and are opaque to callers

At present, most routing tools on iOS are based on URL matching, or based on naming conventions, using the Runtime method for dynamic invocation

The advantage of these dynamic schemes is that they are simple to implement, while the disadvantage is that they need to maintain string tables, or rely on naming conventions, and cannot expose all problems at compile time, requiring errors to be discovered at run time.

MGJRouter

The URL routing mode is mainly MGJRouter represented by Mogujie

The realization idea is as follows:

  • Component modules are instantiated when the App starts, and then those components are directed toModuleManagerregisteredUrl, sometimes you don’t need to instantiate, use class registration
  • When component A needs to call component B, theModuleManagerPass the URL, and parameters follow the URL in GET mode, similar to openURL. The ModuleManager is then responsible for scheduling component B and finally completing the task.
/ / 1, a registered a URL MGJRouter. RegisterURLPattern (" app: / / home ") {(info) and in print (" info: Mgjrouter. openURL("app://home")Copy the code

Advantages of URL routing

  • Highly dynamic, suitable for apps that often carry out operational activities, such as e-commerce
  • Routing rules of multiple platforms can be centrally managed
  • Easy to adapt to URL schemes

Disadvantages of URl routing

  • There are limited ways to pass arguments, and no compiler can do parameter type checking, so all arguments are converted by string
  • Applies only to interface modules, not to generic modules
  • The format of the parameter is not clear, it is a flexible dictionary, and there needs to be a place to look up the format of the parameter.
  • Do not support the storyboard
  • Rely on string hard coding, difficult to manage, mushroom street to do a background management.
  • There is no guarantee that the module being used exists
  • The decoupling capability is limited, and the “registration”, “implementation” and “use” of URL must use the same character rules. Once any party makes modification, the code of other parties will become invalid, and it is difficult to reconstruct

In addition to the MGJRouter, there are the following tripartite frameworks

  • routable-ios
  • JLRoutes
  • HHRouter

Target – Action

  • Detach from business logic
  • Calls are made through the middle tier
  • The middle layer uses runtime reflection
  • Middle tier code optimization

Target-action simple example

Introduction of simple examples

//MTMediator.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface MTMediator : NSObject

//target action
+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl;

@end

NS_ASSUME_NONNULL_END

//MTMediator.m
#import "MTMediator.h"

@implementation MTMediator

+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl{
    Class detailVC = NSClassFromString(@"MTDetailViewController");
    UIViewController *controller = [[detailVC alloc] performSelector:NSSelectorFromString(@"initWithUrlString:") withObject:detailUrl];

    return controller;
}

@end

/ / call
//Target - Action
 UIViewController *vc = [MTMediator detailViewControllerWithUrl:item.articleUrl];
 vc.title = @"Details.";
 [self.navigationController pushViewController:vc animated:YES];
 
Copy the code

Description:

  • Hardcoded (direct invocation, not good for maintenance and extension)
  • Perform can pass a maximum of two parameters to the dictionary to avoid too many parameters

    - (id)performSelector:(SEL)aSelector withObject:(id)object1 withObject:(id)object2;
  • InitWithUrlString: method must be implemented otherwise sel crash cannot be found
  • The business logic is harmonized in Mediator, and each module can write its own MTMediator extension

CTMediator

The main representative framework of the tripartite framework is CTMediator of Casatwy

This solution is based on the OC runtime/category feature to dynamically fetch modules, such as NSClassFromString to fetch classes and create instances, and NSInvocation to dynamically invoke methods via performSelector + NSInvocation

The realization idea is as follows:

  • 1. Add a new interface to the route using the classification, and obtain the corresponding class in the interface through a string
  • 2. Create an instance through Runtime and call its methods dynamically

CTMediator is easy to use:

CTMediator{@objc func A_showHome()->UIViewController? {/ / in the swift when using, need to correspond to the name of the target of the project, or you will find the view controller let params = [kCTMediatorParamsKeySwiftTargetModuleName: "CJLBase_Example"] / / CTMediator performTarget: action: params: shouldCacheTarget: method by passing in the name, If let vc = self.performTarget("A", action: "Extension_HomeViewController", params: params, shouldCacheTarget: false) as? UIViewController{return vc} return nil} //******* NSObject { @objc func Action_Extension_HomeViewController(_ params: [String: Any])->UIViewController{let home = HomeViewController() return home}} ******* CTMediator.sharedInstance().A_showHome() { self.navigationController? .pushViewController(vc, animated: true) }Copy the code

The reference relationship between its modules is shown in the figure below:

advantages

  • usingclassificationYou can explicitly declare interfaces for compilation checks
  • implementationlightweight

disadvantages

  • Need to be inmediatortargetTo add each interface, modular code is more cumbersome
  • incategoryIs still introduced inHard coding of strings, internal use dictionary parameters, and URL routing to some extent also have the same problem
  • There is no guarantee that the module being used exists, and after target is modified, the user can only detect the error at run time
  • Too many Target classes may be created

CTMediator source code analysis

  • Called through the classification of the CTMediator simple example aboveperformTargetCame toCTMediatorThe concrete implementation of, namelyperformTarget:action:params:shouldCacheTarget:, mainly through the passed name, find the correspondingtargetaction
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget { if (targetName == nil || actionName == nil) { return nil; } // When used in swift, you need to pass in the target name of the corresponding project, Otherwise you will find the view controller nsstrings * swiftModuleName = params [kCTMediatorParamsKeySwiftTargetModuleName]; // generate target NSString *targetClassString = nil; If (swiftModuleName. Length > 0) {// Swift stringWithFormat:@"% @.target_ %@", swiftModuleName, targetName]; } else {//OC in target filename join targetClassString = [NSString stringWithFormat:@"Target_%@", targetName]; } / / the cache lookup target NSObject * target = [self safeFetchCachedTarget: targetClassString]; Target if (target == nil) {Class targetClass = NSClassFromString(targetClassString); Target = [[targetClass alloc] init]; NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName]; Sel action = NSSelectorFromString(actionString); If (target == nil) {// If (target == nil) {// If (target == nil) {// If (target == nil) {// If (target == nil) { In practice, you can give a fixed target in advance to be used at this time, Then process this request of [self NoTargetActionResponseWithTargetString: targetClassString selectorString: actionString originParams:params]; return nil; } / / whether to cache the if (shouldCacheTarget) {[self safeSetCachedTarget: target key: targetClassString]; } / / whether the response sel the if ([target respondsToSelector: action]) {/ / dynamic invocation methods return [self safePerformAction: the action target: the target params:params]; } else {// This is where the unresponsive request is handled. If there is no response, try calling the notFound method corresponding to target to handle SEL action = NSSelectorFromString(@"notFound:"); if ([target respondsToSelector:action]) { return [self safePerformAction:action target:target params:params]; } else {// This is also where unresponsive requests are handled. If notFound is not present, this demo will return directly. In practice, you can use the fixed target above mentioned. [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params]; @synchronized (self) { [self.cachedTarget removeObjectForKey:targetClassString]; } return nil; }}}Copy the code
  • Enter thesafePerformAction:target:params:Realization, mainly throughinvocationforParameter passing + message forwarding
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params {// get method signature NSMethodSignature* methodSig = [target methodSignatureForSelector:action]; if(methodSig == nil) { return nil; Const char* retType = [methodSig methodReturnType]; //void type if (STRCMP (retType, @encode(void)) == 0) {... } / /... Omit other types of judgment}Copy the code

More information about CASatwy’s CTMediator can be read in this article

Protocol – Class

  • Add Protocol Wrapper layer (middleware first registers Protocol and Class mappings, willprotocolAnd the correspondingclassforDictionary matching)
  • The middleware returns the Class corresponding to Protocol, andDynamically creating an instance
  • Solve the hard coding problem

A simple example of protocol-class

A simple example

// Specify the Protocol
//MTMediator.h --- start@protocol MTDetailViewControllerProtocol <NSObject> + (__kindof UIViewController *)detailViewControllerWithUrl:(NSString  *)detailUrl; @end @interface MTMediator : NSObject + (void)registerProtol:(Protocol *)protocol class: (Class)cls;
+ (Class)classForProtocol:(Protocol *)protocol;
@end
//MTMediator.h --- end

//MTMediator.m --- start
+ (void)registerProtol:(Protocol *)protocol class: (Class)cls{
    if (protocol && cls) {
        [[[self class] mediatorCache] setObject:cls forKey:NSStringFromProtocol(protocol)];
    }
}

+ (Class)classForProtocol:(Protocol *)protocol{
    return [[[self class] mediatorCache] objectForKey:NSStringFromProtocol(protocol)];
}
//MTMediator.m --- end

/ / is invoked
//MTDetailViewController.h --- start
@protocol MTDetailViewControllerProtocol;

@interface MTDetailViewController : UIViewController<MTDetailViewControllerProtocol>
@end
//MTDetailViewController.h --- end

//MTDetailViewController.m --- start
+ (void)load {
    [MTMediator registerProtol: @protocol(MTDetailViewControllerProtocol) class: [self class]];
}

#pragma mark - MTDetailViewControllerProtocol
+ ( __kindof UIViewController *)detailViewControllerWithUrl:(NSString *)detailUrl{
    return [[MTDetailViewController alloc]initWithUrlString:detailUrl];
}
//MTDetailViewController.m --- end

/ / call
Class cls = [MTMediator classForProtocol: @protocol(MTDetailViewControllerProtocol)];
if ([cls respondsToSelector: @selector(detailViewControllerWithUrl:)]) {
        [self.navigationController pushViewController:[cls detailViewControllerWithUrl:item.articleUrl] animated:YES];
}

Copy the code

Description:

  • The called first registers the mapping between Protocol and Class in the middleware, and only the Protocol is exposed externally

BeeHive

A typical tripartite framework for Protocol is Alibaba’s BeeHive. BeeHive borrowed from the Spring Service, Apache DSO architecture concept, using AOP+ extension App life cycle API form, the business functions, basic functional modules in modular way to solve complex problems in large applications, and between modules in the form of Service call, complex problems will be segmtioned, Modularize services in an AOP manner.

BeeHive core ideas

  • 1. Calls between modules have changed from direct calls to corresponding modules to callsServiceAvoid direct dependence.
  • 2. The distribution of App life cycle will be coupled inAppDelegateEach module exists independently in the form of microapplications.

The following is an example:

******** 1. Register [[BeeHive shareInstance] registerService:@protocol(HomeServiceProtocol) service:[BHViewController class]]; #import "bhService. h" id< HomeServiceProtocol > homeVc = [[BeeHive shareInstance] createService:@protocol(HomeServiceProtocol)];Copy the code

advantages

  • 1. Use the interface call to realize the type safety of parameter passing
  • 2. Directly use the protocol interface of the module without repeated encapsulation

disadvantages

  • 1. Create all objects with a framework in a different way, i.e. no external parameters are supported
  • 2, useOC runtimeCreate objects that do not support Swift
  • 3, just doprotocolclassDoes not support more complex creation methods and dependency injection
  • 4. It is not guaranteed that the protocol used must have a corresponding module, nor can it be directly determined whether a protocol can be used to obtain modules

In addition to BeeHive, there is Swinject

Register the BeeHive module

BeeHive manages each module mainly through the BHModuleManager. Only registered modules are managed in the BHModuleManager.

BeeHive provides three different forms of registration: annotation, static plist, and dynamic registration. Module and Service are not associated. Each Service Module can independently implement functions of Module or Service.

Annotation registration

This method mainly uses BeeHiveMod macro to carry out Annotation marking

//***** use BeeHiveMod(ShopModule) //***** BeeHiveMod macrodefinition #define BeeHiveMod(name) \ class BeeHive; char * k##name##_mod BeeHiveDATA(BeehiveMods) = ""#name""; #define BeeHiveDATA(sectName) __attribute((used, Section ("__DATA,"# sectName "")) //***** char * kShopModule_mod __attribute((used, section("__DATA,""BeehiveMods"" "))) = """ShopModule""";Copy the code

Here are a few things to say about __attribute

  • The first parameterusedUsed: modifies functions. Used means that functions are not optimized under Release even if they are not referenced. Without this modifier, unreferenced segments are removed from the Release environment linker.
  • Through the use of__attribute__((section("name")))To specify which paragraph. Data is used__attribute__((used))To prevent the linker from optimally removing unused segments and then injecting modules into__DATAIn the

Now that the Module has been stored in a special section of the Mach-O file, how do I get it?

  • In the BHReadConfiguration method, Mach-O is used to find the stored data segment and put it into an array

    NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp) { NSMutableArray *configs = [NSMutableArray array]; unsigned long size = 0; Uintptr_t *memory = #ifndef __LP64__ (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; }Copy the code
  • The registered dyLD_callback callback is as follows

    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]; }}}//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)];
                    }
    
                }
            }
        }
    
    }
    __attribute__((constructor))
    void initProphet() {
        The // _dyLD_register_func_for_add_image function is used to register the callback function when dyLD loads the image. The registered callback function is executed when DYLD loads the image
        _dyld_register_func_for_add_image(dyld_callback);
    }
    Copy the code
Read the local Pilst file
  • First, you need to set up the path

    [BHContext shareInstance].moduleConfigName = @"BeeHive.bundle/BeeHive"; / / optional, default is extracted. The bundle/BeeHive plistCopy the code
  • Create a plist file,PlistThe file format is also an array containing multiple dictionaries. There are two keys in the dictionary, one is@"moduleLevel"And the other one is@"moduleClass". Pay attention toThe rootThe name of the array is@ "moduleClasses".

  • Go to the loadLocalModules method and basically take the array from the Plist and add it to the BHModuleInfos array.

    // to initialize the context, load Modules and Services -(void)setContext:(BHContext *)context {_context = context; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ [self loadStaticServices]; [self loadStaticModules]; }); } 👇 // load modules - (void)loadStaticModules { And register with the BHModuleManager BHModuleInfos array [[BHModuleManager sharedManager] loadLocalModules]; // Register all modules and sort them internally by priority [[BHModuleManager sharedManager] registedAllModules]; } 👇 - (void)loadLocalModules {//plist file path NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"]; // Check if file exists if (! [[NSFileManager defaultManager] fileExistsAtPath:plistPath]) { return; } / / read the entire file] [@ "moduleClasses" : array NSDictionary * moduleList = [[NSDictionary alloc] initWithContentsOfFile: plistPath]; ModuleClasses key [[@"moduleClass":"aaa", @"moduleLevel": @"bbb"], [...]] NSArray<NSDictionary *> *modulesArray = [moduleList objectForKey:kModuleArrayKey]; NSMutableDictionary<NSString *, NSNumber *> *moduleInfoByClass = @{}.mutableCopy; / / to iterate through group [self BHModuleInfos enumerateObjectsUsingBlock: ^ (NSDictionary * _Nonnull obj, NSUInteger independence 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]]) {/ / stored in BHModuleInfos [self. BHModuleInfos addObject: obj]; }}]; }Copy the code
Load method registration

This method registers the Module class in the Load method

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

    + (void)registerDynamicModule:(Class)moduleClass { [[BHModuleManager sharedManager] registerDynamicModule:moduleClass]; } 👇 - (void) registerDynamicModule: (Class) moduleClass {[self registerDynamicModule: moduleClass shouldTriggerInitEvent:NO]; } 👇 - (void)registerDynamicModule:(Class)moduleClass shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent {[self addModuleFromObject:moduleClass shouldTriggerInitEvent:shouldTriggerInitEvent]; }Copy the code
  • And the way of the Annotation registered dyld_callback callback, will eventually go addModuleFromObject: shouldTriggerInitEvent: method

      - (void)addModuleFromObject:(id)object
           shouldTriggerInitEvent:(BOOL)shouldTriggerInitEvent
      {
          Class class;
          NSString *moduleName = nil;
    
          if (object) {
              class = object;
              moduleName = NSStringFromClass(class);
          } else {
              return ;
          }
    
          __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];
    
              BOOL responseBasicLevel = [class instancesRespondToSelector: @selector(basicModuleLevel)];
    
              int levelInt = 1;
    
              if (responseBasicLevel) {
                  levelInt = 0;
              }
    
              [moduleInfo setObject:@(levelInt) forKey:kModuleInfoLevelKey];
              if (moduleName) {
                  [moduleInfo setObject:moduleName forKey:kModuleInfoNameKey];
              }
    
              [self.BHModuleInfos addObject:moduleInfo];
    
              id<BHModuleProtocol> moduleInstance = [[class alloc] init];
              [self.BHModules addObject:moduleInstance];
              [moduleInfo setObject:@(YES) forKey:kModuleInfoHasInstantiatedKey];
              [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];
                      }
                      returnmodule1Priority < module2Priority; }}]; [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]; }); }}}Copy the code

Load method, you can also use the BH_EXPORT_MODULE macro instead

#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.

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. As shown below, there are two special events: BHMInitEvent and BHMTearDownEvent

Typedef NS_ENUM(NSInteger, BHModuleEventType) {// Set the Module BHMSetupEvent = 0, // Used to initialize the Module, such as the environment, // Used to remove the Module BHMTearDownEvent, BHMSplashEvent, BHMQuickActionEvent, BHMWillResignActiveEvent, BHMDidEnterBackgroundEvent, BHMWillEnterForegroundEvent, BHMDidBecomeActiveEvent, BHMWillTerminateEvent, BHMUnmountEvent, BHMOpenURLEvent, BHMDidReceiveMemoryWarningEvent, BHMDidFailToRegisterForRemoteNotificationsEvent, BHMDidRegisterForRemoteNotificationsEvent, BHMDidReceiveRemoteNotificationEvent, BHMDidReceiveLocalNotificationEvent, BHMWillPresentNotificationEvent, BHMDidReceiveNotificationResponseEvent, BHMWillContinueUserActivityEvent, BHMContinueUserActivityEvent, BHMDidFailToContinueUserActivityEvent, BHMDidUpdateUserActivityEvent, BHMHandleWatchKitExtensionRequestEvent, BHMDidCustomEvent = 1000 };Copy the code

There are three main types

  • 1.System events: Mainly refers toApplication life cycle events! The general approach isAppDelegateInstead ofInherited from BHAppDelegate
    @interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>
    Copy the code

2,Application events: Official flow chart, whichmodSetup,modInitAnd can be used to code the setup and initialization of each plug-in module.

  • 3,Custom events

All of the above events can be handled by calling 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]; } 👇 #pragma mark-module protocol - (void)handleModuleEvent:(NSInteger)eventType ForTarget :(id<BHModuleProtocol>)target withCustomParam:(NSDictionary *)customParam {switch (eventType) {// initializes the event case BHMInitEvent: //special [self handleModulesInitEventForTarget:nil withCustomParam :customParam]; break; / / destructor event 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; }}Copy the code

As you can see from the above code, with the exception of the BHMInitEvent initialization event and the BHMTearDownEvent removing Module event, All events are called handleModuleEvent: forTarget: withSeletorStr: andCustomParam: method, its internal implementation mainly traversal moduleInstances instance array, Call performSelector: withObject: method corresponding method call

- (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)]; } // Iterate through the moduleInstances array, Call performSelector: withObject: method corresponding method call [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" // Perform method calls [moduleInstance performSelector: Seletor withObject:context];  #pragma clang diagnostic pop [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- %@", [moduleInstance class], NSStringFromSelector(seletor)]]; } }]; }Copy the code

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

BeeHive Protocol registration

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

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

Annotation registration
/ / * * * * * * 1, the Annotation by BeeHiveService macro marks BeeHiveService (HomeServiceProtocol BHViewController) / / * * * * * * 2, and # define a macro definition BeeHiveService(servicename,impl) \ class BeeHive; char * k##servicename##_service BeeHiveDATA(BeehiveServices) = "{ ""#servicename"" : ""#impl""}"; //****** Char * kHomeServiceProtocol_service __attribute((used, section("__DATA,""BeehiveServices"" "))) = "{ """HomeServiceProtocol""" : """BHViewController"""}";Copy the code
Read the local PList file
  • First, as with Module, you need to set the path first

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

  • Also register services in setContext

    // Load services -(void)loadStaticServices {[BHServiceManager sharedManager]. EnableException = self.enableException; [[BHServiceManager sharedManager] registerLocalServices]; } 👇 - (void)registerLocalServices {NSString *serviceConfigName = [BHContext shareInstance].Serviceconfigname; / / the file path nsstrings * 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]; }Copy the code
Load method registration

To register the Protocol in the Load method, register the Protocol by calling registerService:service: in BeeHive

+ (void)load { [[BeeHive shareInstance] registerService:@protocol(UserTrackServiceProtocol) service:[BHUserTrackViewController class]]; } 👇 - (void)registerService:(Protocol *)proto service:(Class) serviceClass {[[BHServiceManager sharedManager] registerService:proto implClass:serviceClass]; }Copy the code

At this point, the three ways to register are complete

The Protocol for

Protocol differs from Module in that Protocol has one more method than Module that returns a Protocol instance object

- (id)createService:(Protocol *)proto; { return [[BHServiceManager sharedManager] createService:proto]; } 👇 - (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; // Check whether protocol is already registered 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 there is a cache, Directly from the cache for the 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)]) // Create a singleton. Else implInstance = [[implClass alloc] init]; If (shouldCache) {/ / cache the [[BHContext shareInstance] addServiceWithImplInstance: implInstance serviceName: serviceStr]; return implInstance; } else { return implInstance; } } } return [[implClass alloc] init]; }Copy the code

CreateService first checks if Protocol is registered. It then takes the corresponding Class from the dictionary and creates a singleton if the shareInstance method is implemented, or an instance if it is not. 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

  • Protocol and the class are dictionary bound, protocol as key and serviceImp (the name of the class) as value

    - (Class)serviceImplClass:(Protocol *)service
    {
        //通过字典将 协议 和 类 绑定,其中协议作为key,serviceImp(类的名字)作为value
        NSString *serviceImpl = [[self servicesDict] objectForKey:NSStringFromProtocol(service)];
        if (serviceImpl.length > 0) {
            return NSClassFromString(serviceImpl);
        }
        return nil;
    }
    Copy the code

Module & Protocol

Here is a brief summary:

  • For Module: array storage

  • For Protocol: Bind Protocol to the class using a dictionary. Key is Protocol and value is serviceImp, which is the name of the class

BeeHive auxiliary class

  • The BHContext class is a singleton with two NSMutableDictionary properties, modulesByName and servicesByName. This class is mainly used to store context information. For example in the application: didFinishLaunchingWithOptions: when you can initialize a lot of context information

    // Save 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
  • BHConfig class: a singleton with an NSMutableDictionary config property that maintains dynamic environment variables as a complement to BHContext

  • BHTimeProfiler class: A Profiler for computing time performance

  • The BHWatchDog class opens a thread and listens for the main thread to block

Refer to the link

  • BeeHive – an elegant but still improving decoupling framework

  • BeeHive, an iOS module decouple practice

— end —