V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
OneAPM
V2EX  ›  Swift

Swift 中的 Runtime

  •  
  •   OneAPM ·
    oneapm · 2015-12-18 15:35:32 +08:00 · 3099 次点击
    这是一个创建于 3049 天前的主题,其中的信息可能已经有所发展或是发生改变。

    即使在 Swift APP 中没有一行 Object-c 的代码,每个 APP 也都会在 Object-c runtime 中运行,为动态任务分发和运行时对象关联开启了一个世界。更确切地说,可能在仅使用 Swift 库的时候只运行 Swift runtime 。但是使用 Objective-C runtime 这么长时间,我们也应该让他充分发挥其作用。

    下面我们将以 Swift 的视角来观察关联对象(associated objects])和方法交叉(method swizzling) 这两个在运行时的技术。

    关联对象(Associated Objects)

    Swift extension 可以给已经存在 Cocoa 类添加极为丰富的功能,具体有:
    ( 1 )添加计算实例属性 ( computed property) 和计算类属性

    ( 2 )定义实例方法和类方法

    ( 3 )提供新的构造器

    ( 4 )定义下标( subscript )

    ( 5 )定义和使用新的嵌套类型

    ( 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 。随便修改基础框架或所使用的三方代码会给项目造成很大的影响。请务必要小心哦。

    文章来源:Swift&Object-c Runtime

    备注:本文已经得到原作者的同意,授权 OneAPM 技术博客进行转载

    OneAPM Mobile Insight 以真实用户体验为度量标准进行 Crash 分析,监控网络请求及网络错误,提升用户留存。访问 OneAPM 官方网站感受更多应用性能优化体验,想阅读更多技术文章,请访问 OneAPM 官方技术博客

    2 条回复    2015-12-21 20:21:18 +08:00
    canautumn
        1
    canautumn  
       2015-12-19 00:37:27 +08:00
    小学生翻译的吧?
    lawder
        2
    lawder  
       2015-12-21 20:21:18 +08:00
    这翻译也是醉了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5702 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 87ms · UTC 06:08 · PVG 14:08 · LAX 23:08 · JFK 02:08
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.