前言
我们都知道OC是一门动态语言,它的动态特性就是基于Runtime,正因为Runtime的存在,OC 才可以在运行时创建类、进行消息转发和方法交换等。那么Swift作为一门静态语言,它有Runtime吗?这里我们可以研究一下。
一、Runtime
这里我们按照OC的runtime用法来获取Swift类的属性和方法,直接添加代码:
import Foundation
class Person {
var age = 3
func run() {
print("run")
}
}
do {
var methodCount: UInt32 = 0
let methodlist = class_copyMethodList(Person.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodlist?[i] {
let methodName = method_getName(method)
print("methodName:\(String(describing: methodName))")
} else {
print("methodName not found")
}
}
var propertyCount: UInt32 = 0
let propertyList = class_copyPropertyList(Person.self, &propertyCount)
for i in 0..<numericCast(propertyCount) {
if let property = propertyList?[i] {
let propertyName = property_getName(property)
print("propertyName:\(String(utf8String: propertyName)!)")
} else {
print("propertyName not found")
}
}
}
执行之后可以发现Person类的方法和属性都为空。如果我们在方法和属性前面都加上@objc标识,会发生什么?
import Foundation
class Person {
@objc var age = 3
@objc func run() {
print("run")
}
}
do {
var methodCount: UInt32 = 0
let methodlist = class_copyMethodList(Person.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodlist?[i] {
let methodName = method_getName(method)
print("methodName:\(String(describing: methodName))")
} else {
print("methodName not found")
}
}
var propertyCount: UInt32 = 0
let propertyList = class_copyPropertyList(Person.self, &propertyCount)
for i in 0..<numericCast(propertyCount) {
if let property = propertyList?[i] {
let propertyName = property_getName(property)
print("propertyName:\(String(utf8String: propertyName)!)")
} else {
print("propertyName not found")
}
}
}
/* 执行结果
methodName:init
methodName:age
methodName:run
methodName:setAge:
propertyName:age
Program ended with exit code: 0
*/
执行之后可以看到Person类的方法和属性都能正常打印了。这个如果我们去桥接文件看一下,还会发现一个问题
从桥接文件中无法找到Person类的相关信息,也就是说OC是无法调用Swift中的Person类。此时我们再让Person类继承自NSObject,执行之后发现桥接文件中会出现Person类的相关信息:
如果此时我们把方法和属性前的@objc都去掉再执行,则会发现执行结果是这样的:
再查看桥接文件:
这里可以看到Person类只有一个初始化方法,别的啥都没有。结合之前分析过的方法调度,这里我们可以总结一下:
- 对于纯
Swift类来说,方法和属性不加任何修饰符的情况下,这个时候其实已经不具备所谓的Runtime特性了。 - 对于纯
Swift类,方法和属性添加@objc标识的情况下,当前我们可以通过Runtime API拿到, 但是在我们的OC中是没法进行调度的。 - 对于继承自
NSObject类来说,如果我们想要动态的获取类的属性和方法,必须在其声明前添加@objc关键字,否则也是没有办法通过Runtime API获取的。 - 纯
Swift类没有动态性,但在方法、属性前添加dynamic修饰,可获得动态性。 - 继承自
NSObject的Swift类,其继承自父类的方法具有动态性,其它自定义方法、属性想要获得动态性,需要添加dynamic修饰。 - 若方法的参数、属性类型为
Swift特有(无法映射到OC的类型,如Character、Tuple),则此方法、属性无法添加dynamic修饰(编译器报错)。
那么纯Swift类没有Runtime的特性,我们该如何获取类的方法、属性呢?
二、Mirro(反射)
所谓反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OC的Runtime要⽐其他语⾔中的反射强⼤的多。而Swift是⼀⻔类型安全的语⾔,它不⽀持我们像OC那样直接操作,但它的标准库提供了反射机制来让我们访问成员信息。Swift的反射机制是基于⼀个叫 Mirror 的结构体来实现的。为具体的实例创建⼀个Mirror对象,然后就可以通过它查询这个实例。
1. 基本用法
这里我们直接添加代码:
import Foundation
class Person {
var age = 3
func run() {
print("run")
}
}
let p = Person()
p.run()
let mirror = Mirror(reflecting: p)
mirror.children.forEach { item in
print("\(String(describing: item.label)):\(item.value))")
}
/* 执行结果
run
Optional("age"):3)
Program ended with exit code: 0
*/
执行之后可以发现,遍历 mirror.children 就可以将Person的方法和属性打印出来。我们查看一下 mirror.children :
这里可以看到 mirror.children 只是存放元祖类型 Child 的集合。那么它的值是如何来的,我们去源码中查看一下。
2. 源码分析
这里我们在 Mirro.swift 中找到初始化方法:
这里可以看到,首先通过模式匹配判断 subject 是否遵循了 CustomReflectable,是的话就调customMirror,否则就走 internalReflecting 初始化。那么 CustomReflectable 协议是什么?我们可以看一下:
这是可以看到,我们正常调试时 po p 打印的仅仅是地址,如果我们增加了 CustomReflectable 协议:
这是可以看到,我们调试时 po p 打印的除了地址还有其它属性。有点类似自定义的 description 效果,我们调试时可以这么用。接下来我们继续查看 internalReflecting 初始化方法:
这里我们通过全局搜索可以找到该初始化方法,大致看一下,这里做了几件事:
- 调用
_getNormalizedType获取subject的具体类型 - 调用
_getChildCount获取该类型的childCount - 当
childCount大于0时调用getChild获取child - 将这些
child储存到children中
这里查看一下 _getNormalizedType 、_getChildCount 和 getChild:
从这里可以看到这几个方法前都用编译器字段 @_silgen_name 标识,这是 Swift 的一个隐藏符号,作用是将某个 C/C++ 语言函数直接映射为 Swift 函数。 这里我们可以试一下,先定义一个C函数:
这里可以看到,我们定义的 c_max(C函数) 被映射成 swift_max(Swift函数),并能够正常调用。回到源码中,_getNormalizedType 、_getChildCount 和 getChild 最终调的是:
swift_reflectionMirror_normalizedTypeswift_reflectionMirror_countswift_reflectionMirror_subscript
这里我们分别搜索一下:
先搜索 swift_reflectionMirror_normalizedType 可以找到:
这里可以看到 swift_reflectionMirror_normalizedType 调了call后返回 impl->type。
再搜索 swift_reflectionMirror_count:
这里可以看到 swift_reflectionMirror_count 调了call后返回的 impl->count。
最后搜索 swift_reflectionMirror_subscript:
这里可以看到 swift_reflectionMirror_subscript 调了call后返回的 impl->subscript。到这里我们可以发现这些方法有个共同点,它们都在调 call。这里我们接着搜索 call:
在 ReflectionMirror.mm 中可以看到 call 方法的具体实现,注意这里定义了 call 和 callClass 两个闭包,通过后面的模式匹配代码可以发现除了类调 callClass(最后也会调call) ,其它的如枚举、结构体都是调 call 。查看 call 可以得知传入的信息都是 ReflectionMirrorImpl 类型,那么这个 ReflectionMirrorImpl 是什么? 我们直接搜索 ReflectionMirrorImpl:
这里可以看到这个 ReflectionMirrorImpl 其实是抽象类。再查看模式匹配的逻辑可以得知不同类型在调 call 时传入的 impl 不同,比如 纯Swift类是ClassImpl(OC类是ObjCClassImpl), 当然这些 impl 都实现了抽象类 ReflectionMirrorImpl,也就是说这些 impl 都有 type(MetaData)、count 和 subscript。 这里我们看一下 ClassImpl(截取片段):
这里可以看到 ClassImpl 的具体信息, 它有个属性isReflectable,首先是将 type 强转成 ClassMetadata ,然后或取它的 Description,最后取 Description 的 isReflectable,这个 isReflectable 就是标识当前类型是否支持反射。另外我们可以看到 count 和 subscript。 先看 count,它是在获取 ClassMetadata 的 Description, 然后从 Description 中取 NumFields。这里我们搜索一下 ClassMetadata:
这里可以看到 ClassMetadata其实是 TargetClassMetadat 的别名,从 TargetClassMetadat 中找到 Description 属性,这个 Description 是 TargetClassDescriptor 类型,在这个类中可以找到 NumFields,它表示属性数量。回到 ClassImpl 的具体信息中国,我们接着看 subscript:
这里可以看到,该方法其实是在通过偏移量 fieldOffset 在 ClassMetadata 中取属性值。其实不管是类还是其它类型(如枚举,结构体等)都有自己的 MetaData,这个 MetaData 中包含了该类型的相关信息,而 Mirror 的底层是在操作 MetaData,从而获取我们需要的信息(比如 FieldDescrition)。
三、总结
这里我们总结一下 Mirror 的步骤:
- 初始化时调了
call(OpaqueValue *passedValue, const Metadata *T, const Metadata *passedType, const F &f) -> decltype(f(nullptr))。 call中通过getKind获取对应实例的类型,并创建对应类型的ReflectionMirrorImpl。ReflectionMirrorImpl中包含了type(MetaData)、count和subscript。- 通过
ReflectionMirrorImpl的count获取属性个数。 - 通过
ReflectionMirrorImpl的subscript获取属性信息。