前言
在 Objective-C 中相信每一个 iOS 开发都知道 Runtime , 现在 Swift 已经更新到 5.6 版本了,在学习 Swift 的过程中就有一个疑问,Swift 有没有 Runtime 呢?
什么是 Runtime
Objective-C 是一个动态语言,这意味着它不仅需要一个编译器,也需要一个运行时系统来动态得创建类和对象、进行消息传递和转发,而这些特性就是源于 Runtime。Swift 是静态类型的语言,元数据类型允许代码在运行时检查和操作任意值,而这些是如何实现的?通过下面的内容进行介绍:
Swift 有没有 Runtime 特性呢?
下面通过一个例子来验证下:
- 创建了一个类 Animal, 通过 OC 语言 RunTime 的写法来获取
类的属性和方法
class Animal {
var age: Int = 2
func eat(){
print("eat")
}
}
func test(){
var methodCount:UInt32 = 0
let methodlist = class_copyMethodList(Animal.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodlist?[i]{
let methodName = method_getName(method);
print("⽅法列表:\(String(describing: methodName))")
} else {
print("not found method");
}
}
var count:UInt32 = 0
let proList = class_copyPropertyList(Animal.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[i]{
let propertyName = property_getName(property);
print("属性成员属性:\(String(utf8String: propertyName)!)")
} else {
print("没有找到你要的属性");
}
}
print("调用这个方法")
}
test()
运行后发现没有获取到任何属性和方法:
- 在类的属性和方法前面加上
@objc关键字
class Animal {
@objc var age: Int = 2
@objc func eat(){
print("eat")
}
}
运行后发现获取到了属性和方法
- 假如当前类继承于
NSObject
class Animal: NSObject {
var age: Int = 18
func eat(){
print("eat")
}
}
运行结果显示只获取带了 init 方法
我们也可以查看,当前 Swift 类继承于 NSObject ,Swift 类暴露给 OC 的信息
发现确实只有一个 init 方法
- 当前 Swift 类继承于
NSObject,并且属性和方法用@objc修饰
class Animal: NSObject {
@objc var age: Int = 2
@objc func eat(){
print("eat")
}
}
可以看到运行的结果,打印和属性和方法
查看确实暴露出了属性和方法,这样才导致通过 Runtime 的 API 获取到了属性和方法。可想而知 Runtime 的 Hook、关联属性这些 Swift 也是使用不了的。
根据上面的例子,就可以得到如下的结论:
-
对于纯 Swift 类来说,⽅法和属性在不加任何修饰符的情况下。这个时候其实已经 不具备 Runtime 特性了。
-
对于纯 Swift 类,⽅法和属性添加
@objc标识的情况下,当前我们可以通过Runtime API拿到, 但是在 OC 中我们是没法进⾏调度的。 -
对于继承⾃
NSObject类来说,如果我们想要动态的获取当前的属性和⽅法,必须在其声明前添加 @objc 关键字,否则也是没有办法通过 Runtime API 获取的。 -
纯swift类没有动态性,但在⽅法、属性前添加
dynamic修饰,可获得动态性。 继承⾃NSObject的swift类,其继承⾃⽗类的⽅法具有动态性,其它⾃定义⽅法、属性想要获得动 态性,需要添加dynamic修饰。 -
若⽅法的参数、属性类型为 swift特有、⽆法映射到 objective-c 的类型(如Character、Tuple),则 此⽅法、属性⽆法添加dynamic修饰(编译器报错)
那么 Swift 类没有 Runtime 的特性,怎么去获取属性、方法呢?
Mirror 反射机制
所谓反射就是可以动态获取类型、成员信息,在运⾏时可以调⽤⽅法、属性等⾏为的特性。在使⽤OC开发时很少强调其反射概念,因为OC的Runtime要⽐其他语⾔中的反射强⼤的多。但是 Swift 是⼀⻔类型安全的语⾔,不⽀持我们像 OC 那样直接操作,它的标准库仍然提供了反射机制来让我们访问成员信息。
Swift 的反射机制是基于⼀个叫 Mirror 的结构体来实现的。为具体的实例创建⼀个 Mirror 对象,然后就可以通过它查询这个实例。
Mirror的简单使用
let animal = Animal()
//⾸先通过构造⽅法构建⼀个Mirror实例,这⾥传⼊的参数是 Any,也就意味着当前可以是类,结构体,枚举等。返回结果是一个提供该值子元素集合 `Children` 的相关信息的实例
let mirror = Mirror(reflecting: animal)
//在Child的值上用Mirror去遍历整个对象的层级视图
for pro in mirror.children{
//然后我们可以直接通过 label 输出当前的名称,value 输出当前反射的值
print("\(pro.label):\(pro.value)")
}
假如我们不使用 Mirror ,这时打印实例信息会是这样的,没有具体的属性信息的
根据上面创建的 Mirror 实例,跟进去看到对应的定义
//常量类型是这个
public let children: Mirror.Children
//Children是一个集合类型,参数是泛型
public typealias Children = AnyCollection<Mirror.Child>
//泛型参数是一个元组类型
public typealias Child = (label: String?, value: Any)
Mirror源码窥探
Swift 的运行时的底层是使用 C++ 实现的,但是在 Swift 中不能直接访问 C++ 的类,所以有一个 C 的连接层。反射的 Swift 实现在 ReflectionMirror.swift,C++ 实现在 ReflectionMirror.cpp。
⾸先我们现在源⽂件⾥⾯搜索 Mirror.Swift ,在源码中我们可以很清晰的看到 Mirror 是由结构体实现的,快速定位到初始化的⽅法
可以看到,这⾥接受⼀个 Any 类型的参数,同样的这⾥有⼀个 if case 的写法来判断当前的 subject 是否遵循了 customReflectable 协议,如果是我们就直接调⽤ customMirror , 否则就进⾏下级函数的调⽤。
这⾥有两个需要注意的点 if case 的写法,这⾥其实枚举 Case 的模式匹配,和我们的 Switch ⼀样,这⾥是只有⼀个 case 的 switch 语句。与此同时这⾥出现了⼀个 customRefletable 的协议。
customRefletable 的具体用法
⾸先我们遵循 customReflectable 协议,并实现其中的属性 customMirror,customMirror会返回⼀个 Mirror 对象。代码实现如下:
class Animal: CustomReflectable {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
var customMirror: Mirror {
let info = KeyValuePairs<String, Any>.init(dictionaryLiteral: ("age", age), ("name", name))
let mirror = Mirror.init(self, children: info, displayStyle: .**class**, ancestorRepresentation: .generated)
return mirror
}
}
var animal = Animal(age: 2, name: "pig")
运行下,打印下 animal
平常是不能打印出信息的
这个是 Mirror 结构体的初始化方法
如果没有遵循这个协议,走的是下面的方法。全局搜索 internalReflecting ,然后在 ReflectionMirror.swift 发现了 internalReflecting。
获取当前 subject 的类型
let subjectType = subjectType ?? _getNormalizedType(subject, type: type(of: subject))
全局搜索 _getNormalizedType,在 ReflectionMirror.cpp 文件发现了
getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type 最终调⽤的是ReflectionMirror.cpp 中的 C++ 代码,这⾥使⽤了⼀个编译器字段 @_silgen_name ,其实是 Swift的⼀个隐藏符号,作⽤是将某个C/C++语⾔函数直接映射为Swift函数。
call 的实现并没有想象中那么令人激动。主要是一个大型的 switch 声明和一些额外的代码去处理特殊的情况。重要的是它会用一个 ReflectionMirrorImpl 的子类实例去结束调用 f,然后会调用这个实例上的方法去让真正的工作完成。接着跟进去ReflectionMirrorImpl
ReflectionMirrorImpl 结构体的具体实现(可以看到这是⼀个抽象类,也就意味着不同类型的 反射都需要去实现 ReflectionMirrorImpl ),在下面代码我们页发现了TupleImpl、StructImpl 等,也就是我们常见类型class, struct, enum, Tuple 的具体实现。
结构体的反射
- 首先是一个帮助方法去检查结构体是否完全支持反射。结构体元数据里储存这样一个可被访问的标志位。跟上面元组的代码类似,可以知道 type 实际上是一个 StructMetadata 指针,所以我们可以自由的传入:
struct StructImpl : ReflectionMirrorImpl {
bool isReflectable() {
const auto *Struct = static_cast<const StructMetadata *>(type);
const auto &Description = Struct->getDescription();
return Description->isReflectable();
}
}
- 结构体的显示样式是 s :
char displayStyle() override {
return 's';
}
- 子元素的数量是元数据给出的字段的数量,也可能是 0(如果这个类型实际上不能支持反射的话)
intptr_t count() override {
if (!isReflectable()) {
return 0;
}
auto *Struct = static_cast<const StructMetadata *>(type);
return Struct->getDescription()->NumFields;
}
- 像之前那样,subscript 方法是比较复杂的部分。它开始也是类似的,做边界检查和查找偏移值:
intptr_t childOffset(intptr_t i) override {
auto *Struct = static_cast<const StructMetadata *>(type);
if (i < 0 || (size_t)i > Struct->getDescription()->NumFields)
swift::crash("Swift mirror subscript bounds check failure");
// Load the offset from its respective vector.
return Struct->getFieldOffsets()[i];
}
- 通过 _swift_getFieldAt获取类型信息,一但它有字段信息,一切就会进行得和元组对应部分的代码类似。填写名字和计算字段储存的指针:
const FieldType childMetadata(intptr_t i, const char **outName,
void (**outFreeFunc)(const char *)) override {
StringRef name;
FieldType fieldInfo;
std::tie(name, fieldInfo) = getFieldAt(type, i);
assert(!fieldInfo.isIndirect() && "indirect struct fields not implemented");
*outName = name.data();
*outFreeFunc = nullptr;
return fieldInfo;
}
读取 Struct 结构体源码
下面我们就尝试以 struct 为例来看⼀下 Mirror 都是如何获取到这些数据的。当然的属性数量 (可以看到的是,这⾥通过 Metadata 中的 getDescription 查询字段 NumFields )
const TargetStructDescriptor<Runtime> *getDescription() const {
return llvm::cast<TargetStructDescriptor<Runtime>>(this->Description);
}
swift_getField 的帮助函数可以查找给定类型相应的字段描述符
可以看到是这⾥通篇都是通过 Metadata ,getDescription() ,FieldDescrition
这⼏个东⻄来去实现的,⼀个是当前类型的元数据,⼀个是当前类型的描述,⼀个是对当前类型属性的 描述。所以看到这⾥我们能够明⽩ Mirror 是如何⼯作的。 同时我们在前⾯的探索过程中也探索了类,结构体和枚举,接下来我们尝试能不能⾃⼰利⽤ Metadata 来还原获取当前类型的各种属性。
Struct 的代码实现
TargetStructMetadata结构
打开 Swift 源码,定位到 Metadata.h文件,搜索 TargetStructMetadata,然后一步步的去跟进定位结构,这里有整理出来的一张流程图,很清晰的看出各种结构:
把上面的流程图转换成伪代码:
struct StructMetaData{
var kind : Int32
//从TargetValueMetadata继承
var typeDescriptor : UnsafeMutablePointer<StructDescriptor>
}
struct StructDescriptor {
//Flags、Parent从TargetContextDescriptor继承
//描述上下文的标志,包括其Kind和Version
let flags: Int32
//父上下文,如果为顶级上下文,则为null
let parent: Int32
//结构体的名称
var name: RelativePointer<CChar>
var AccessFunctionPtr: RelativePointer<UnsafeRawPointer>
//记录属性内容
var Fields: RelativePointer<FieldDescriptor>
//结构中存储的属性数
var NumFields: Int32
//属性在元数据中字段偏移向量的偏移量
var FieldOffsetVectorOffset: Int32
}
//记录结构体内所有属性的结构
struct FieldDescriptor {
var MangledTypeName: RelativePointer<CChar>
var Superclass: RelativePointer<CChar>
var kind: UInt16
var fieldRecordSize: Int16
var numFields: Int32
//连续存储空间 (有几个数据,就会在后面添加几个记录,通过内存平移读取)
var fields: FieldRecord
}
struct FieldRecordT<Element> {
var element: Element
mutating func element(at i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
//每个属性的内容
struct FieldRecord {
var Flags: Int32
var MangledTypeName: RelativePointer<CChar>
var FieldName: RelativePointer<CChar>
}
// 相对位移指针,相当于RelativeIndirectPointer和RelativeDirectPointer
struct RelativePointer<T> {
var offset: Int32
// 偏移offset位置,获取内容指针
mutating func get() -> UnsafeMutablePointer<T>{
let offset = self.offset
//withUnsafePointer获取指针
// UnsafeMutablePointer 返回T类型对象的指针
// UnsafeRawPointer将p指针转换为未知类型
// advanced进行内存偏移
// numericCast将offset转换为偏移单位数
// assumingMemoryBound绑定指针为T类型
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
解析自定义的 Struct
我们可以根据还原出来的代码,来解析自定义的 Struct
struct Animal {
var age: Int = 2
var name: String = "pig"
}
//UnsafeMutablePointer<StructMetaData>.self 获取当前 struct 的 Metadata
//利⽤强转函数 unsafeBitCast 按位转换内存指针
//把 Animal Metadata 转成还原出来的 StructMetaData
let ptr = unsafeBitCast(Animal.self as Any.Type, to: UnsafeMutablePointer<StructMetaData>.self)
//通过属性内存的访问,获取到 name 字段的内存地址
let namePtr = ptr.pointee.typeDescriptor.pointee.name.get()
//经过 String 函数输出,得到我们结构体的名字
print("current class name: \(String(cString: namePtr))")
//获取属性的个数
let numFields = ptr.pointee.typeDescriptor.pointee.NumFields
print("当前类属性的个数: \(numFields)")
//获取属性的描述信息
let fieldDespritor = ptr.pointee.typeDescriptor.pointee.Fields.get()
//遍历属性
for i in 0..<numFields {
//获取属性的信息
let record = withUnsafePointer(to: &fieldDespritor.pointee.fields){
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: FieldRecord.self).advanced(by: numericCast(i)))
}
//读取属性的名称
let recordNameStr = String(cString: record.pointee.FieldName.get())
//读取属性的类型
let manNameStr = String(cString: record.pointee.MangledTypeName.get())
print("类型名称:\(recordNameStr)---- 类型:\(manNameStr)")
}
运行就可以看到确实获取到了 Struct 的名字,属性的名称以及属性的类型
这里的 Si 就是 Int 类型,SS 就代表 String 类型。这样就完成了 Struct 的源码还原,其实 Enum 和结构体的 Metadata 数据结构差不多,而 Class 的实现起来麻烦一些。
总结:
结合起来整体分为这几个步骤:
-
获取当前 struct 的 Metadata,然后转成 StructMetaData
-
通过属性内存的访问,获取结构体的名称
-
获取属性的个数
-
获取属性的描述信息
-
读取属性的信息
非常感谢这位同学整理的流程图:www.jianshu.com/p/30dc12515…
参考文章: swift.gg/2018/11/15/…