iOS组件化

·  阅读 2686

这是我参与8月更文挑战的第26天,活动详情查看: 8月更文挑战

iOS组件化使用

一、实现方案

目前组件化方案大体分为两种URL/protocol注册调度runtime调度

注册调度的方案存在以下两个问题:

  • 命名域渗透
  • 注册是不必要的,因为我们需要维护注册列表,这也是一部分成本

故而本文选择runtime调度的方案来实现组件化;也就是采用CTMediatorTarget_Action组件化方案实现;CTMediator的方案在支持Objective-C组件调用的基础上,扩展支持Swift组件,可以达到混合调用的目的,组件的开发可以使用Objective-C或者Swift语言;

CTMediator方案的表象是通过runtime调度Target-Action,但是CTMediator方案的本质是在不需要动业务代码的情况下,完成调度

所以在提供Target-Action的时候,我们一般都选择让Action把对应的业务做完

Swift工程声明Target-Action的注意事项:

  • Target对象必须要继承自NSObject
  • Action方法必须带@objc前缀
  • Action方法第一个参数不能有Argument Label

二、引入中间件

pod 'CTMediator', '~> 25'

三、使用(以登录Login模块为例)

登录模块目录

Mediator文件夹存放Login模块对外开放的中间件
Target_Login文件即为Target_Action方案的具体实现,也是调用登录模块功能的实现 CTMediator+Login文件即为Target_Action方案对外开放的接口,也是外部模块调用Login模块时可使用的方法。

两个类的后缀Login可以不必保持一致,但需要在内部定义时指定好对应的映射关系,此处为了便于理解,将后缀保持一致。

四、代码解析

1、Target_Login

Login模块对外暴露的接口类CTMediator+Login中方法的具体实现

class Target_Login: NSObject {
    
    /// 获取登录视图控制器
    /// - Parameter sender: 参数
    /// - Returns: 返回控制器
    @objc func Action_getLoginVC(_ sender: NSDictionary) -> UIViewController {
        let login = LoginViewController()
        return login
    }
}
复制代码

Swift工程声明Target-Action的注意事项

  • Target对象必须要继承自NSObject;
  • Action方法必须带@objc前缀;
  • 响应者action方法首参数不要带Argument Label,用_;
  • 响应者的Target-Action需要将block转化成closure;

2、CTMediator+Login

此类为对外开发的可调用的接口,也是中间件CTMediator的类别
其内部实现为:

import CTMediator

let kCTMediatorTarget_Login = "Login";//对应Target_Login中的Login

let kCTMediatorAction_getLoginVC = "getLoginVC";//对应Target_Login中的方法名Action_getLoginVC后半段

extension CTMediator {
    
    /// 获取登录视图控制器
    /// - Returns: 返回控制器
    func mediator_getLoginVC() -> UIViewController {
        let loginVC: UIViewController = performTarget(kCTMediatorTarget_Login, action: kCTMediatorAction_getLoginVC, params: [kCTMediatorParamsKeySwiftTargetModuleName:AppInfo.appDisplayName], shouldCacheTarget: false) as! UIViewController
        return loginVC
    }
}
复制代码

调用swift项目模块需要添加参数kCTMediatorParamsKeySwiftTargetModuleName,对应为工程名字

其中 kCTMediatorTarget_Login对应的字符串Login即为Target_Login类对应的后半部分; kCTMediatorAction_getLoginVC对应的字符串getLoginVC即为Target_Login类对应的方法Action_getLoginVC后半部分;

此处的方法名mediator_getLoginVC跟上述定义的字符串并无关系,为了便于理解方法对应关系,起了一样的名字

我们来看一下- (id)performTarget:(NSString *)targetName action:(NSString *)actionName params:(NSDictionary *)params shouldCacheTarget:(BOOL)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) {
        // 这里是处理无响应请求的地方之一,这个demo做得比较简单,如果没有可以响应的target,就直接return了。实际开发过程中是可以事先给一个固定的target专门用于在这个时候顶上,然后处理这种请求的
        [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 {
        // 这里是处理无响应请求的地方,如果无响应,则尝试调用对应target的notFound方法统一处理
        SEL action = NSSelectorFromString(@"notFound:");
        if ([target respondsToSelector:action]) {
            return [self safePerformAction:action target:target params:params];
        } else {
            // 这里也是处理无响应请求的地方,在notFound都没有的时候,这个demo是直接return了。实际开发过程中,可以用前面提到的固定的target顶上的。
            [self NoTargetActionResponseWithTargetString:targetClassString selectorString:actionString originParams:params];
            [self.cachedTarget removeObjectForKey:targetClassString];
            return nil;
        }
    }
}
复制代码

CTMediator的实现中,会根据这些定义的字符串通过运行时机制拼接成对应的Target_Login及对应的方法Action_getLoginVC并调用。

五、使用及解耦思路

1、使用方法:

let login = CTMediator.sharedInstance()?.mediator_getLoginVC()
复制代码

获取登录控制器界面,然后进行操作。

2、解耦思路

  • 只需要在Target_Login中对自身模块产生依赖,但Target_Login自身并不暴露与外部
  • 外部通过调用CTMediator+Login来实现对Target_Login的调用,但类CTMediator+LoginTarget_Login并不直接产生依赖关系,而是通过运行时实现内部依赖。
  • 去model化,模块之前通过NSDictionary传值

六、其他

1、特殊类型传值

以三级区域选择列表为例,演示闭包传值:

    /// 选择区域
    func selectAreaAction() {
        let area = CTMediator.sharedInstance()?.mediator_getAreaVC(callBack: { (dict) in
            
        })
        area!.hidesBottomBarWhenPushed = true
        navigationController?.pushViewController(area!, animated: true)
    }
复制代码

其中callBack即为选中区域之后,返回时的闭包传值,传回选中的区域信息。此闭包最终需要通过中间件绑定在三级区域选择模块的实现类上,那么如何操作:

1> 将闭包放在NSDictionary中,通过中间件传递给区域选择类

let kCTMediatorTarget_Area = "Area";//对应Target_Area中的Area

let kCTMediatorAction_getAreaVC = "getAreaVC";//对应Target_Area中的方法名Action_getAreaVC后半段

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()
    }
}
复制代码

kCTMediatorParamsKeySwiftTargetModuleName 此参数为swift调用的命名空间,对应工程名字

2> 将闭包绑定于区域选择类

class Target_Area: NSObject {
    
    /// 获取区域选择视图控制器
    /// - Parameter sender: 参数
    /// - Returns: 返回控制器
    @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
    }
}
复制代码
分类:
iOS
标签:
分类:
iOS
标签:
收藏成功!
已添加到「」, 点击更改