- 我们知道
Swift
是一门静态语言,但让类继承NSObject
,然后在属性和方法前面添加@objc
,可以让它动态的处理一些事情。其实Swift
本身也可以动态的获取类的成员及类型,这种方式就叫做反射(Mirror)
Mirror的Api简介
-
Mirror
是一个结构体,它的Api
如下:struct Mirror { public enum DisplayStyle : Sendable { case `struct` case `class` case `enum` case tuple case optional case collection case dictionary case set public static func == (a: Mirror.DisplayStyle, b: Mirror.DisplayStyle) -> Bool public func hash(into hasher: inout Hasher) public var hashValue: Int { get } } // 初始化 public init(reflecting subject: Any) public enum AncestorRepresentation { case generated case customized(() -> Mirror) case suppressed } public init<Subject, C>(_ subject: Subject, children: C, displayStyle: Mirror.DisplayStyle? = nil, ancestorRepresentation: Mirror.AncestorRepresentation = .generated) where C : Collection, C.Element == Mirror.Child public init<Subject, C>(_ subject: Subject, unlabeledChildren: C, displayStyle: Mirror.DisplayStyle? = nil, ancestorRepresentation: Mirror.AncestorRepresentation = .generated) where C : Collection public init<Subject>(_ subject: Subject, children: KeyValuePairs<String, Any>, displayStyle: Mirror.DisplayStyle? = nil, ancestorRepresentation: Mirror.AncestorRepresentation = .generated) public let displayStyle: Mirror.DisplayStyle? // 反射主体的类型 public let subjectType: Any.Type public typealias Child = (label: String?, value: Any) public typealias Children = AnyCollection<Mirror.Child> public let children: Mirror.Children // 父mirror,没有则为nil public var superclassMirror: Mirror? { get } public func descendant(_ first: MirrorPath, _ rest: MirrorPath...) -> Any? }
Api
主要分为三类:-
4个
初始化方法:
init(reflecting subject: Any)
是最简单的初始化方法,subject
是实例对象
-
- 成员变量:
displayStyle
: 反射主题在枚举DisplayStyle
中对应的类型children
: 是一个集合,结合的元素是元组,元祖由可选变迁label
,和值value
构成。subjectType
: 反射主体的类型
-
- 方法:
descendant
- 方法:
-
Mirror简单使用
-
下面来通过案例介绍
mirror
的一些简单用法struct WSTeacher { var name: String = "wushuang" var age: Int = 18 } let t = WSTeacher() let mirror = Mirror(reflecting: t) for obj in mirror.children { print("\(obj.label!) : \(obj.value)") }
- 此案例可以将
struct
的成员变量以及其在实例中对应的值都打印出来:
- 此案例可以将
-
既然
mirror
可以根据实例对象,获取给对象类型中的成员变量以及对应的值,我们可以使用mirror
来将对象转成JSON
对象转JSON
-
具体代码如下:
struct WSTeacher { var name: String = "wushuang" var age: Int = 18 } let t = WSTeacher() func test(_ obj: Any) -> Any { let mirror = Mirror(reflecting: obj) guard !mirror.children.isEmpty else { return obj } var keyValue: [String: Any] = [:] for child in mirror.children { if let key = child.label { keyValue[key] = test(child.value) } else { print(" key is not exist ") } } return keyValue }
-
代码主要是根据对象创建
mirror
对象,然后遍历children
中的child
,将child
中的label
为key
,value
为值存入Dictionary
中,如果value
也是一个对象,则也走一遍上述流程。最终得到JSON
数据如下:
-
-
还可以将遍历的过程写成协议,具体过程如下:
protocol WSJSONMap { func jsonMap() -> Any } extension WSJSONMap { func jsonMap() -> Any { let mirror = Mirror(reflecting: self) guard !mirror.children.isEmpty else { return self } var keyValue: [String: Any] = [:] for children in mirror.children { if let value = children.value as? WSJSONMap { if let key = children.label { keyValue[key] = value.jsonMap() } else { print(" key is not exist ") } } else { print(" value is not obey WSJSONMap ") } } return keyValue } } struct WSPerson: WSJSONMap { var name: String = "wushuang" var age: Int = 18 } extension String : WSJSONMap { } extension Int : WSJSONMap { } var ws = WSPerson()
- 只需要结构体继承协议,然后结构体中成员变量的类型
遵守协议
,就可以调用协议方法,如果成员变量的类型没有遵守协议
,则不会写入最终的json
中:
- 如果
key
不存在或者value
没有遵守协议,就会打印出来错误信息,但我们理想的效果是通过结果直接返回,此时就需要用到错误处理Error
- 只需要结构体继承协议,然后结构体中成员变量的类型
抛出错误
-
首先定义一个
Error
类型的枚举:enum WSJSONMapError: Error { case emptyKey case nonComplianceProtocol }
-
然后将协议方法中的错误替换成对应的
Error
类型,如果直接return
错误类型,则会混淆是Json
返回还是错误返回,所以此时需要使用throw
将错误抛出
throw和throws
-
throw
:将错误抛出 -
throws
:返回值中可能有错误抛出,则返回值需要用throws
修饰 -
修改的具体代码如下:
- 由于协议方法返回值可能存在错误,所以调用时需要加上
try
,也就是错误处理
- 由于协议方法返回值可能存在错误,所以调用时需要加上
错误处理
- 有以下几种错误处理的方式:
try&try?&try!
-
try
:只管调用,即使有错误会向抛给上层函数,可以理解为甩锅
-
try?
:返回的是可选类型
,调用的结果如下:没有错误
: 返回json
有错误
:返回nil
-
try!
:认为代码不会有错误
这三种方式处理错误,依然会使程序崩溃,所以需要结合do-catch
使用
do-catch
-
do-catch
结合try
遇到错误类型时会在catch
打印出具体的错误: -
do-catch
结合try?
遇到错误类型时会在do
打印出nil
-
do-catch
结合try!
遇到错误类型时,依然会崩溃
localizedError
-
如果想要打印更多的错误信息,则可以使用系统的
localizedError
,它的Api
如下:public protocol LocalizedError : Error { var errorDescription: String? { get } // 错误描述 var failureReason: String? { get } // 失败原因 var recoverySuggestion: String? { get } // 建议 var helpAnchor: String? { get } // 帮助 }
-
此时我们选择使用
错误描述
,代码如下:- 在打印错误时输出
error.localizedDescription
就可以打印出定义的内容:
- 在打印错误时输出
CustomNSError
-
CustomNSError
相当于是NSError
,它的Api
如下:public protocol CustomNSError : Error { static var errorDomain: String { get } var errorCode: Int { get } var errorUserInfo: [String : Any] { get } }
- 我们可以使自定义的协议继承它,然后根据枚举设置
code
:
- 在
do-catch
中的错误code
打印如下:
- 我们可以使自定义的协议继承它,然后根据枚举设置
通过对
Mirror
的案例使用,会产生以下疑问:
1.Mirror
是怎么获取实例变量的名字以及它的值?
2.Swift
是静态语言,系统做了什么使Swift
具有反射特性?
带着疑问,我们去 Swift源码 进行分析
Mirror源码解析
-
在源码中,有三个文件与
Mirror
相关的,他们的关系如下:-
Mirror.swift
:暴露给Swift
上层使用的Api
,以及一些方法的实现
-
ReflectionMirror.swift
:Mirror.swift
中的函数调用的是ReflectionMirror.swift
中Mirror
的拓展函数,而Mirror
的拓展函数是@_silgen_name
包装函数后的匿名函数
-
ReflectionMirror.cpp
:@_silgen_name
的包装函数都在ReflectionMirror.cpp
中
-
-
@_silgen_name
作用分析,代码如下:// test.c int addNum(int a, int b) { return a + b; } // main.swift @_silgen_name("addNum") func wushuangAdd(a: Int, b: Int) ->Int let result = wushuangAdd(a: 20, b: 10) print(result)
-
在
Swift
工程中创建test.c
的c
文件,不创建桥接文件,然后在c
文件中定义addNum
方法,接着在main.swift
文件中使用@_silgen_name
把字符串addNum
包装成swift
方法wushuangAdd
-
运行结果如下:
-
结果成功的调用了
c
中定义的函数,使用桥接文件
,然后c
文件带.h
的情况下也是可以调用的 -
根据结果可以得出以下结论
结论:
Swift工程
可以在没有c.h
,没有桥接文件
的条件下,使用@_silgen_name
关键字将c
中的函数暴露给swift
使用 -
-
下面从最基本的
Api
开始分析,首先进入Mirror.swift
文件,可以看到Mirror
是一个结构体,与上面提到的Api
基本一致,那我们直接来看初始化方法:public init(reflecting subject: Any) { if case let customized as CustomReflectable = subject { self = customized.customMirror } else { self = Mirror(internalReflecting: subject) } }
- 如果
subject
的类型属于CustomReflectable
,则生成的mirror
由它的customMirror
属性决定;反之则由系统创建
- 如果
-
然后进入
ReflectionMirror.swift
文件查看internalReflecting
的函数实现:- 函数主要有以下
5
个步骤:-
- 获取实际的类型
-
- 获取成员变量的数量
-
- 将成员变量的
name
和value
组装成child
,并放入集合Children
中
- 将成员变量的
-
- 设置父
mirror
- 设置父
-
- 获取对象的
displayStyle
- 获取对象的
-
- 函数主要有以下
下面我们主要去分析前面三个步骤:
一、 获取type
-
获取
type
的核心方法是调用_getNormalizedType
,而它是swift_reflectionMirror_normalizedType
函数的包装函数:@_silgen_name("swift_reflectionMirror_normalizedType") internal func _getNormalizedType<T>(_: T, type: Any.Type) -> Any.Type
- 所以核心方法实际是
ReflectionMirror.cpp
中的swift_reflectionMirror_normalizedType
函数:
SWIFT_CC(swift) SWIFT_RUNTIME_STDLIB_API const Metadata *swift_reflectionMirror_normalizedType(OpaqueValue *value, const Metadata *type, const Metadata *T) { return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->type; }); }
- 该方法返回的是
call
函数,参数value
是mirror
实例,type
是对象的类型,T
是传入的对象,而类型最终的返回值是impl->type
- 所以核心方法实际是
二、获取count
-
获取
count
的方法_getChildCount
,实际是调用ReflectionMirror.cpp
中的swift_reflectionMirror_count
方法:@_silgen_name("swift_reflectionMirror_count") internal func _getChildCount<T>(_: T, type: Any.Type) -> Int
- 在
swift_reflectionMirror_count
的方法中,它调用的也是call
方法,然后通过impl->count
获得count
:
intptr_t swift_reflectionMirror_count(OpaqueValue *value, const Metadata *type, const Metadata *T) { return call(value, T, type, [](ReflectionMirrorImpl *impl) { return impl->count(); }); }
- 在
三、获取成员变量
-
获得成员变量和上面一样,最终也是调用包装方法:
@_silgen_name("swift_reflectionMirror_subscript") internal func _getChild<T>( of: T, type: Any.Type, index: Int, outName: UnsafeMutablePointer<UnsafePointer<CChar>?>, outFreeFunc: UnsafeMutablePointer<NameFreeFunc?> ) -> Any
swift_reflectionMirror_subscript
方法最终调用的也是call
:
AnyReturn swift_reflectionMirror_subscript(OpaqueValue *value, const Metadata *type, intptr_t index, const char **outName, void (**outFreeFunc)(const char *), const Metadata *T) { return call(value, T, type, [&](ReflectionMirrorImpl *impl) { return impl->subscript(index, outName, outFreeFunc); }); }
- 最后通过
impl
调用subscript
方法来获取成员变量
通过上面的简单分析,我们得到他们的获取都与
ReflectionMirrorImpl
有关,下面我们去看看它到底是个什么
ReflectionMirrorImpl(反射基类)
-
ReflectionMirrorImpl
是反射基类,它是一个结构体:- 结构体里有
type
,count
和subscript
等属性和方法
- 结构体里有
反射的种类
-
在文件中可以看到
ReflectionMirrorImpl
有几下几个子类:-
TupleImpl
:元祖类型的反射
-
StructImpl
:结构体类型的反射
-
EnumImpl
: 枚举的反射
-
ClassImpl
: 类的反射
-
ObjCClassImpl
:OCClass
的反射
-
MetatypeImpl
:元数据
-
OpaqueImpl
:不透明类型
-
了解了
ReflectionMirrorImpl
后,我再去分析上面三个步骤的详细过程
获取type
-
首先进入
call
函数:- 在
call
中,先创建type
和value
,然后通过unwrapExistential
对他们进行赋值,再赋值给impl
的type
- 在
unwrapExistential
函数中,又调用了getDynamicType
来动态获取type
:
- 实际上获取
type
的核心步骤都在元数据中进行的,下面我们以结构体为例子
去分析
- 在
-
先去看看
StructImpl
:-
StructImpl
是一个结构体,在里面可以看到获取count
,和获取成员变量的方法,但并没有看到type
的获取,而获取count
和isReflectable
,是通过getDescription
来得到,也许type
的获取和getDescription
有关。getDescription
是StructMetadata
的实例调用的,StructMetadata
是TargetStructMetadata
的别名 -
TargetStructMetadata
里getDescription
最终调用的是this->Description
,而它本身没有这个成员,说明它可能在父类TargetValueMetadata
中,
-
-
进入
TargetValueMetadata
中,我们发现了Description
:Description
是对元数据的描述,起决定作用的是传入的TargetValueTypeDescriptor
,
-
在进入
TargetValueTypeDescriptor
->TargetTypeContextDescriptor
,发现里面有个name
,根据注释得知它就是type
的名字:name
是TargetRelativeDirectPointer
指针,是传入的const char
类型
-
通过进入
TargetRelativeDirectPointer
->RelativeDirectPointer
:RelativeDirectPointer
里提供了父类的指针
和值
的获取方法,他们都是调用的是父类的get
方法得到的
-
再进入父类
RelativeDirectPointerImpl
,它有一个成员变量RelativeOffset
,ValueTy
是传入的值,PointerTy
是指针:get
方法核心是调用applyRelativeOffset
:
applyRelativeOffset
的核心代码如下:
applyRelativeOffset
主要是获取该内存的base
地址,然后offset
是结构体相对于base
的偏移量,二者相加就得到了它的内存地址
-
通过分析,我们知道可以得到
name
,再调用get
方法来获取type
。但提前要搞清楚内存的结构TargetTypeContextDescriptor
继承自TargetContextDescriptor
,TargetContextDescriptor
,有两个成员Flags
和Parent
,都是Int32
类型
-
下面我们用伪代码来验证
name
是不是我们要的type
代码验证
-
通过上面分析,可以写出相应的数据类型:
struct WSMetadata { var kind: Int var desc: UnsafeMutablePointer<WSMetadataDesc> } struct WSMetadataDesc { var flags: Int32 var parent: Int32 var name: WSRelativeDirectPointer<CChar> } struct WSRelativeDirectPointer<T> { var offSet: Int32 mutating func get() -> UnsafeMutablePointer<T> { let offset = self.offSet return withUnsafePointer(to: &self) { ptr in return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self)) } } }
-
然后通过
unsafeBitCast
函数进行按位强转,将WSPerson
结构体的内存强转到WSMetadata
上struct WSPerson { var age: Int = 18 var name: String = "wushuang" } let ptr = unsafeBitCast(WSPerson.self as Any.Type, to: UnsafeMutablePointer<WSMetadata>.self) let namePtr = ptr.pointee.desc.pointee.name.get() print(String(cString: namePtr))
- 打印结果如下:
count
-
在上面的
StructImpl
结构中,我们知道count
是通过Description
中的NumFields
来获取的,而getDescription
返回的是TargetStructDescriptor
类型,进入类中,于是发现成员NumFields
和FieldOffsetVectorOffset
-
然后再找到
TargetTypeContextDescriptor
,发现name
还有两个成员变量AccessFunctionPtr
和Fields
:- 这两个成员变量和
name
的模版函数是一样的,只是传入的类型不一样
- 这两个成员变量和
代码验证
-
搞清楚结构后,再来补充
WSMetadataDesc
的成员变量,由于AccessFunctionPtr
和Fields
传入的类型不确定,可以写成原生指针
,具体代码如下:struct WSMetadataDesc { var flags: Int32 var parent: Int32 var name: WSRelativeDirectPointer<CChar> var AccessFunctionPtr: WSRelativeDirectPointer<UnsafeRawPointer> var Fields: WSRelativeDirectPointer<UnsafeRawPointer> var NumFields: Int32 var FieldOffsetVectorOffset: Int32 }
- 然后打印
NumFields
:
- 改变
WSPerson
成员数量再打印:
- 结果与
WSPerson
的成员数量完全一致
- 然后打印
获取实例变量的名字和值
获取实例变量主要跟StructImpl
中的subscript
方法有关
-
从代码中可知,获取实例变量名字调用
getFieldAt
方法:- 这里主要是先从
desc->Fields
中获取fields
数组,然后根据下标获取其中一个field
,最后再从field
中获取name
- 这里主要是先从
-
在上面分析中我们知道
Field
是模版函数TargetRelativeDirectPointer
传入类型FieldDescriptor
构造来的,下面来看看FieldDescriptor
:FieldDescriptor
是一个类,有5个成员变量
:MangledTypeName
和Superclass
是RelativeDirectPointer
模版函数构造,且传入的都是const char
类型Kind
和FieldRecordSize
是uint16_t
类型NumFields
是uint32_t
类型
- 在
getFields
方法中,可以看出它是一片连续的内存,存储FieldRecord
类型的数据
- 再查看
FieldRecord
的数据类型:
- 它有三个成员,而
FieldName
就是我们要找的成员变量的名字
-
下面将
WSMetadataDesc
的结构体继续改写:struct WSMetadata { var kind: Int var desc: UnsafeMutablePointer<WSMetadataDesc> } struct WSMetadataDesc { var flags: Int32 var parent: Int32 var name: WSRelativeDirectPointer<CChar> var AccessFunctionPtr: WSRelativeDirectPointer<UnsafeRawPointer> var Fields: WSRelativeDirectPointer<WSFieldDescriptor> var NumFields: Int32 var FieldOffsetVectorOffset: Int32 } struct WSFieldDescriptor { var MangledTypeName: WSRelativeDirectPointer<CChar> var Superclass: WSRelativeDirectPointer<CChar> var Kind: Int16 var FieldRecordSize: Int16 var NumFields: Int32 var fields: WSFieldRecord // 一块连续的内存,存储的都是 WSFieldRecord 类型数据 } struct WSFieldRecord { var Flags: Int32 var MangledTypeName: WSRelativeDirectPointer<CChar> var FieldName: WSRelativeDirectPointer<CChar> } struct WSRelativeDirectPointer<T> { var offSet: Int32 mutating func get() -> UnsafeMutablePointer<T> { let offset = self.offSet return withUnsafePointer(to: &self) { ptr in return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self)) } } }
-
调用方法如下:
struct WSPerson { var age: Int = 18 var name: String = "wushuang" var height: CGFloat = 185.0 var hobby: String = "basketball" var nickName: String = "ws" } // 指向 WSMetadata的指针 let ptr = unsafeBitCast(WSPerson.self as Any.Type, to: UnsafeMutablePointer<WSMetadata>.self) // 获取指向 fields 的首地址 var fieldsDesPtr = ptr.pointee.desc.pointee.Fields.get() let recordPtr = withUnsafePointer(to: &fieldsDesPtr.pointee.fields) { ptr in return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).assumingMemoryBound(to: WSFieldRecord.self)) } // 获取成员变量的名字的方式 let age = recordPtr.pointee.FieldName.get() print(String(cString: age)) let name = (recordPtr + 1).pointee.FieldName.get() print(String(cString: name)) let height = recordPtr[2].FieldName.get() print(String(cString: height)) let hobby = recordPtr.advanced(by: 3).pointee.FieldName.get() print(String(cString: hobby))
- 打印成员变量主要有三个步骤:
-
- 获取指向
WSMetadata
的指针
- 获取指向
-
- 获取指向连续内存
fields
的首地址
- 获取指向连续内存
-
- 根据
内存平移
获取每个成员变量的field
,再获取对应的FieldName
- 根据
-
- 打印成员变量主要有三个步骤:
总结
mirror
在反射时,通过反射对象的实例来初始化一个struct
类型数据,这个数据的内存结构与反射对象对应的类是一致的,然后通过访问对应内存的位置来读取相应的type
,count
,以及成员变量信息
。