The purpose of this article is to document usageCTMediatorsFramework for componentized exploration and practice process

1. Basic layering of componentization

  • Generally, the idea of componentization can be roughly divided into the following three layers:
    • Basic module
    • General module
    • Business module
  • The development and integration sequence for modules is roughly bottom-up, that is, fromBasic moduleGeneral moduleBusiness moduleBut the order of dependence is from top to bottom,businessRely ongeneral.generalRely onbasis
Based module integration sequence ` ` - > ` module ` - > ` business module ` ` rely on order basis module ` ` < < -- ` general module - ` ` business moduleCopy the code
  • Basic moduleGeneral moduleDepending on your opinion, it can be viewed as the same level or two levels
    • Basic moduleIt mainly encapsulates some modules that are not related to business, such as some classification and tool classes. Here, it is clear that it is not linked to business. Simply speaking, this layer can be used in other projects without modification, which is the most ideal
    • General moduleThis is actually calledGeneric business module, this layer mainly embodiesgeneralSecondly reflectbusiness-oriented. For example, some common components, such as general UIButton, waterfall flow, classification related to business hooks, time calculation NSDate. This layer must be the embodiment of the general business hook, if not entirely dependent on business logic, inBasic module

2. Service module

In fact, apart from the technical aspects of componentization, the design of business modules is the most difficult. We must focus on the whole project and how to divide the project, not only considering the rationality of the current stratification, but also considering the expansion of the future. Because if a module is frequently changed, it is necessary to reasonably design the front-end interface, interaction with other modules and so on. Since this article mainly focuses on THE CTM framework, this layer completely depends on the business of different apps, so specific expansion will not be done here

  1. Generally, the relationship between project modules is shown in the following figure, which is the project before componentization. The relationship between the modules written here can be understood as the relationship between classes

  • Modules are more or less related, and communication between modules (that is, method calls between classes) needs to take place#importImport header is a strong coupling
  • The first problem to be solved in componentization between modules is the coupling relationship between modules. As long as the coupling relationship is lower, modules can be more independent
  • The basic idea of ending is to establishCommunication intermediate layer, each module through the middle layer of technical secondary school, modules do not directly contact. The following figure

  • But this design of the middle layer introduces a new problem,
    • Each module in the middle layer of the strong coupling, he needs to import all modules, so that modules can communicate.
    • The resulting middle layer is relatively large. For example, module A only wants to communicate with module C, at this time, the middle layer only needs to be designed to contain AC, but now the middle layer has all the codes of ABCD, which is unreasonable
    • One idea is to talk about the stratification of the middle layer, so there may be a permutation and combination relationship, AB, AC, AD, BC, BD, CD, can we optimize the middle layer, here we first foreshadowed

3. Introduction of CTMediator Communication intermediate framework

  • Frame address: github.com/casatwy/CTM…
  • Code structure diagram:

  • The premise of using the framework: reasonable business modules have been divided for the project, and the business of the project is simply stratified, without considering the communication between modules

  • The basic idea of the framework is

    • Isolate each module into a separate entityThe Target layer(This has nothing to do with OC target-action mode), this layer is a separate class that can be understood as a declaration of modules, analogous to the relationship between.h and.m in OC files,The Target layerIs H and module is M
    • The Target layerAs the entrance of the module communication, commonly speaking is the declaration file of the module, the method call entrance of the module is encapsulated once, that is to say, the module can provide no external functions. For example, the login module, which can provide login functions, registration, forget password and so on, then we expose these methods toThe Target layerInside, external callsThe Target layerMethod, which is then called indirectly inside the module
    • The Target layerFor example, if the external parameters are not passed in accordance with reasonable rules, we can intercept the judgment first. Here, we can also put the fault-tolerant logic in the module intoThe Target layer
  • Eliminate coupling altogether — Runtime

    • Convert method calls to methods that call modules without importing them, avoiding inter-module imports
    • In the name of the class (string), method name (stringThe method invocation is encapsulated using method signature + NSInvocationperformSelectorThe runtime parses the string into the corresponding class or method, so that if we want to call method B of class A, we don’t need to import class A, but use @ “A” and @ “B” to convert the string into specific classes and methods through Runtime (we will analyze the source code later).
    • Because you need to convert a string to a class or method, there are inevitable hand errors, so parameters such as target(class), action(class), and so on for fault tolerance processing, the framework is processed internally

4. Increase classification — solve the coupling between the middle layer and modules and reduce the arrangement and combination of the middle layer

4.1 Overview

Design classification generally serves two purposes

  • To prevent accidents of CTM framework, although the code of CTMediator is generally small and the logic is not very complex, in order to avoid excessive changes in the code of the framework, CTM is not directly modified to call, and it is expanded by creating its classification. This idea is also reflected when we use other three-party frameworks. For example, in the use of AFN and other network frameworks, we will not directly call the code within the framework, but an independent class to manage and encapsulate AFN, through this class to interact with the outside

  • Avoid strong coupling of projects or modules to the CTMediator framework

    • We have a separate class for each module, and the methods in the classification andThe Target layerDeclared methods for one-to-one correspondence.
    • Also take the login module for example, we put the login, registration, forget the password and other functions inThe Target layerThe declaration and implementation are already written in the class, and we then copy these declarations into the classification as appropriate.
    • To the outside world, this category is the declaration file of this module. If you want to use login, registration, etc., just import this category, which is the same as the effect of using the classification in our general sense.
    • So a complete module is containedclassification –> The Target layer —> Module source layer[See picture above]

4.2 CTMediator code and Demo introduction

Let’s take a look at the github demo

1. Overall introduction of the project
  • Project code structure

  • And when the code runs, it’s a tableView

  • We take thepresent image This operation
2. This function will beDemoModuleADetailViewControllerModal comes out and needs to pass a UIImage into this controller,DemoModuleADetailViewControllerHereinafter referred to asModule A
  • Module AThe declaration exposes an imageView, which is used to pass values
@interface DemoModuleADetailViewController : UIViewController
@property (nonatomic, strong, readonly) UILabel *valueLabel;
@property (nonatomic, strong, readonly) UIImageView *imageView;
@end
Copy the code
  • Module AThe target layerTarget_AClass corresponding methods and their implementation as follows, mainly createModule AController, parse the parameter solution passed in, and assign the controller, and implement Modal
- (id)Action_nativePresentImage:(NSDictionary *)params
{
    DemoModuleADetailViewController *viewController = [[DemoModuleADetailViewController alloc] init];
    viewController.valueLabel.text = @"this is image";
    viewController.imageView.image = params[@"image"];
    [[UIApplication sharedApplication].keyWindow.rootViewController presentViewController:viewController animated:YES completion:nil];
    return nil;
}
Copy the code
  • The method nameAction_nativePresentImageThe basis is concatenated according to the current CTM rules, and the rule of method name isAction_Spell the upper Dharma name,nativePresentImageIs the name of the method we can define, which is declared concatenated at the classification level, as defined in the demostatic stringMy understanding of naming and how to define strings, as long as the project internal convention is good, everyone according to this rule.

3. The second step solves the problem of method names, and then we take a look at method calls as a whole, step by step from cell clicks
  • Cell Click to invoke CTM classification

  • The realization of classification method

  • Call to CTM internal method, class and method name conversion, create objects, internal should do the corresponding annotation, the next step onperformSelector: Further encapsulation
- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params ShouldCacheTarget shouldCacheTarget: (BOOL) {/ / tagrt sentenced to empty the if (targetName = = nil | | actionName = = nil) {return nil. } / / special markers of swift nsstrings * swiftModuleName = params [kCTMediatorParamsKeySwiftTargetModuleName]; // concatenate target-action tagert, which is the class name of the method called. The class name is Target_ method name NSString *targetClassString = nil; if (swiftModuleName.length > 0) { targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName]; } else {// Class name rule is Target_ method name targetClassString = [NSString stringWithFormat:@"Target_%@", targetName]; } NSObject *target = self.cachedTarget[targetClassString]; // Class object if (target == nil) {Class targetClass = NSClassFromString(targetClassString); target = [[targetClass alloc] init]; Action_ method name: NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName]; SEL action = NSSelectorFromString(actionString); If (target == nil) {return if (target == nil) {return if (target == nil) {return 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; If (shouldCacheTarget) {self.cachedTarget[targetClassString] = target; } the if ([target respondsToSelector: action]) {/ / the underlying method call 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]; [self.cachedTarget removeObjectForKey:targetClassString]; return nil; }}}Copy the code
  • This part is mainly fault tolerance of parameters, yesperformSelectorFurther encapsulate, and return the object of the method caller
- (id)safePerformAction:(SEL)action target:(NSObject *)target params:(NSDictionary *)params
{
    NSMethodSignature* methodSig = [target methodSignatureForSelector:action];
    if(methodSig == nil) {
        return nil;
    }
    const char* retType = [methodSig methodReturnType];

    if (strcmp(retType, @encode(void)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        return nil;
    }

    if (strcmp(retType, @encode(NSInteger)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

    if (strcmp(retType, @encode(BOOL)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        BOOL result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

    if (strcmp(retType, @encode(CGFloat)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        CGFloat result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

    if (strcmp(retType, @encode(NSUInteger)) == 0) {
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSig];
        [invocation setArgument:&params atIndex:2];
        [invocation setSelector:action];
        [invocation setTarget:target];
        [invocation invoke];
        NSUInteger result = 0;
        [invocation getReturnValue:&result];
        return @(result);
    }

#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
    return [target performSelector:action withObject:params];
#pragma clang diagnostic pop
}
Copy the code

At this point, a complete CTM call is completed, and the general flow is as follows