即便在 Swift APP 中没有一行 Object-c 的代码,每一个 APP 也都会在 Object-c runtime 中运行,为动态任务分发和运行时对象关联开启了一个世界。更确切地说,可能在仅使用 Swift 库的时候只运行 Swift runtime。可是使用 Objective-C runtime 这么长时间,咱们也应该让他充分发挥其做用。html
下面咱们将以 Swift 的视角来观察关联对象(associated objects])和方法交叉(method swizzling) 这两个在运行时的技术。swift
####关联对象(Associated Objects)安全
Swift extension 能够给已经存在 Cocoa 类添加极为丰富的功能,具体有: (1)添加计算实例属性 ( computed property) 和计算类属性性能优化
(2)定义实例方法和类方法网络
(3)提供新的构造器app
(4)定义下标(subscript)框架
(5)定义和使用新的嵌套类型ide
(6)使一个遵照某个接口性能
相比之下, Objective-C 的 category 就逊色多了。好比说 Objective-C 中的 extension 就没法向既有类添加属性。优化
庆幸的是 Objective-C 的 关联对象(Associated Objects) 能够改善这个缺憾。例如要向一个工程里全部的 view controllers 中添加一个 descriptiveName 属性,咱们能够简单的使用 objc_get/setAssociatedObject()
来填充其 get 和 set 块:
Swift 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?, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC) ) } } }
注意,在私有嵌套 struct 中使用 static var,这样会生成咱们所需的关联对象键,但不会污染整个命名空间。
#####方法交叉(Method Swizzling)
有时为了方便,也有多是解决某些框架内的 bug,或者别无他法时,须要修改一个已经存在类的方法的行为。方法交叉能够实现两个方法的交换,至关因而用你本身写的方法来重载原有方法,而且还可以是原有方法的行为保持不变。
下面,咱们说一个例子,在这个例子中咱们交叉 UIViewController 的 viewWillAppear 方法,而后打印出每个在屏幕上显示的 view。方法交叉发生在 initialize 类方法调用时(以下代码所示);替代的实如今 nsh_viewWillAppear 方法中:
Swift 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 { println("viewWillAppear: \(name)") } else { println("viewWillAppear: \(self)") } } }
load vs. initialize (Swift 版本)
Objective-C runtime 理论上会在加载和初始化类的时候调用两个类方法: load and initialize。在讲解 method swizzling 的原文中曾指出出于安全性和一致性的考虑,方法交叉过程 永远 会在 load() 方法中进行。每个类在加载时只会调用一次 load 方法。另外一方面,一个 initialize 方法能够被一个类和它全部的子类调用,好比说 UIViewController 的该方法,若是那个类没有被传递信息,那么它的 initialize 方法就永远不会被调用了。
可不一样的是,在 Swift 中 load 类方法是不会被 runtime 调用,所以 Method Swizzling 就没有办法来实现,可是,咱们有以下两个方法能够来解决:
1.在 initialize 中实现方法交叉 这种作法很安全,你只须要确保相关的方法交叉在一个 dispatch_once 中就行了(这也是最推荐的作法)。
2.在 app delegate 中实现方法交叉 不像上面经过类扩展进行方法交叉,而是简单地在 app delegate 的 application(_:didFinishLaunchingWithOptions:) 方法调用时中执行相关代码也是能够的。基于对类的修改,这种方法应该就足够确保这些代码会被执行到。
最后,提醒你们,在不得已的状况下才去使用 Objective-C runtime。随便修改基础框架或所使用的三方代码会给项目形成很大的影响。请务必要当心哦。
备注:本文已经获得原做者的赞成,受权 OneAPM 技术博客进行转载
OneAPM Mobile Insight 以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提高用户留存。访问 OneAPM 官方网站感觉更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客。