-
Runtime
在Swift中,通过class_copyMethodList
和class_copyPropertyList
来获取Swift
类中的方法列表
和属性列表
,例:
class YYTeacher {
var age : Int = 18
func teach() {
print("teach")
}
}
let t = YYTeacher()
func test() {
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(YYTeacher.self, &methodCount)
for i in 0..
通过执行上面代码,可知:
①属性和方法前+@objc
:获取到方法列表(:teach,:age, :setAge:)和属性列表(age)
特点:无意义
,不能提供给OC类使用
②继承NSObject
,去掉@objc
:只能获取到方法列表中的:init
方法
特点:获取不到方法列表和属性列表
③继承NSObject
且属性和方法前+@objc
:获取到方法列表(:teach,:init,:age, :setAge:)和属性列表(age)
总结
如下:
- 对于纯
Swift
类来说,没有动态
特性。方法和属性前不
加任何修饰符
的情况下,该类不具备所谓的Runtime
特性。 - 对于纯
Swift
类来说,如果方法和属性前没有添加@objc
标识,是不
能通过Runtime API
来获取到方法列表
和属性列表
的。 - 在方法和属性前添加了
@objc
标识的前提下,想要在OC
中使用,还必须继承自NSObject
;如果想要方法交换,还需要加上dynamic
标识,否则只是暴露给OC并没有动态性。 - 扩展:通过查看源码可知
Swift
有默认的基类SwiftObject
,且实现了NSObject
;Swift为了和OC交互,内部保留了OC
的数据结构
(isa
和refCounts
);继承NSObject
是为了声明这是一个和OC
交互的类帮助编译器判断该类在编译过程中走哪些分支。
-
反射
反射
就是在运行时
允许动态
访问类型
、成员信息
等行为的特性,作用于对象
;Swift标准库提供了反射机制,对于OC
的Runtime
来说,有很多方式来动态获取修改代码,反射
对其不值一提
(所以OC中并无反射
)。
class YYTeacher : NSObject {
var age : Int = 18
var height = 1.80
}
let t = YYTeacher()
let mirror = Mirror(reflecting: t)
for pro in mirror.children {
print("\(pro.label!):\(pro.value)")
}
上面的代码可以获取到对象t
的成员属性
。
-
反射的使用
1. 封装一个基础类的JSON解析
方法
- 将JSON解析方法放在
协议
中,需要解析JSON的类
及其属性实现
该协议即可
protocol JSONMapProtocol {
func jsonMap() -> Any
}
- 为了方便使用,不用在需要的地方一一实现该方法,给协议一个
默认
的实现
extension JSONMapProtocol {
func jsonMap() -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else {
return self
}
var resultDict : [String : Any] = [:]
for child in mirror.children {
if let value = child.value as? JSONMapProtocol {
if let keyName = child.label {
// 递归调用,若有多层,则深度解析(所以需要解析的属性也要遵守该协议)
resultDict[keyName] = value.jsonMap()
} else {
print("No keys")
}
} else {
print("No Comply JSONMapProtocol") // 未遵守JSONMapProtocol
}
}
return resultDict
}
}
-
调用
该方法
// 属性遵守协议
extension String:JSONMapProtocol{}
extension Int:JSONMapProtocol{}
// 类遵守协议
class YYTeacher: JSONMapProtocol {
var age : Int = 18
var name = "YY"
}
class YYMathTeacher: YYTeacher {
var teachType = 1
var t = YYTeacher()
}
// 调用
let mathT = YYMathTeacher()
print(mathT.jsonMap())
得到结果["t": ["name": "YY", "age": 18], "teachType": 1]
2. 在上面JSON解析基础类的基础上,封装错误处理
定义错误
// 错误类型
enum JSONMapError:Error {
case emptyError
case noComformProtocolError
}
// 错误信息描述
extension JSONMapError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyError:
return "空错误"
case .noComformProtocolError:
return "未遵守协议"
}
}
}
- 解析方法若出错,通过
throws
抛出错误
protocol JSONMapProtocol {
func jsonMap() throws -> Any
}
extension JSONMapProtocol {
func jsonMap() throws -> Any {
let mirror = Mirror(reflecting: self)
guard !mirror.children.isEmpty else {
return self
}
var resultDict : [String : Any] = [:]
for child in mirror.children {
if let value = child.value as? JSONMapProtocol {
if let keyName = child.label {
resultDict[keyName] = try value.jsonMap()
} else {
throw JSONMapError.emptyError
}
} else {
throw JSONMapError.noComformProtocolError
}
}
return resultDict
}
}
-
调用
该方法
- 通过try、try?、try!
处理错误
try
:将错误抛给上层
函数,若上层函数未处理
,则崩溃
并跳转
至上层函数所在的文件;若要处理则通过catch代码块处理
/**
Fatal error: Error raised at top level: FirstSwiftTest.JSONMapError.noComformProtocolError: file Swift/ErrorType.swift, line 200
2021-08-08 10:45:07.768701+0800 FirstSwiftTest[6049:365271] Fatal error: Error raised at top level: FirstSwiftTest.JSONMapError.noComformProtocolError: file Swift/ErrorType.swift, line 200*/
try?
:返回一个可选
类型,不
会向上
抛出,成功
则返回具体的字典值
;错误
则统一返回nil
try!
:保证绝对不会出错则用try!,如果出错则编译失败
/**
Fatal error: 'try!' expression unexpectedly raised an error: FirstSwiftTest.JSONMapError.noComformProtocolError: file FirstSwiftTest/main.swift, line 141
2021-08-08 10:44:38.917009+0800 FirstSwiftTest[6037:364958] Fatal error: 'try!' expression unexpectedly raised an error: FirstSwiftTest.JSONMapError.noComformProtocolError: file FirstSwiftTest/main.swift, line 141*/
- 通过
do catch
代码块进行捕获并处理异常
extension String:JSONMapProtocol{}
extension Int:JSONMapProtocol{}
class YYTeacher: JSONMapProtocol {
var age : Int = 18
var name = "YY"
var height = 1.80 //新加未遵守协议的属性
}
class YYMathTeacher: YYTeacher {
var teachType = 1
var t = YYTeacher()
}
let mathT = YYMathTeacher()
var dict : Any?
do {
try dict = mathT.jsonMap()
print(dict)
} catch { // 返回的是Any,将error转为JSONMapError再调用其errorDescription属性
if let jsonMapError = error as? JSONMapError {
print(jsonMapError.errorDescription!)
} else {
print(error.localizedDescription)
}
}
得到结果为:未遵守协议
(因为这里height
属性为Double
类型,未
遵守JSONMapProtocol
)
扩展
:CustomNSError
用来桥接原来OC的NSError
中的code
、domain
、UserInfo
;如果想让我们的自定义Error
可以转成NSError
,实现CustomNSError
就可以完整的as成NSError。
extension JSONMapError : CustomNSError {
var errorUserInfo : [String : Any] {
}
var errorCode: Int {
}
static var errorDomain: String {
}
}
-
Mirror源码分析
通过查看源码可知:Mirror
实际上是一个Struct
其中有一个初始化方法init(reflecting subject: Any)
方法里面在获取对象的类型
和属性个数
等
@_silgen_name
:可以对于某些简单的代码,直接跳过桥接文件
和.h头文件
与C
代码交互。
- 在
.c
文件中声明并实现
一个方法num_add
int num_add(int a, int b) {
return a + b;
}
- 在
Swift
文件中,不
需要桥接
文件和.h
头文件时也能访问
该方法
@_silgen_name("num_add")
func swift_num_add(a : Int32, b : Int32) -> Int32
var num = swift_num_add(a: 22, b: 11)
print(num) //33
- 由于C语言中函数的符号是
_ + 函数名
,所以多个C文件中不可能
存在多个名称一样的函数,Swift可以放心使用@_silgen_name
来与之交互
- 通过源码分析
Struct结构
来了解在Swift
的反射中,怎么确定类型?
模拟Struct源码结构来分析:是如何将一个自定义的struct的Metadata
绑定到struct结构
的(反射中取类型
以及type(of:)
的原理)?
struct StructMetadata {
var kind : Int32
var desc : UnsafeMutablePointer
}
// 对metadata的描述
struct StructMetadataDesc {
var flags : Int32
var parent : Int32
var name : StructMetadataDescName
}
// type的名称
struct StructMetadataDescName {
var offset : Int32
// 其实就是取字符串存储的地址
mutating func get() -> UnsafeMutablePointer {
let offset = self.offset
return withUnsafePointer(to: &self) { ptr in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
struct YYTeacher {
var name = "YY"
var age = 12
}
var t = YYTeacher.self
// 如何将YYTeacher的metadata绑定到StructMetadata?
//unsafeBitCast:谨慎使用,有更好的方法再来替换 按位强制转换
let ptr = unsafeBitCast(YYTeacher.self as Any.Type, to: UnsafeMutablePointer.self)
let namePtr = ptr.pointee.desc.pointee.name.get()
let nameStr = String(cString: namePtr)
print(nameStr) // YYTeacher
下面用一张图帮助理解:
其实,StructMetadataDesc中的name
就是内存地址0x100
(相当于char *
),StructMetadataDescNameoffset
为0x10。
- 模拟反射中
获取属性个数
的原理:
更深入查看源码来补充StructMetadata的属性,
struct StructMetadataDesc {
var flags : Int32
var parent : Int32
var name : StructMetadataDescName
var AccessFunctionPtr : StructMetadataDescName
var Fields : StructMetadataDescName
var NumFields : Int32 // 属性的count
var FieldOffsetVectorOffset : Int32
}
print(ptr.pointee.desc.pointee.NumFields) // 2 属性的个数
源码中也是这样实现的:
- 模拟反射中
获取属性的名称和值
的原理,先查看源码实现:
模拟实现之前,先查看源码中Fields字段的类型:
FieldDescriptor
中的 getFields()
返回的是一个装FieldRecord的数组
,其实就是返回的一块连续内存空间
,存储每一个FieldRecord
;所以在模拟实现时,直接用一个字段来表示这块连续内存。
struct StructMetadataDesc {
var flags : Int32
var parent : Int32
var name : StructMetadataDescName
var AccessFunctionPtr : StructMetadataDescName
var Fields : StructMetadataDescName
var NumFields : Int32 //TargetStructDescriptor
var FieldOffsetVectorOffset : Int32
}
//class FieldDescriptor
struct StructMetadataDescFileds {
var mangledTypeName : StructMetadataDescName
var superclass : StructMetadataDescName
var kind : UInt16
var fieldRecordSize : Int16
var numFields : Int32
// ArrayRef getFields()一个数组
var fields : StructMetadataDescFiledRecord // 存储的一块连续内存空间的首地址
}
// class FieldRecord
struct StructMetadataDescFiledRecord {
var flags : Int32
var MangledTypeName : StructMetadataDescName
var FieldName : StructMetadataDescName
}
在上面获取类型及属性个数的基础上调用:
//unsafeBitCast:谨慎使用,有更好的方法再来替换 按位强制转换
let ptr = unsafeBitCast(YYTeacher.self as Any.Type, to: UnsafeMutablePointer.self)
// 存储的一块连续内存空间的首地址
var fieldsPtr = ptr.pointee.desc.pointee.Fields.get()
// 通过类型转换再移动步长的方式来获取每个属性
// 因为已经转换了类型,即已知类型,所以这里移动的步长为0、1、2...
let fieldRecordPtr = withUnsafePointer(to: &fieldsPtr.pointee.fields) {
return UnsafeMutablePointer(mutating: UnsafeRawPointer($0).assumingMemoryBound(to: StructMetadataDescFiledRecord.self).advanced(by: 0))
}
let fieldNameStr = String(cString: fieldRecordPtr.pointee.FieldName.get())
print(fieldNameStr)
优化:上面的方式,只能通过修改步长来一次获取到一个属性,如何一次性得到每一个属性?
struct StructMetadataDescFiledRecordT {
var element : Element
mutating func element(at i : Int) -> UnsafeMutablePointer {
return withUnsafePointer(to: &self) { ptr in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).assumingMemoryBound(to:Element.self).advanced(by: i))
}
}
}
StructMetadataDescFileds
中的fields
字段改为:
var fields : StructMetadataDescFiledRecordT
调用时:
let fieldCount = fieldsPtr.pointee.numFields
for i in 0..
获取属性值
,原理如下:
let fieldsCount = ptr.pointee.desc.pointee.NumFields
// 用来表示一块连续的内存空间
// start : structmetadata的首地址 + 偏移量---->连续内存空间的首地址
// count : 连续内存空间中存的个数
var bufferPtr = UnsafeBufferPointer(start: UnsafeRawPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self).advanced(by: numericCast(offset))).assumingMemoryBound(to: Int32.self), count: Int(fieldsCount))
// 结构体实例var t1 = YYTeacher() t1的内存地址
var valuePtr = withUnsafeMutablePointer(to: &t1) { $0 }
var bufferPtr1 = UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(bufferPtr[0]))).assumingMemoryBound(to: String.self)
print(bufferPtr1.pointee) //YY
var bufferPtr2 = UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(bufferPtr[1]))).assumingMemoryBound(to: Int.self)
print(bufferPtr2.pointee) //12
var bufferPtr3 = UnsafeRawPointer(UnsafeRawPointer(valuePtr).advanced(by: numericCast(bufferPtr[2]))).assumingMemoryBound(to: Double.self)
print(bufferPtr3.pointee) //1.8
type(of:)
及dump(obj)
就是基于Mirror
反射的原理。
注意:源码调试
的过程中所有dlopen
错误都是由于路径错误
(dyld、lldb.framework以及文件路径)。
- 将原来的LLDB.framework压缩为LLDB.framework.zip,再将此LLDB.framework删掉,然后将另外的LLDB.framework添加到该文件夹下。
- 将源码中的lldb修改为liblldb.dylib放到.vscode文件夹下的lib文件夹中将原来的liblldb.dylib替换
-
元类型(Metadata)、AnyClass、Self
AnyObject
:类的实例
(一定不
能为nil
),类的类型
,仅类
能实现的协议
。AnyObject!
表示为可选
类型
// 此时代表YYTeacher类的实例对象
var t : AnyObject = YYTeacher()
// 此时代表YYTeacher类的类型
var t1 : AnyObject = YYTeacher.self
// 此时也代表一个类型 NSNumber类型
var age : AnyObject = 10 as NSNumber
// 此时代表仅class才能实现的协议
protocol TestPro : AnyObject {}
//如果这里改为struct实现该协议会报错
class TestCls : TestPro {}
Any
:代表任意类型,比AnyObject
更广泛
,包括function
类型和Optional
类型
// 此时如果使用AnyObject则会报错
var arr : [Any] = [1, true, "a"]
AnyClass
:任意实例对象
的类型
,通过查看源码可知typealias AnyClass = AnyObject.Type
,即任意类的元类型
,任意类的类型都隐式遵守
这个协议。
T.self
:如果T是实例
,则返回的是它本身
;如果T是类
,则返回的是其Metadata
。
T.Type
:一种类型
,如果T是类
,T.self
是T.Type
类型(Metadata
);如果T是实例
,T.self
是实例
的类型。
//这个self就是YYTeacher.Type类型
var t = YYTeacher.self
// 此时self是YYTeacher类型
var t1 = t.self
type(of:)
:用来获取一个值运行时期的动态
类型(dynamic type
),返回的是Any.Type
// static type:编译器确定好的
// dynamic type:原本的类型
var age = 12
func test(_ value : Any) {
// age原本类型为Int,age的dynamic type为Int
let valueType = type(of: value)
print(valueType)
}
// test的参数类型为Any,age作为参数传进去,static type为Any
test(age)
特殊情况:有协议&泛型
的情况下,type(of:)无法
拿到真实
的类型,需要作as Any
处理:
protocol YYProtocol {
}
class YYTeacher: YYProtocol {
var age = 10
}
let t = YYTeacher()
let t1 : YYProtocol = YYTeacher()
func test(_ value : T) {
let valueType = type(of: value as Any)
print(valueType)
}
test(t) //无as Any时,YYTeacher;有as Any时,YYTeacher
test(t1)//无as Any时,YYProtocol;有as Any时,YYTeacher
- 扩展:
寄存器
就是用来放东西的,既可以放地址
,也可以放地址中的值
。
x8
:取的是寄存器的地址
==po withUnsafePointer(to: &t){print($0)}
[x8]
:取的是寄存器中的值
==po t