如何在 Swift 中高效地使用 Method Swizzling

如何在 Swift 中高效地使用 Method Swizzling,第1张

概述作者:uraimo,原文链接,原文日期:2015-10-23 译者:小铁匠Linus;校对:100mango;定稿:Channe 示例程序可以从 Github 上下载。 (译者注:译者也写了一篇关于 Method Swizzling 的文章 Runtime 之 Method Swizzling,顺便写了两个小 demo 来对比 Objective-C 和 Swift 之间 Method Swizz

作者:uraimo,原文链接,原文日期:2015-10-23
译者:小铁匠Linus;校对:100mango;定稿:Channe

示例程序可以从 Github 上下载。

(译者注:译者也写了一篇关于 Method Swizzling 的文章 Runtime 之 Method Swizzling,顺便写了两个小 demo 来对比 Objective-C 和 Swift 之间 Method Swizzling 的异同,示例代码传送门)

Method Swizzling 在 Objective-C 或其他语言中是一种很有名的技术,用来支持动态方法派发。

Method Swizzling 通过改变特定 selector(方法)与实际实现之间的映射,在 runtime 时将一个方法的实现替换成其它方法的实现。

虽然这看起来非常方便,但是这个功能也有其缺点。在 runtime 执行这类更改时,你就不能在编译时利用那些可用的安全检查了。因此,应该小心使用 Method Swizzling。

NSHipster 上有一篇关于如何在 Objective-C 中使用 Method Swizzling 的文章(译者注:南峰子的技术博客有这篇文章的译文)(其他详情也可以看这里)以及 Stackoverflow 上有一些如何使用 Method Swizzling 的讨论。

Swift 关于方法派发是使用静态方法的,但有些情形可能需要用到 Method Swizzling。

在 Swift 中使用 Method Swizzling 之前,让我再次重申一下这种技术还是尽量少用,只有当你的问题不能用 Swift 的方式解决,也不能用子类、协议或扩展解决时才使用。

正如 NSHipster 上另一篇文章描述的那样,在 Swift 中对一个来自基本框架(Foundation、UIKit 等)的类使用 Method Swizzling 与 Objective-C 没什么区别。

extension UIVIEwController {    public overrIDe static func initialize() {        struct Static {            static var token: dispatch_once_t = 0        }        // 确保不是子类        if self !== UIVIEwController.self {            return        }        dispatch_once(&Static.token) {            let originalSelector = Selector("vIEwWillAppear:")            let swizzledSelector = Selector("newVIEwWillAppear:")            let originalMethod = class_getInstanceMethod(self,originalSelector)            let swizzledMethod = class_getInstanceMethod(self,swizzledSelector)            let dIDAddMethod = class_addMethod(self,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEnCoding(swizzledMethod))            if dIDAddMethod {                class_replaceMethod(self,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEnCoding(originalMethod))            } else {                method_exchangeImplementations(originalMethod,swizzledMethod);            }        }    }    // MARK: - Method Swizzling    func newVIEwWillAppear(animated: Bool) {        self.newVIEwWillAppear(animated)        if let name = self.descriptivename {            print("vIEwWillAppear: \(name)")        } else {            print("vIEwWillAppear: \(self)")        }    }}

在这个例子中,应用中的每个 UIVIEwController 都会执行额外的 *** 作,而原始的 vIEwWillAppear 方法会被保护起来不执行,这种情形只能通过 Method Swizzling 来实现。

vIEwWillAppear 方法的实现会被替换成 initializenewVIEwWillAppear 方法的实现。值得注意的是,在 swizzle 后,会递归调用 newVIEwWillAppear 方法,以替换之前原始的 vIEwWillAppear 方法。

与 Objective-C 的第一个不同之处就是 swizzling 不在 load 方法里执行。

加载一个类的定义时,会调用 load 方法,因此这地方适合执行 Method Swizzling 。

但是 load 方法只在 Objective-C 里有,而且不能在 Swift 里重载,不管怎么试都会报编译错误。接下来执行 swizzle 最好的地方就是 initialize 了,这是调用你的类中第一个方法前的地方。

所有对修改方法执行的 *** 作放在 dispatch_once 中,这是为了确保这个过程只执行一次。

这些都是你要知道的关于继承自基本框架或 Objective-C 桥接类的。如果你要在纯 Swift 类中正确使用 Method Swizzling 的话,就需要将下面这些注意事项记在心上。

Swift 自定义类中使用 Method Swizzling

要在 Swift 自定义类中使用 Method Swizzling 有两个必要条件:

包含 swizzle 方法的类需要继承自 NSObject

需要 swizzle 的方法必须有动态属性(dynamic attribute)

更多关于为什么必须需要这些可以在苹果的文档里查看:

需要动态派发

当 @objc 属性将你的 Swift API 暴露给 Objective-C runtime 时,不能确保属性、方法、初始器的动态派发。Swift 编译器可能为了优化你的代码,而绕过 Objective-C runtime。当你指定一个成员变量 为 dynamic 时,访问该变量就总会动态派发了。因为在变量定义时指定 dynamic 后,会使用 Objective-C runtime 来派发。

动态派发是必须的。但是,你必须使用 dynamic 修饰才能知道在运行时 API 的实现被替换了。举个例子,你可以在 Objective-C runtime 使用 method_exchangeImplementations 方法来交换两个方法的实现。假如 Swift 编译器将方法的实现内联(inline)了或者访问去虚拟化(devirtualize)了,这个交换过来的新的实现就无效了。

说白了,如果你想要替换的方法没有声明为 dynamic 的话,就不能 swizzle。

翻译成代码就是这样:

class TestSwizzling : NSObject {    dynamic func methodone()->Int{        return 1    }}extension TestSwizzling {        //在 Objective-C 中,我们在 load() 方法进行 swizzling。但Swift不允许使用这个方法。    overrIDe class func initialize()    {        struct Static        {            static var token: dispatch_once_t = 0;        }                // 只执行一次        dispatch_once(&Static.token)        {                let originalSelector = Selector("methodone");                let swizzledSelector = Selector("methodTwo");                                let originalMethod = class_getInstanceMethod(self,originalSelector);                let swizzledMethod = class_getInstanceMethod(self,swizzledSelector);                                method_exchangeImplementations(originalMethod,swizzledMethod);        }    }        func methodTwo()->Int{        // swizzling 后,该方法就不会递归调用        return methodTwo()+1    }}var c = TestSwizzling()print(c.methodone())  //2print(c.methodTwo())  //1

这个简单的例子中,在 TestSwizzling 对象第一个方法被调用前,methodonemethodTwo 的实现被相互替换了。

结束语

正如你看到的,在 Swift 中还是可以使用 Method Swizzling 的,只是我的意见是,绝大部分时候不应该出现在实际的产品代码里。如果你想通过 swizzling 快速解决问题的话,那更好的办法就是想出一个更好的架构,重构你的代码。

本文由 SwiftGG 翻译组翻译,已经获得作者翻译授权,最新文章请访问 http://swift.gg。

总结

以上是内存溢出为你收集整理的如何在 Swift 中高效地使用 Method Swizzling全部内容,希望文章能够帮你解决如何在 Swift 中高效地使用 Method Swizzling所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/web/1090734.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-27
下一篇 2022-05-27

发表评论

登录后才能评论

评论列表(0条)

保存