即使在没有一行目标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运行时应该是最后的手段,而不是开始的地方。修改代码所基于的框架,以及您运行的任何第三方代码,是破坏整个堆栈稳定性的快速方法。轻点踩!