【译】Swift&目标C运行时

146 阅读4分钟

原文链接 Swift & the Objective-C Runtime

即使在没有一行目标C代码的情况下编写,每个Swift应用程序都在目标C运行时内执行,打开了动态调度和相关运行时操作的世界。可以肯定的是,情况可能并不总是如此——无论何时出现仅限Swift的框架,都可能导致仅限Swift的运行时。但是只要目标C运行时在我们身边,让我们充分利用它。

本周,我们将对NS Hipster上涵盖的两种运行时技术进行新的、以Swift为重点的研究,当时目标C是镇上唯一的游戏:相关的**对象方法**摇摆。

注意:这篇文章主要涵盖了这些技术在斯威夫特中的使用——关于完整的介绍,请参考原始文章。

关联对象

快速扩展允许在增加现有可可类的功能方面有很大的灵活性,但是它们和它们的目标C兄弟类别一样受到限制。也就是说,您不能通过扩展向现有类添加属性。

令人高兴的是,目标C相关的物体前来救援。例如,要向项目中的所有视图控制器添加描述性名称属性,我们只需在后备get和set块中使用objc_get/setAssociatedObject()添加计算属性:

extension UIViewController {    private struct AssociatedKeys {        static var DescriptiveName = "nsh_DescriptiveName"    }    var descriptiveName: String? {        get {            return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String        }        set {            if let newValue = newValue {                objc_setAssociatedObject(                    self,                    &AssociatedKeys.DescriptiveName,                    newValue as NSString?,                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC                )            }        }    }}

请注意在私有嵌套结构中使用静态变量——这种模式创建了我们需要的静态关联对象键,但不会混淆全局命名空间。

方法Swizzling

有时是为了方便,有时是为了解决框架中的错误,或者有时是因为没有其他方法,您需要修改现有类方法的行为。方法swizzling允许您交换两个方法的实现,本质上是用自己的方法重写现有的方法,同时保留原始方法。

在这个例子中,我们摇动UI View Controller的view Will Epar方法,以便在视图即将出现在屏幕上的任何时候打印消息。swizzling发生在特殊类方法初始化中(见下面的注释);替换实现在nsh_viewWillAppear方法中:

extension UIViewController {    public override class func initialize() {        struct Static {            static var token: dispatch_once_t = 0        }        // make sure this isn't a subclass        if self !== UIViewController.self {            return        }        dispatch_once(&Static.token) {            let originalSelector = Selector("viewWillAppear:")            let swizzledSelector = Selector("nsh_viewWillAppear:")            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 nsh_viewWillAppear(animated: Bool) {        self.nsh_viewWillAppear(animated)        if let name = self.descriptiveName {            print("viewWillAppear: \(name)")        } else {            print("viewWillAppear: \(self)")        }    }}

加载与初始化(Swift版)

在应用程序进程中加载和初始化类时,目标C运行时通常会自动调用两个类方法:加载和初始化。在关于**方法swizzling**的完整文章中,Mattt写到,为了安全和一致性,swizzling应该总是在load()中完成。另一方面,可以对一个类及其所有子类调用单个初始化方法,这些子类可能存在于UI View Controller中,或者如果该特定类从未发送消息,则根本不调用。

不幸的是,在Swift中实现的加载类方法永远不会被运行时调用,这使得该建议不可能实现。相反,我们只能在第二选择中选择:

  • 在初始化中实现方法swizzling

  • 这是可以安全地完成的,只要您在执行时检查类型,并在dispatch_once中包装搅动(无论如何您都应该这样做)。

  • 在应用委托中实现方法swizzling

  • 不用通过类扩展添加方法swizzling,只需将方法添加到应用委托中,以便在调用应用程序(_: did Finish Launting With Options:)时执行。根据您正在修改的类,这可能就足够了,并且应该保证您的代码每次都被执行。

最后,请记住,修补目标C运行时应该是最后的手段,而不是开始的地方。修改代码所基于的框架,以及您运行的任何第三方代码,是破坏整个堆栈稳定性的快速方法。轻点踩!