即使不写一行OC(Objective-C)代码,每个Swfit应用还是执行在OC运行时内部,打开一个动态调度的世界和有关的运行时操作。的确,项目只用Swift框架的情况不总是存在,但一旦这种情况来临,可能就会出现运行时内只存在Swift而没有OC。只要OC运行时一直存在,咱们就该发挥它的全部潜力
关联对象(Associated Objects)
Swift扩展对于在现有Cocoa类中添加功能函数有极大地灵活性,但就像Swift同胞OC的类别(category)一样,扩展是有限制的。也就是说,通过扩展也不能对现有的类添加属性。
//令人欣慰的是,OC关联对象可实现对现有类添加属性。例如,为了在工程内对所有View Controllers添加名为descriptiveName属性,我们只需简单地在属性的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
)
}
}
}
}
方法置换(Method Swizzling)
有时为了方便,有时为了解决框架的bug,有时因为没有其他方法,需要改变已存在类的方法的实现。方法置换可以交换两个方法的实现,最重要的是覆盖已存在的方法的同时不改变原方法的实现。
在本例中,通过改变UIViewController’s viewWillAppear方法的实现在任何界面即将出现时打印一条信息。置换发生在类的静态initialize方法,替换的实现是在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)")
}
}
}
load vs initialize (Swift版本)
OC运行时在加载和初始化应用类过程中通常会自动调用两个类方法:load和initialize。在文章method swizzling中,Mattt指出从安全和便利角度,替换过程通常应该在load方法内。load方法每个类只会调用一次,且是在加载类时被调用。从另外方面,initialize方法能被类及其子类(对于UIViewController来说很可能存在)调用,但在没有任何消息发送到该类情况下,initialize不会被调用。
不幸的是,Swift在运行时不会调用load方法,所以Mattt推荐的方式不能实现。作为替代,我们选择了次优方法:
在initialize方法中做置换
这种实现是安全的,只要你在运行时检查好类型且用dispatch_once包裹置换方法。
在app delegate中做置换
不通过类扩展添加置换方法,而简单的把替换过程在application(_:didFinishLaunchingWithOptions:)中执行。取决于你修改的类,这种方式可能是有效的且应该能保证你的的代码每次都执行。