CTMediator的Swift应用

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

内容简介:如果你的工程是采用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方案有任何问题的,都可以在文章下方评论区向我提问。


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

查看所有标签

猜你喜欢:

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

《10%创业家》

《10%创业家》

[美] 帕特里克•J.麦金尼斯 / 李文远 / 广东人民出版社 / 2017-4 / 45.00

还在打工和创业之间苦苦挣扎吗?麦金尼斯用亲身经历告诉你,不用辞职,只需投入10%的时间和资源,就能获得100%的财务自由。你不需要雄厚的资本,也不必占用工作时间,只要准确掌握本书所授的方法,就能立即开始创业。 麦金尼斯是世界银行风投顾问,同时也是一名10%创业家。在本书中,他结合自身的创业咨询经历,为读者讲解了移动互联时代的5种创业模式,还提供了创业基因测试、10%创业计划、自传模板等个性化......一起来看看 《《10%创业家》》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

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

多种字符组合密码

SHA 加密
SHA 加密

SHA 加密工具