CTMediator的Swift应用

栏目: Swift · 发布时间: 7年前

内容简介:如果你的工程是采用CTMediator方案做的组件化,看完本文以后,你就可以做到渐进式地迁移到Swift了。 CTMediator支持所有情况的调用,具体可以看文后总结。你的工程可以让Swift组件和Objective-C组件通过CTMediator混合调用 也就是说:以后再开新的组件,可以直接用Swift来写,旧有代码不会收到任何影响。本文提及的框架:

如果你的工程是采用CTMediator方案做的组件化,看完本文以后,你就可以做到渐进式地迁移到Swift了。 CTMediator支持所有情况的调用,具体可以看文后总结。你的工程可以让Swift组件和Objective-C组件通过CTMediator混合调用 也就是说:以后再开新的组件,可以直接用Swift来写,旧有代码不会收到任何影响。

这篇文章适用最低支持iOS 8的工程,且必须使用Cocoapods 1.6.0.beta.1以上的版本

本文提及的框架: CTMediator

相关文章:《 iOS应用架构谈 组件化方案 》 《 在现有工程中实施基于CTMediator的组件化方案

Swift调度Demo工程: SwfitDemo ,Objective-C调度Demo工程: ModulizedMainProject

跑Demo前先添加私有仓库:

pod repo add PrivatePods https://github.com/ModulizationDemo/PrivatePods.git

然后进入对应工程 pod update --verbose 即可。

最近我惊喜地发现,几天前(8月17号)Cocoapods在1.6.0.beta.1版提供了 --use-modular-headers 参数,它可以大大简化依赖Objective-C Pod的Swift Pod的发版流程。

于是我打算把我过去这些为Objective-C的工程写的框架全部用Swift再写一遍。目前已经完成的只有 SwiftHandyFrame 。(这个 工具 在我自己的业余项目里用了一圈,感觉很不错,达到了我之前设想的目的。)

然而以上开源工程中,有一个从设计思想上就非常依赖Objective-C的框架是 CTMediator

我尝试去思考一个新的、符合Swift且优雅的组件化方案,但并没有找到比CTMediator更好或一样好的方案。于是我探索尝试了一下,也填了一些坑。最终我发现CTMediator方案本身虽然很不Swift,但即使是在Swift工程中,CTMediator依旧是一个很优雅的组件化方案。因为:

  1. CTMediator是一套不需要随工程迭代修改的代码,它也不与任何业务产生交互,引入之后对Swift工程开发的影响为0
  2. Swift工程可以使用Extension替代原先Objective-C工程的Category并能够正常发版。因此在方案实施全程都可以做到纯Swift编码,可以完全忘记CTMediator是Objective-C工程这回事儿
  3. 新版的CocoaPods对依赖Objective-C Pod的Swift Pod十分友好,在Swift Pod中引入CTMediator可以做到完全无感,你即使不会写Objective-C,也不影响你来使用CTMediator方案
  4. Swift工程也可以使用Objective-C工程历史遗留的Category和Target-Action来完成调度,因此Objective-C开发团队可以使用旧有代码渐进地实施Swift迁移,而不必担心引入新的bug。

综合以上几点,可以给到2个结论:

  1. CTMediator方案是可以优雅地应用在Swift工程中的
  2. 凡是过去应用CTMediator组件化方案的Objective-C团队都可以做到无痛迁移Swift

前面的部分论证了 结论1

正文章接下来的内容有两个目的:

结论2
CTMediator是Objective-C工程
  1. 为Swift响应者组件提供Target-Action
  2. 为方便调度给CTMediator写Extension
  3. Swift响应者工程、Swift调度者工程、Extension工程的发版
  4. Swift调用者通过Extension调度Objective-C响应者

1. 为Swift响应者组件提供Target-Action

Target-Action 的目的

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

所以在提供Target-Action的时候,我们一般都选择让Action把对应的业务做完,如果有调用者需要补充逻辑的,通过closure给到。而不是返回一个什么对象给调用者,然后调用者再去做逻辑。

举个例子:

A需要展示B页面。你可以选择:

1. 让B的Target-Action直接返回一个UIViewController,然后A去push或者present。
2. 让B的Target-Action直接完成页面展示的操作,具体是push还是present,由A传过来的参数决定。

一个好的Target-Action倾向于2。因为这种做法对于A业务来说,留下的足迹更小,踏雪无痕是我们追求的目标。

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

NSObject
@objc

一个Swift版的Target-Action如下:

class Target_A: NSObject { // 必须要继承自NSObject

    // 正确的Action声明
    @objc func Action_viewController(_ params:[AnyHashable:Any]?) -> UIViewController {

    }

    // 错误的Action声明:没有带@objc前缀
    func Action_viewController(_ params:[AnyHashable:Any]?) -> UIViewController {

    }

    // 错误的Action声明:方法带上了Argument Label
    func Action_viewController(viewControllerParams params:[AnyHashable:Any]?) -> UIViewController {

    }

    // 错误的Action声明:方法带上了Argument Label
    func Action_viewController(params:[AnyHashable:Any]?) -> UIViewController {

    }

}

params 的类型也可以为 NSDictionary ,所以这么写也是可以的:

    func Action_viewController(params:NSDictionary) -> UIViewController {

    }

Swift工程实现Action时的注意事项

这里要注意的点在于如何处理block或closure。主要原因是如果调用者通过Category来发起调用,那么就只能传递block。如果是通过Extension来发起调用,那么就只能传递closure。至于调用者是Swift还是Objective-C倒是无所谓的。

  • 我们先看调用者是通过Category发起的调用,然后响应者是Swift的情况

由于Category中只能传递block,所以此时你的Action获得了一个block。在swift响应者中,这个Action就要这么写:

    @objc func Action_viewController(_ params:[AnyHashable:Any]?) -> UIViewController {

        if let actionParams = params {
            let block = actionParams["callback"]

            // 转换一下
            typealias CallbackType = @convention(block) (String) -> Void
            let blockPtr = UnsafeRawPointer(Unmanaged<AnyObject>.passUnretained(block as AnyObject).toOpaque())
            let callback = unsafeBitCast(blockPtr, to: CallbackType.self)

            // 此时block就变成了closure,就可以正常调用了
            callback("success") 
        }

        let aViewController = ViewController()
        return aViewController
    }
  • 我们再看调用者通过Extension发起的调用,然后响应者是Swift的情况

这种情况就很自然了,因为两边都是Swift环境(对的,我们完全可以忽略CTMediator是一个Objective-C组件的事实),所以可以直接使用:

    @objc func Action_viewController(_ params:[AnyHashable:Any]?) -> UIViewController {

        if let actionParams = params {
            if let callback = actionParams["callback"] as? (String) -> Void {
                callback("success")
            }
        }

        let aViewController = ViewController()
        return aViewController

    }

所以如果你的响应者需要同时服务来自Category的调度和Extension的调度,而且需要处理block或closure的话,你就需要在Category中或Extension中给到一个参数,来决定你如何实现这个Action。不过一般来说这种情况很少,为了同一个调度又写Category又写Extension基本上是不太可能的。

2. 为方便调度给CTMediator写Extension

Extension 的目的

理论上我们可以直接使用CTMediator来完成调度。但是这么做的话,写调用代码的工程师就会产生这样的迷惑:“为了调度成功,我应该给到什么target,什么action,以及参数都要传哪些?”

// 如果直接这么使用CTMediator,调用工程师需要去查文档获知自己这次调用对应的target-action是什么,参数是什么,module_name是什么。
    CTMediator.sharedInstance.performTarget("A",action: "viewController", params: ["name":"casa", "age":18, kCTMediatorParamsKeySwiftTargetModuleName:"module_name"], shouldCacheTarget: false)

所以为了不让写代码的工程师迷惑,我们提供Extension来描述一个调用应该传什么样的参数。在Extension的实现中,我们写入Target、Action以及ModuleName,这样工程师就不必迷惑了。

// extension CTMediator
    public func A_show(callback:@escaping (String) -> Void) -> UIViewController?

// 给CTMediator写了extension之后,调用工程师拿到方法,按照方法的参数列表给到参数就可以了,不必去考虑对应的target-action是什么、参数是什么、module_name是什么了。
    let acontroller = CTMediator.sharedInstance().A_show { (result) in
        print(result)
    }

写Extension的注意事项

  1. 写Extension的人需要知道响应者的module名,并且在传入的参数字典中给到,例如: [kCTMediatorParamsKeySwiftTargetModuleName:"module_name"] ,这是Swift与Objective-C不同的地方。如果响应者是Swift,不给到Module名的话,runtime是调度不到响应者target-action的。如果是Objective-C的响应者,这个就可以省略了。
  2. cocoapods发版的时候要带 --use-modular-headers ,因为这个组件依赖了 CTMediator ,它是Objective-C的工程。 --use-modular-headers 这个参数在 Cocoapods 1.6.0.beta.1 以上的版本才有。
  3. 如果Extension里的某个方法要被Objective-C使用,那需要带上前缀 @objc

一个完整可行的Extension是这样的:

import CTMediator

extension CTMediator {
    // 如果这个方法也要给Objective-C工程调用,就需要加上@objc
    @objc public func A_show(callback:@escaping (String) -> Void) -> UIViewController? {
        let params = [
            "callback":callback,
            kCTMediatorParamsKeySwiftTargetModuleName:"A_swift" // 需要给到module名
            ] as [AnyHashable : Any]
        if let viewController = self.performTarget("A", action: "viewController", params: params, shouldCacheTarget: false) as? UIViewController {
            return viewController
        }
        return nil
    }
}

它的发版命令是这样的:

// 带上--use-modular-headers,Cocoapods 1.6.0.beta.1以上支持
pod repo push Your_Repository Your_Podspec_file.podspec --verbose --allow-warnings --use-libraries --use-modular-headers

3. Swift响应者工程、Swift调度者工程、Extension工程的发版

其实跟之前私有pod的发版流程一模一样,只是如果你的Swift工程依赖了Objective-C工程的话,你多带一个 --use-modular-headers 参数而已。

4. Swift调用者通过Extension调度Objective-C响应者

这种情况下要注意的其实还是只有block和closure之间的转化,Extension需要将Swift的Closure转化成Objective-C的block对象之后再传递,完整的Extension示例如下:

extension CTMediator {
    public func A_showObjc(callback:@escaping (String) -> Void) -> UIViewController? {

        // 将closure类型转化为block类型
        let callbackBlock = callback as @convention(block) (String) -> Void
        let callbackBlockObject = unsafeBitCast(callbackBlock, to: AnyObject.self)

        // 转化完毕就可以放入params中传递了
        let params = ["callback":callbackBlockObject] as [AnyHashable:Any]

        if let viewController = self.performTarget("A", action: "viewController", params: params, shouldCacheTarget: false) as? UIViewController {
            return viewController
        }
        return nil
    }
}

对应的响应者Target-Action如下:

- (UIViewController *)Action_viewController:(NSDictionary *)params
{
    typedef void (^CallbackType)(NSString *);
    CallbackType callback = params[@"callback"];
    if (callback) {
        callback(@"success");
    }
    AViewController *viewController = [[AViewController alloc] init];
    return viewController;
}

这样就能保证调用成功。

5. 总结

其实如果你的工程是Swift调用者、Swift响应者的情况,那么应用CTMediator就一点问题都没有。

对于Objective-C的开发团队来说,如果开始渐进地将工程Swift化,那么就需要分各种情况去处理block和closure转化的问题。在这里我把可能出现的所有情况及其处理方法总结如下:

Swift调用者 + Extension + Swift响应者

kCTMediatorParamsKeySwiftTargetModuleName
_

Swift调用者 + Extension + Objective-C响应者

kCTMediatorParamsKeySwiftTargetModuleName

Swift调用者 + Category + Swift响应者

kCTMediatorParamsKeySwiftTargetModuleName
_

Swift调用者 + Category + Objective-C响应者

kCTMediatorParamsKeySwiftTargetModuleName

Objective-C调用者 + Category + Objective-C响应者

kCTMediatorParamsKeySwiftTargetModuleName

Objective-C调用者 + Category + Swift响应者

kCTMediatorParamsKeySwiftTargetModuleName
_

Objective-C调用者 + Extension + Objective-C响应者

kCTMediatorParamsKeySwiftTargetModuleName

Objective-C调用者 + Extension + Swift响应者

kCTMediatorParamsKeySwiftTargetModuleName
_

最后给到示例工程:

Objective-C为调用者: ModulizedMainProject

Swift为调用者: SwfitDemo

使用前先添加私有仓库:

pod repo add PrivatePods https://github.com/ModulizationDemo/PrivatePods.git

然后进入对应工程 pod update --verbose 即可。

关于CTMediator还有什么内容我没讲,或者没讲清楚的,再或者你针对CTMediator方案有任何问题的,都可以在文章下方评论区向我提问。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Spark SQL内核剖析

Spark SQL内核剖析

朱锋、张韶全、黄明 / 电子工业出版社 / 2018-8 / 69.00元

Spark SQL 是 Spark 技术体系中较有影响力的应用(Killer application),也是 SQL-on-Hadoop 解决方案 中举足轻重的产品。《Spark SQL内核剖析》由 11 章构成,从源码层面深入介绍 Spark SQL 内部实现机制,以及在实际业务场 景中的开发实践,其中包括 SQL 编译实现、逻辑计划的生成与优化、物理计划的生成与优化、Aggregation 算......一起来看看 《Spark SQL内核剖析》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具