IOS underlying principles article summary

This article mainly talks about how to communicate between componentization

Componentized communication scheme

At present, there are three main ways in the mainstream:

  • 1. URL routing

  • 2, target – the action

  • 3. Protocol match

URL routing

Most routing tools on iOS today are based on URL matching or are called dynamically using the Runtime method based on naming conventions

These dynamic schemes have the advantage of being simple to implement, but the disadvantage is that they need to maintain a string table or rely on naming conventions that don’t expose all problems at compile time and require errors to be discovered at run time.

The URL routing mode is mainly MGJRouter represented by Mogujie

The realization idea is as follows:

  • When the App starts, each component module is instantiated, and then these components register the Url with the ModuleManager. In some cases, you do not need to instantiate and use class registration

  • When component A needs to call component B, the URL is passed to the ModuleManager, and the parameter is passed along with the URL in GET mode, similar to openURL. 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: \ (info) ")} / / 2, call routing MGJRouter. OpenURL (" app: / / home ")Copy the code

Advantages of URL routing

  • Extremely dynamic, suitable for frequent operation of the app, such as e-commerce

  • Conveniently manage routing rules of multiple platforms

  • Easy to adapt URL Scheme

Disadvantages of URl routing

  • The parameters are passed in a limited way, and there is no way to use the compiler for parameter type checking, so all parameters are converted from strings

  • Applies only to interface modules, not to generic modules

  • The format of the arguments is not clear, it is a flexible dictionary, and you also need a place to look up the format of the arguments.

  • Do not support the storyboard

  • Relying on string hard coding, difficult to manage, mogujie do a special management background.

  • There is no guarantee that the module being used exists

  • The decoupling capability is limited, and the “registration”, “implementation” and “use” of URLS must use the same character rules. Any modification by either party will invalidate the other party’s code, and it is difficult to refactor

In addition to CTMediator, there are the following tripartite frameworks

  • routable-ios
  • JLRoutes
  • HHRouter

target-action

This is based on the OC’s runtime and category features to dynamically retrieve the module. For example, retrieve the class from NSClassFromString and create the instance, and then dynamically invoke the method using performSelector + NSInvocation

Its main representative framework is Casatwy’s CTMediator

The realization idea is as follows:

  • 1. Add a new interface to the route using the classification, and obtain the corresponding class from the interface through a string

  • 2, create an instance through Runtime and dynamically call the methods of the instance

Extension CTMediator{@objc func A_showHome()->UIViewController? { let params = [ kCTMediatorParamsKeySwiftTargetModuleName: "CJLBase_Example" ] 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 following figure shows the reference relationship between modules

advantages

  • Classification can be used to explicitly declare the interface, compile check

  • Lightweight implementation

disadvantages

  • Each interface needs to be readded to Mediator and Target, which can be cumbersome when modularized

  • In the category, string hardcoding is still introduced, and dictionaries are used internally to pass parameters, which has some of the same problems as URL routing

  • There is no guarantee that the module in use will always exist, and after the target is modified, the consumer can only find the error at runtime

  • It is possible to create too many Target classes

CTMediator source analysis

  • By calling in the classificationperformTargetCame toCTMediator, namelyperformTarget:action:params:shouldCacheTarget:, mainly by passing in the name, to find the correspondingtargetaction
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget { if (targetName == nil || actionName == nil) { return nil; } // 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]; NSString *targetClassString = nil; If (swiftModulene.length > 0) {targetClassString = [NSString stringWithFormat:@"% @.target_ %@", swiftModuleName, targetName]; } else {//OC target filename concatenation targetClassString = [NSString stringWithFormat:@"Target_%@", targetName]; } / / the cache lookup target NSObject * target = [self safeFetchCachedTarget: targetClassString]; If (target == nil) {Class targetClass = NSClassFromString(targetClassString); Target = [[targetClass alloc] init]; } NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName]; Sel action = NSSelectorFromString(actionString); If (target == nil) {return (target == nil) {return (target == nil) {return (target == nil) {return (target == nil) {return (target == nil) {return (target == nil) {return (target == nil) {return (target == nil); The actual development process can give a fixed target 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 no response is handled. If there is no response, try calling the notFound method of the corresponding target to process SEL action = NSSelectorFromString(@"notFound:"); if ([target respondsToSelector:action]) { return [self safePerformAction:action target:target params:params]; } else {// This is where we handle unfound requests. When there are no notFound, this demo returns directly. In the actual development process, you can use the fixed target on top of the previously 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 the method signature NSMethodSignature* methodSig = [target methodSignatureForSelector:action]; if(methodSig == nil) { return nil; } // Const char* retType = [methodSig methodReturnType]; // Const char* retType = [methodSig methodReturnType]; // encode(STRCMP (retType, @encode(void)) == 0) { } / /... Omit other types of judgments}Copy the code

protocol class

Protocol matching is implemented as follows:

  • 1. Perform a dictionary match between the protocol and the corresponding class

  • 2. Obtain a class using protocol and dynamically create an instance

Protocol A typical tripartite framework is Alibaba’s BeeHive. BeeHive uses the architectural concept of Spring Service and Apache DSO for reference, adopts THE form of AOP+ extended App life cycle API, solves complex problems in large applications with business functions and basic function modules in the form of modules, and lets modules call in the form of Service, and divides complex problems. Modularize services with AOP.

Core idea of BeeHive

  • 1. The call between each module changes from directly calling the corresponding module to calling Service, avoiding direct dependence.

  • 2. The distribution of App life cycle is coupled to the App delegate and logically split. Each module exists independently as a micro application.

The following is an example (intended to write in swift, but there is a problem, temporarily write in OC) :

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

advantages

  • 1. Using the interface call, the type safety of parameter passing is realized

  • 2. Use the protocol interface of the module directly, without repackaging

disadvantages

  • 1, use the framework to create all objects, create a different way, that is, do not support external incoming parameters

  • Create object with OC Runtime, do not support SWIFT

  • 3. Only protocol and class are matched. More complex creation methods and dependency injection are not supported

  • 4. It is not guaranteed that the protocol used must have a corresponding module, nor can it be directly determined whether a certain protocol can be used to obtain a module

In addition to BeeHive, there’s Swinject

BeeHive module registration

In BeeHive, the BHModuleManager is used to manage modules. The BHModuleManager manages only registered modules.

BeeHive provides three different forms of call, static PList, dynamic registration, and annotation. There is no association between Module and Service. Each Service Module can implement the functions of Module or Service separately.

1. Annotation registration This method is mainly used to mark the Annotation through BeeHiveMod macro

//***** use BeeHiveMod(ShopModule) //***** BeeHiveMod macro definition #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

The following points need to be made about __attribute

  • The first argument, used, is used to decorate the function. When used, it means that even if the function is not referenced, it will not be optimized under Release. If you do not use this modifier, the Release environment linker will remove unreferenced segments.

  • Specifies which segment by using __attribute__((section(“name”))). The data is marked with __attribute__((used)), preventing the linker from optimizing to remove unused segments and inject modules into __DATA

At this point the Module is stored in a special section of the Mach-o file, so how do you fetch it?

  • Enter theBHReadConfigurationMethod, mainly byMach-OFind the stored data segment and put it into the array
NSArray<NSString *>* BHReadConfiguration(char *sectionName,const struct mach_header *mhp) { NSMutableArray *configs = [NSMutableArray array]; unsigned long size = 0; # uintptr_t *memory = 1 uintptr_t *memory = 1 uintptr_t *memory = 1 uintptr_t *memory = 1 (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

2. Read the local Pilst file

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

  • Enter theloadLocalModulesMethod, mainly fromPlistI’m going to take the array, and I’m going to add the array toBHModuleInfosArray.
// initialize 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 {// read modules from the local plist file, Register it with [[BHModuleManager sharedManager] loadLocalModules] in the BHModuleInfos array of the BHModuleManager. // Register all modules, internally ranking them by priority [[BHModuleManager sharedManager] registedAllModules]; } 👇 - (void)loadLocalModules {//plist file path NSString *plistPath = [[NSBundle mainBundle] pathForResource:[BHContext shareInstance].moduleConfigName ofType:@"plist"]; // Check whether the 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

3, load method registration

This method registers the Module class in the Load method

+ (void)load
{
    [BeeHive registerDynamicModule:[self class]];
}
Copy the code
  • Enter theregisterDynamicModuleimplementation
+ (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

Its underlying or the same as the first way, will eventually go addModuleFromObject: shouldTriggerInitEvent: method

  • Load method, you can also useBH_EXPORT_MODULEMacro instead of
#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 be passed a parameter indicating whether the Module is loaded asynchronously. If YES, it is loaded asynchronously; if NO, it is loaded synchronously.

2. BeeHive module event

BeeHive provides life cycle events for each module to interact with the BeeHive host environment and to be aware of changes in the module life cycle.

BeeHive Each module receives some 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 Module BHMSetupEvent = 0, // Used to initialize the Module, for example, environment, // 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 lifecycle events!

The general way to do this is to change the AppDelegate to inherit from BHAppDelegate

@interface TestAppDelegate : BHAppDelegate <UIApplicationDelegate>
Copy the code
  • 2,Application events: The official flow chart, in whichmodSetup,modInitCan be used to code the plug-in module setup and initialization.

  • 3,Custom event

All of these 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) {// initialize 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, except for the BHMInitEvent initialization event and the BHMTearDownEvent Module removal 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)]; } // Iterates over 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-warc -performSelector -Leaks "// Method call [moduleInstance performSelector:seletor withObject:context]; #pragma clang diagnostic pop [[BHTimeProfiler sharedTimeProfiler] recordEventTime:[NSString stringWithFormat:@"%@ --- %@", [moduleInstance class], NSStringFromSelector(seletor)]]; } }]; }Copy the code

Note: All modules must follow the BHModuleProtocol, otherwise these events cannot be received.

3. BeeHive module call

In BeeHive, each Protocol is managed through the BHServiceManager. The BHServiceManager manages only the protocols that have been registered.

There are three ways to register a Protocol, which are the same as registering a Module

1. Register with Annotation

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

2. Read the local PLIST file

  • First, as with modules, you need to set the path
[BHContext shareInstance].serviceConfigName = @"BeeHive.bundle/BHService";
Copy the code
  • Set the plist file

  • It was also in thesetContextWhen the registeredservices
// Load services -(void)loadStaticServices {[BHServiceManager sharedManager]. EnableException = self.enableException; // Load services -(void)loadStaticServices {[BHServiceManager sharedManager]. [[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

3, load method registration

To register a Protocol in the Load method, call BeeHive registerService:service: to register the Protocol

+ (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 are created

The Protocol for

The difference between Protocol and Module is that Protocol has one more method than Module and can return 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 the protocol is 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)]) implInstance = [[implClass Class] shareInstance]; Else // Create an instance object 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

The createService first checks whether the Protocol is registered. Then pull out the corresponding Class from the dictionary and create a singleton object if shareInstance is implemented, or an instance object if not. If you also implement singleton, you can further cache implInstance and serviceStr in the servicesByName dictionary of the BHContext. This can be passed along with the context

  • Enter theserviceImplClassImplementation, from which you can see that protocol and class are passedThe dictionaryThe binding,protocolAs akey.serviceImp(name of class) asvalue
- (Class)serviceImplClass:(Protocol *)service {// bind the Protocol to the Class using the dictionary. ServiceImp (the name of the class) as the value nsstrings * serviceImpl = [[self servicesDict] objectForKey: NSStringFromProtocol (service)]; if (serviceImpl.length > 0) { return NSClassFromString(serviceImpl); } return nil; }Copy the code

Module & Protocol

Here’s a quick summary:

  • For Module: Array storage

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

Auxiliary class

  • The BHConfig class: is a singleton with an internal config property of type NSMutableDictionary that maintains dynamic environment variables as a complement to the BHContext

  • The BHContext class: is a singleton containing two NSMutableDictionary properties, modulesByName and servicesByName. This class is primarily used to hold context information. For example in the 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
  • BHTimeProfiler class: Profiler for computing time performance aspects

  • BHWatchDog class: used to open a thread and listen for congestion on the main thread

Refer to the link

  • BeeHive – An elegant but evolving decoupling framework
  • BeeHive, an iOS module decoupling practice