相对于Objective-C的Runtime机制,Swift的运行时机制相对低调很多,Swift语言是用C++编写的,Swift的核Library使用Swift编写的.
方法调度
Objective-C采用消息发送策略,选择器向接收器发送消息,编译阶段无法知道对象是否有对应的方法,运行时根据isa指针,找到对象所属的类结构体,然后结合类中的缓存方法列表指针和虚函数指针找到选择器对应的SEL选择器类型变量,如果找到则SEL变量对应的IMP指针找到方法实现.如果找不到对应的方法,则会启动消息转发机制,如果仍然失败,抛出异常或崩溃.
Swift的方法调度分为静态调度和动态调度两种.
静态调度:Swift中的struct方法调度是静态的,执行的时候直接跳到方法的实现,静态调度可以进行inline和其他编译器优化.需要额外的方法来存储方法信息.
struct Point{
var x:Double // 8 Bytes
var y:Double // 8 bytes
func draw(){
print("Draw point at\(x,y)")
}
}
let point1 = Point(x: 5.0, y: 5.0)
point1.draw()
print("占用内存大小:\(MemoryLayout<Point>.size)") //16
动态调度:Swift中Class是动态调度的,添加方法之后Class本身在栈上分配的仍然是一个word.堆上需要额外的一个word来存储Class的Type信息,在Class的Type信息中,在Class的Type信息中,存储着virtual table(V-Table)。根据V-Table就可以找到对应的方法执行体.
class Point{
var x:Double // 8 Bytes
var y:Double // 8 bytes
init(x:Double,y:Double) {
self.x = x
self.y = y
}
func draw(){
print("Draw point at\(x,y)")
}
}
let point2 = Point(x: 5.0, y: 5.0)
point2.draw()
print(MemoryLayout<Point>.size) //8
方法获取
Objective-C运行时依赖TypeEncoding,也就是method_getTypeEncoding返回的结果,他指定了方法的参数类型以及在函数调用时参数入栈所要的内存空间,没有这个标识就无法动态的压入参数
如果Swift类没有继承NSObject,那么是无法通过运行时获取属性和方法的.如果Swift类继承了NSObject,属性或方法中包含Objective-C中不存在的类型,如果说元组,那么也是对应的属性或方法是无法获取的.
定义TestClass和TestController:
class TestClass {
var tBool:Bool = true
var tInt:Int32 = 32
var tFloat:Float = 72.5
var tString:String = "FlyElephant"
var tObject:AnyObject? = nil
func tInterViewInfo() {
}
}
class TestController:UIViewController {
var tBool:Bool = true
var tInt:Int32 = 32
var tFloat:Float = 72.5
var tString:String = "FlyElephant"
var tObject:AnyObject? = nil
override func viewDidLoad() {
super.viewDidLoad()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func tReturnVoid(view:UIView) {
}
func tReturnVoidWithBool(value:Bool) {
}
func tReturnTuple(boolValue:Bool) -> (String,Int) {
return ("FlyElephant",100)
}
func tReturnVoidWithCharacter(aCharacter:Character) {
}
func tableView(tableView:UITableView) -> Int {
return 10
}
}
测试代码:
private func setUp1() {
let testClass:TestClass = TestClass()
showClsRuntime(cls: object_getClass(testClass))
print("\n")
let testController:TestController = TestController()
showClsRuntime(cls: object_getClass(testController))
}
func showClsRuntime(cls:AnyClass) {
print("showClsRuntime--获取方法(FlyElephant)")
var methodNum:UInt32 = 0
let methodList = class_copyMethodList(cls, &methodNum)
for index in 0..<numericCast(methodNum) {
let method:Method = methodList![index]!
print(String(utf8String: method_getTypeEncoding(method)) ?? " ",terminator: " ")
print(String(utf8String: method_copyReturnType(method)) ?? " ",terminator: " ")
print(String(_sel: method_getName(method)),terminator: " ")
print("\n")
}
print("showClsRuntime--获取变量(FlyElephant)")
var propertyNum:UInt32 = 0
let propertyList = class_copyPropertyList(cls, &propertyNum)
for index in 0..<numericCast(propertyNum) {
let property:objc_property_t = propertyList![index]!
print(String(utf8String: property_getName(property)) ?? " ",terminator: " ")
print(String(utf8String: property_getAttributes(property)) ?? " ",terminator: " ")
print("\n")
}
}
方法交换
相对于Objective-C的方法交换,对于单独的Swift类,是无法通过Objective-C直接交换的,对于继承的NSObject的类,也不是所有的方法都可以直接交换.
按照OC的套路定义的交换方法:
func methodSwizzle(cls:AnyClass,originalSelector:Selector,swizzledSelector:Selector) {
let originalMethod = class_getInstanceMethod(cls, originalSelector)
let swizzledMethod = class_getInstanceMethod(cls, swizzledSelector)
let didAddMethod = class_addMethod(cls, originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod))
if didAddMethod {
class_replaceMethod(cls, swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod))
} else {
method_exchangeImplementations(originalMethod, swizzledMethod);
}
}
交换测试:
methodSwizzle(cls: object_getClass(self), originalSelector: #selector(ViewController.viewDidAppear(_:)), swizzledSelector: #selector(ViewController.fe_viewDidAppear(_:)))
methodSwizzle(cls: object_getClass(self), originalSelector: #selector(ViewController.testMethod), swizzledSelector: #selector(ViewController.fe_testMethod))
testMethod()
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
}
func fe_viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
print("FlyElephant_viewDidAppear方法交换")
}
dynamic func testMethod() {
print("testMethod交换之前的执行")
}
dynamic func fe_testMethod() {
print("fe_testMethod交换之后的执行")
}
注意测试方法加入了dynamic特性,否则是无法通过运行时进行交换的,viewDidAppear是继承Objective-C类获得的方法,本身就被修饰为dynamic,所以能被动态替换.
测试的交换的是写在ViewController中的,Objective-C runtime 理论上会在加载和初始化类的时候调用两个类方法: load 和 initialize
。出于安全性和一致性的考虑,方法交叉过程 永远 会在 load()
方法中进行.
每一个类在加载时只会调用一次 load方法,一个 initialize 方法可以被一个类和它所有的子类调用,Swift中load类方法不会被runtime调用,所有可以在initialize执行交互过程,由于initialize会执行多次,可以通过dispatch_once确保只执行一次.
参考资料
Swift进阶之内存模型和方法调度
https://stackoverflow.com/questions/39302834/does-swift-guarantee-the-storage-order-of-fields-in-classes-and-structs/39302927#39302927
http://nshipster.cn/swift-objc-runtime/
Swift Runtime 编译和运行时原理初探
http://allegro.tech/2014/12/swift-method-dispatching.html
Type EnCodings
Swift Runtime分析:还像OC Runtime一样吗?