Runtime相关-Swift

深入了解过Objective-C这门语言的人一定知道,这是一门动态语言。在日常的开发中,也常常会使用到Runtime对自己的项目做一些小”手脚”,比如说替换这些视图控制器的viewWillAppear方法让他们在启动的时候能在自己的事件日志中记录一下,或者是记录一下这个视图控制器的时间;或者是对某个模型做一些便利化的操作,比如说动态的获取到模型里边的属性名称,然后自动的encode与decode,将JSON自动的转为Model之类的操作。

但在Swift中,纯的Swift类是没有Runtime这个概念的,它的函数调用已经不是obj-c的objc——msgsend,在编译时就确定了要调用说明函数。
可是如果你把你的Swift类继承自NSObject,它就具有了动态性,也能使用Runtime的方法了。有一点要注意的是,Tuple这个类型是swift独有的无法映射到OC,所以就无法通过runtime获取。

下面是一个简单的例子,通过runtime交换Button的点击事件,来实现Button不能无限点击的效果:

//
// ButtonExtensions.swift
// RuntimeDemo
//
// Created by panxinyu on 04/05/2017.
// Copyright © 2017 panxinyu. All rights reserved.
//
import UIKit
extension UIButton{
private static let doOnce:() = {
swizzleSendActionMethod()
}()
override open class func initialize(){
_ = doOnce
}
private static func swizzleSendActionMethod(){
let originalSelector = #selector(sendAction(_:to:for:))
let swizzleSelector = #selector(xy_sendAction(_:to:for:))
changeMethod(original: originalSelector, swizzled: swizzleSelector)
}
func xy_sendAction(_ action: Selector, to target: Any?, for event: UIEvent?){
struct once{
static var canClick = true
}
if once.canClick{
// 注意这里不是死循环,因为我们已经把方法实现替换掉了,它实际上是调用原本的点击方法
xy_sendAction(action, to: target, for: event)
once.canClick = false
DispatchQueue.main.asyncAfter(deadline: .now() + 2, execute: {
once.canClick = true
})
}else{
print("单身20年手速,怕不怕")
}
}
}
extension NSObject{
/// 利用runtime替换方法实现 *****慎用*******
///
/// - Parameters:
/// - original: 原始方法
/// - swizzled: 替换后的方法
static func changeMethod(original:Selector, swizzled:Selector) -> () {
let originalMethod = class_getInstanceMethod(self, original)
let swizzledMethod = class_getInstanceMethod(self, swizzled)
//在进行 Swizzling 的时候,需要用 class_addMethod 先进行判断一下原有类中是否有要替换方法的实现
let didAddMethod: Bool = class_addMethod(self, original, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
//如果 class_addMethod 返回 yes,说明当前类中没有要替换方法的实现,所以需要在父类中查找,这时候就用到 method_getImplemetation 去获取 class_getInstanceMethod 里面的方法实现,然后再进行 class_replaceMethod 来实现 Swizzing
if didAddMethod {
class_replaceMethod(self, swizzled, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod)
}
}
}

如果有些Button需要不延时点击的话,可以给Button加上一个tag判断即可

下面还有一个runtime的使用场景:

当我们用NSCoding协议进行序列化和反序列化的时候,通常的做法是在encoder和decoder中把每一个属性都写进去,但是如果这个类有很多属性的时候,这就变成了一件麻烦事;
这时我们就可以使用runtime获取这个类的属性列表,然后遍历属性利用KVO进行归档和解档操作了

代码如下:

required init?(coder aDecoder: NSCoder) {
super.init()
// copyIvarList 获取一个类的成员变量个数
// 这个变量来储存变量的个数
var ivarCount:UInt32 = 0
let ivars = class_copyIvarList(Person.self, &ivarCount)
print("\(ivarCount) --- ivar count")
// 遍历成员变量获得成员变量的name
for i in 0..<ivarCount{
if let ivar = ivars?[Int(i)] {
// 获取到的变量名是 UnsafePointer<Int8> ,利用cString转换成String
let key = String(cString: ivar_getName(ivar))
// 解档
let value = aDecoder.decodeObject(forKey: key)
// KVC
setValue(value, forKey: key)
}
}
}
func encode(with aCoder: NSCoder) {
// copyIvarList 获取一个类的成员变量个数
var ivarCount:UInt32 = 0
let ivars = class_copyIvarList(Person.self, &ivarCount)
print("\(ivarCount) --- ivar count")
// 遍历成员变量获得成员变量的name
for i in 0..<ivarCount{
if let ivar = ivars?[Int(i)] {
// 获取到的变量名是 UnsafePointer<Int8> ,利用cString转换成String
let key = String(cString: ivar_getName(ivar))
//归档
aCoder.encode(self.value(forKey: key), forKey: key)
}
}
}
// 使用
NSKeyedArchiver.archiveRootObject(p, toFile: path)
if let p1 = NSKeyedUnarchiver.unarchiveObject(withFile: path) as? Person{
// do something
}

我们还可以自定义一个ArchivableObject的类,方便使用:

import Foundation
class Person:ArchivableObject{
var name:String!
var age:NSNumber?
var gender:String! = "none"
}
class ArchivableObject: NSObject,NSCoding {
required init?(coder aDecoder: NSCoder) {
super.init()
// copyIvarList 获取一个类的成员变量个数
var ivarCount:UInt32 = 0
let ivars = class_copyIvarList(Person.self, &ivarCount)
print("\(ivarCount) --- ivar count")
// 遍历成员变量获得成员变量的name
for i in 0..<ivarCount{
if let ivar = ivars?[Int(i)] {
let key = String(cString: ivar_getName(ivar))
// 解档
let value = aDecoder.decodeObject(forKey: key)
// KVC
setValue(value, forKey: key)
}
}
}
override init() {
super.init()
}
func encode(with aCoder: NSCoder) {
// copyIvarList 获取一个类的成员变量个数
var ivarCount:UInt32 = 0
let ivars = class_copyIvarList(Person.self, &ivarCount)
print("\(ivarCount) --- ivar count")
// 遍历成员变量获得成员变量的name
for i in 0..<ivarCount{
if let ivar = ivars?[Int(i)] {
let key = String(cString: ivar_getName(ivar))
//归档
aCoder.encode(self.value(forKey: key), forKey: key)
}
}
}
}

之前在OC中我们使用runtime的一个场景就是给一个类的category添加属性,同样在swift中我们还是可以这样使用,来给类的extension添加存储属性,代码如下:

// MARK: - UIViewController Extension
fileprivate var foo:Void?
extension UIViewController{
/// set this property to True to disable swipe pop
var foo:Bool{
get{
return objc_getAssociatedObject(self, &foo) as? Bool ?? false
}
set{
objc_setAssociatedObject(self, &foo, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
}

在上面的代码中我们就给UIViewController的extension添加一个foo的属性。