Swift和Objective-C运行时

即使不写一行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:)中执行。取决于你修改的类,这种方式可能是有效的且应该能保证你的的代码每次都执行。

    原文作者:heigo
    原文地址: https://www.jianshu.com/p/16581dd95f9d
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞