This is the 26th day of my participation in the August More Text Challenge

IOS componentized use

I. Implementation scheme

Currently, componentization schemes can be divided into two types: URL/ Protocol registration scheduling and Runtime scheduling

The registered scheduling scheme has the following two problems:

  • Named domain penetration
  • Registration is not necessary because we need to maintain the registration list, which is part of the cost

Therefore, this paper chooses the runtime scheduling scheme to achieve componentization; That is to use CTMediator Target_Action componentization scheme to achieve; CTMediator scheme extended support to Swift component on the basis of supporting Objective-C component call, which could achieve the purpose of mixed call. The development of components could use Objective-C or Swift language.

The appearance of CTMediator scheme is to schedule target-action through runtime, but the essence of CTMediator scheme is to complete the scheduling without the need to move the business code.

Therefore, when providing target-action, we usually choose to let the Action do the corresponding work

Note for Swift project statement target-action:

  • TargetObjects must be inherited fromNSObject
  • ActionMethods must have@objcThe prefix
  • ActionMethod cannot have the first argumentArgument Label

Second, the introduction of middleware

pod ‘CTMediator’, ‘~> 25’

Iii. Use (Take the Login module as an example)

The Mediator folder stores the middleware Target_Login file opened by Login module, which is the concrete implementation of Target_Action scheme. CTMediator+Login file is the open interface of Target_Action scheme, and is also the method that external modules can use when calling Login module.

The suffix Login of the two classes need not be the same, but the mapping relationship needs to be specified in the internal definition. Keep the suffix the same for easy understanding.

Fourth, code analysis

1, Target_Login

The concrete implementation of CTMediator+Login method in the interface class exposed by Login module

class Target_Login: NSObject {
    
    /// Get the login view controller
    /// -parameter sender: Parameter
    /// - Returns the controller
    @objc func Action_getLoginVC(_ sender: NSDictionary) -> UIViewController {
        let login = LoginViewController(a)return login
    }
}
Copy the code

The Swift project declares precautions for target-action

  • TargetObjects must be inherited fromNSObject;
  • ActionMethods must have@objcThe prefix.
  • The responderactionDo not take the first parameter of the methodArgument Labelwith_;
  • The responderTarget-ActionYou need toblockConverted intoclosure;

2, CTMediator + Login

This type refers to the callable interface developed externally and is also the category of middleware CTMediator. Its internal implementation is as follows:

import CTMediator

let kCTMediatorTarget_Login = "Login";// corresponds to Login in Target_Login

let kCTMediatorAction_getLoginVC = "getLoginVC";// corresponds to the second half of the method name Action_getLoginVC in Target_Login

extension CTMediator {
    
    /// Get the login view controller
    /// - Returns the controller
    func mediator_getLoginVC(a) -> UIViewController {
        let loginVC: UIViewController = performTarget(kCTMediatorTarget_Login, action: kCTMediatorAction_getLoginVC, params: [kCTMediatorParamsKeySwiftTargetModuleName:AppInfo.appDisplayName], shouldCacheTarget: false) as! UIViewController
        return loginVC
    }
}
Copy the code

Call kCTMediatorParamsKeySwiftTargetModuleName swift project module need to add parameters, corresponding to project name

The string Login corresponding to kCTMediatorTarget_Login is the last part of Target_Login class. The kCTMediatorAction_getLoginVC string getLoginVC is the latter part of the Action_getLoginVC method corresponding to Target_Login.

The method name mediator_getLoginVC here is not related to the string defined above, but is given the same name to make it easier to understand method mappings

– (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params ShouldCacheTarget :(BOOL) An implementation of shouldCacheTarget:

- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)shouldCacheTarget
{
    NSString *swiftModuleName = params[kCTMediatorParamsKeySwiftTargetModuleName];
    
    // generate target
    NSString *targetClassString = nil;
    if (swiftModuleName.length > 0) {
        targetClassString = [NSString stringWithFormat:@"%@.Target_%@", swiftModuleName, targetName];
    } else {
        targetClassString = [NSString stringWithFormat:@"Target_%@", targetName];
    }
    NSObject *target = self.cachedTarget[targetClassString];
    if (target == nil) {
        Class targetClass = NSClassFromString(targetClassString);
        target = [[targetClass alloc] init];
    }

    // generate action
    NSString *actionString = [NSString stringWithFormat:@"Action_%@:", actionName];
    SEL action = NSSelectorFromString(actionString);
    
    if (target == nil) {
        // This is one of the places to handle non-responsive requests. The demo is made simple, so if there is no target to respond to, just return. In practice, a fixed target could be assigned in advance to handle the request at this time
        [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
        return nil;
    }
    
    if (shouldCacheTarget) {
        self.cachedTarget[targetClassString] = target;
    }

    if ([target respondsToSelector:action]) {
        return [self safePerformAction:action target:target params:params];
    } else {
        // This is where the non-response request is handled. If there is no response, try calling the notFound method corresponding to target
        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. Without notFound, the demo returns 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

In the implementation of CTMediator, these defined strings are concatenated into the corresponding Target_Login and corresponding Action_getLoginVC method by the runtime mechanism and invoked.

Five, use and decoupling ideas

1. Use method:

let login = CTMediator.sharedInstance()?.mediator_getLoginVC()
Copy the code

Obtain the login controller page and perform operations.

2. Decouple the idea

  • Only need toTarget_LoginIs dependent on its own module, butTarget_LoginThe self is not exposed to the outside
  • Externally by callingCTMediator+LoginTo implement theTarget_LoginBut classCTMediator+LoginwithTarget_LoginDependencies are not generated directly, but are implemented internally through the runtime.
  • De-model the module before passingNSDictionaryThe value of

Sixth, other

1. Special types of value transfer

Take the three-level region selection list as an example to demonstrate the closure pass value:

    /// Select a region
    func selectAreaAction(a) {
        let area = CTMediator.sharedInstance()?.mediator_getAreaVC(callBack: { (dict) in
            
        })
        area!.hidesBottomBarWhenPushed = true
        navigationController?.pushViewController(area!, animated: true)}Copy the code

CallBack is the closure transmission value after the selected region is returned and the information of the selected region is returned. This closure ultimately needs to be tied to the implementation class of the tier 3 region selection module via middleware.

1> Put the closure in the NSDictionary and pass it through the middleware to the region selection class

let kCTMediatorTarget_Area = "Area";// corresponds to the Area in Target_Area

let kCTMediatorAction_getAreaVC = "getAreaVC";// corresponds to the second half of the method name Action_getAreaVC in Target_Area

extension CTMediator {
    func mediator_getAreaVC(callBack: @escaping (NSDictionary) - >Void) -> UIViewController {
        let params = [kCTMediatorParamsKeySwiftTargetModuleName:AppInfo.appDisplayName, "callBack": callBack] as [AnyHashable : Any]
        if let loginVC = performTarget(kCTMediatorTarget_Area,
                                                      action: kCTMediatorAction_getAreaVC,
                                                      params: params,
                                                      shouldCacheTarget: false) as? UIViewController {
            return loginVC
        }
        return CTMErrorViewController()}}Copy the code

KCTMediatorParamsKeySwiftTargetModuleName this parameter for swift call namespace, corresponding to the project name

2> Bind the closure to the locale selection class

class Target_Area: NSObject {
    
    /// Get the area selection view controller
    /// -parameter sender: Parameter
    /// - Returns the controller
    @objc func Action_getAreaVC(_ data: NSDictionary) -> UIViewController {
        typealias CallBack = (_ dictionary: NSDictionary) - >Void
        let callBack: CallBack = data.object(forKey: "callBack") as! CallBack
        let area = SelectAreaController()
        area.selectedBlock = {(dict) in
            callBack(dict)
        }
        return area
    }
}
Copy the code