-
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..<numericCast(methodCount) {
if let method = methodList?[i]{
let methodName = method_getName(method);
print("方法列表 :\(String(describing: methodName))")
} else {
print("not found method");
}
}
var proCount : UInt32 = 0
let proList = class_copyPropertyList(YYTeacher.self, &proCount)
for i in 0..<numericCast(proCount) {
if let property = proList?[i] {
let propertyName = property_getName(property)
print("屬性成員屬性:\(String(utf8String: propertyName)!)")
} else {
print("沒有找到你要的屬性")
}
}
}
test()
通過執(zhí)行上面代碼,可知:
①屬性和方法前+@objc:獲取到方法列表(:teach,:age, :setAge:)和屬性列表(age)
特點(diǎn):無意義,不能提供給OC類使用
②繼承NSObject,去掉@objc:只能獲取到方法列表中的:init方法
特點(diǎn):獲取不到方法列表和屬性列表
③繼承NSObject且屬性和方法前+@objc:獲取到方法列表(:teach,:init,:age, :setAge:)和屬性列表(age)
總結(jié)如下:
- 對(duì)于純
Swift類來說,沒有動(dòng)態(tài)特性。方法和屬性前不加任何修飾符的情況下,該類不具備所謂的Runtime特性。 - 對(duì)于純
Swift類來說,如果方法和屬性前沒有添加@objc標(biāo)識(shí),是不能通過Runtime API來獲取到方法列表和屬性列表的。 - 在方法和屬性前添加了
@objc標(biāo)識(shí)的前提下,想要在OC中使用,還必須繼承自NSObject;如果想要方法交換,還需要加上dynamic標(biāo)識(shí),否則只是暴露給OC并沒有動(dòng)態(tài)性。 - 擴(kuò)展:通過查看源碼可知
Swift有默認(rèn)的基類SwiftObject,且實(shí)現(xiàn)了NSObject;Swift為了和OC交互,內(nèi)部保留了OC的數(shù)據(jù)結(jié)構(gòu)(isa和refCounts);繼承NSObject是為了聲明這是一個(gè)和OC交互的類幫助編譯器判斷該類在編譯過程中走哪些分支。

-
反射
反射就是在運(yùn)行時(shí)允許動(dòng)態(tài)訪問類型、成員信息等行為的特性,作用于對(duì)象;Swift標(biāo)準(zhǔn)庫提供了反射機(jī)制,對(duì)于OC的Runtime來說,有很多方式來動(dòng)態(tài)獲取修改代碼,反射對(duì)其不值一提(所以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)")
}
上面的代碼可以獲取到對(duì)象t的成員屬性。
-
反射的使用
1. 封裝一個(gè)基礎(chǔ)類的JSON解析方法
- 將JSON解析方法放在
協(xié)議中,需要解析JSON的類及其屬性實(shí)現(xiàn)該協(xié)議即可
protocol JSONMapProtocol {
func jsonMap() -> Any
}
- 為了方便使用,不用在需要的地方一一實(shí)現(xiàn)該方法,給協(xié)議一個(gè)
默認(rèn)的實(shí)現(xiàn)
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 {
// 遞歸調(diào)用,若有多層,則深度解析(所以需要解析的屬性也要遵守該協(xié)議)
resultDict[keyName] = value.jsonMap()
} else {
print("No keys")
}
} else {
print("No Comply JSONMapProtocol") // 未遵守JSONMapProtocol
}
}
return resultDict
}
}
-
調(diào)用該方法
// 屬性遵守協(xié)議
extension String:JSONMapProtocol{}
extension Int:JSONMapProtocol{}
// 類遵守協(xié)議
class YYTeacher: JSONMapProtocol {
var age : Int = 18
var name = "YY"
}
class YYMathTeacher: YYTeacher {
var teachType = 1
var t = YYTeacher()
}
// 調(diào)用
let mathT = YYMathTeacher()
print(mathT.jsonMap())
得到結(jié)果["t": ["name": "YY", "age": 18], "teachType": 1]
2. 在上面JSON解析基礎(chǔ)類的基礎(chǔ)上,封裝錯(cuò)誤處理
定義錯(cuò)誤
// 錯(cuò)誤類型
enum JSONMapError:Error {
case emptyError
case noComformProtocolError
}
// 錯(cuò)誤信息描述
extension JSONMapError: LocalizedError {
var errorDescription: String? {
switch self {
case .emptyError:
return "空錯(cuò)誤"
case .noComformProtocolError:
return "未遵守協(xié)議"
}
}
}
- 解析方法若出錯(cuò),通過
throws拋出錯(cuò)誤
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
}
}
-
調(diào)用該方法
- 通過try、try?、try!
處理錯(cuò)誤
try:將錯(cuò)誤拋給上層函數(shù),若上層函數(shù)未處理,則崩潰并跳轉(zhuǎn)至上層函數(shù)所在的文件;若要處理則通過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?:返回一個(gè)可選類型,不會(huì)向上拋出,成功則返回具體的字典值;錯(cuò)誤則統(tǒng)一返回nil
try!:保證絕對(duì)不會(huì)出錯(cuò)則用try!,如果出錯(cuò)則編譯失敗
/**
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代碼塊進(jìn)行捕獲并處理異常
extension String:JSONMapProtocol{}
extension Int:JSONMapProtocol{}
class YYTeacher: JSONMapProtocol {
var age : Int = 18
var name = "YY"
var height = 1.80 //新加未遵守協(xié)議的屬性
}
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轉(zhuǎn)為JSONMapError再調(diào)用其errorDescription屬性
if let jsonMapError = error as? JSONMapError {
print(jsonMapError.errorDescription!)
} else {
print(error.localizedDescription)
}
}
得到結(jié)果為:未遵守協(xié)議(因?yàn)檫@里height屬性為Double類型,未遵守JSONMapProtocol)
擴(kuò)展:CustomNSError用來橋接原來OC的NSError中的code、domain、UserInfo;如果想讓我們的自定義Error可以轉(zhuǎn)成NSError,實(shí)現(xiàn)CustomNSError就可以完整的as成NSError。
extension JSONMapError : CustomNSError {
var errorUserInfo : [String : Any] {
}
var errorCode: Int {
}
static var errorDomain: String {
}
}
-
Mirror源碼分析
通過查看源碼可知:Mirror實(shí)際上是一個(gè)Struct

其中有一個(gè)初始化方法init(reflecting subject: Any)

方法里面在獲取對(duì)象的類型和屬性個(gè)數(shù)等


@_silgen_name:可以對(duì)于某些簡單的代碼,直接跳過橋接文件和.h頭文件與C代碼交互。
- 在
.c文件中聲明并實(shí)現(xiàn)一個(gè)方法num_add
int num_add(int a, int b) {
return a + b;
}
- 在
Swift文件中,不需要橋接文件和.h頭文件時(shí)也能訪問該方法
@_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語言中函數(shù)的符號(hào)是
_ + 函數(shù)名,所以多個(gè)C文件中不可能存在多個(gè)名稱一樣的函數(shù),Swift可以放心使用@_silgen_name來與之交互


- 通過源碼分析
Struct結(jié)構(gòu)來了解在Swift的反射中,怎么確定類型?

模擬Struct源碼結(jié)構(gòu)來分析:是如何將一個(gè)自定義的struct的Metadata綁定到struct結(jié)構(gòu)的(反射中取類型以及type(of:)的原理)?
struct StructMetadata {
var kind : Int32
var desc : UnsafeMutablePointer<StructMetadataDesc>
}
// 對(duì)metadata的描述
struct StructMetadataDesc {
var flags : Int32
var parent : Int32
var name : StructMetadataDescName<CChar>
}
// type的名稱
struct StructMetadataDescName<T> {
var offset : Int32
// 其實(shí)就是取字符串存儲(chǔ)的地址
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 YYTeacher {
var name = "YY"
var age = 12
}
var t = YYTeacher.self
// 如何將YYTeacher的metadata綁定到StructMetadata?
//unsafeBitCast:謹(jǐn)慎使用,有更好的方法再來替換 按位強(qiáng)制轉(zhuǎn)換
let ptr = unsafeBitCast(YYTeacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
let namePtr = ptr.pointee.desc.pointee.name.get()
let nameStr = String(cString: namePtr)
print(nameStr) // YYTeacher
下面用一張圖幫助理解:

其實(shí),StructMetadataDesc中的name就是內(nèi)存地址0x100(相當(dāng)于char *),StructMetadataDescName<T>中的offset為0x10。
- 模擬反射中
獲取屬性個(gè)數(shù)的原理:
更深入查看源碼來補(bǔ)充StructMetadata的屬性,

struct StructMetadataDesc {
var flags : Int32
var parent : Int32
var name : StructMetadataDescName<CChar>
var AccessFunctionPtr : StructMetadataDescName<UnsafeRawPointer>
var Fields : StructMetadataDescName<UnsafeRawPointer>
var NumFields : Int32 // 屬性的count
var FieldOffsetVectorOffset : Int32
}
print(ptr.pointee.desc.pointee.NumFields) // 2 屬性的個(gè)數(shù)
源碼中也是這樣實(shí)現(xiàn)的:

- 模擬反射中
獲取屬性的名稱和值的原理,先查看源碼實(shí)現(xiàn):







模擬實(shí)現(xiàn)之前,先查看源碼中Fields字段的類型:



FieldDescriptor 中的 getFields()返回的是一個(gè)裝FieldRecord的數(shù)組,其實(shí)就是返回的一塊連續(xù)內(nèi)存空間,存儲(chǔ)每一個(gè)FieldRecord;所以在模擬實(shí)現(xiàn)時(shí),直接用一個(gè)字段來表示這塊連續(xù)內(nèi)存。
struct StructMetadataDesc {
var flags : Int32
var parent : Int32
var name : StructMetadataDescName<CChar>
var AccessFunctionPtr : StructMetadataDescName<UnsafeRawPointer>
var Fields : StructMetadataDescName<StructMetadataDescFileds>
var NumFields : Int32 //TargetStructDescriptor
var FieldOffsetVectorOffset : Int32
}
//class FieldDescriptor
struct StructMetadataDescFileds {
var mangledTypeName : StructMetadataDescName<CChar>
var superclass : StructMetadataDescName<CChar>
var kind : UInt16
var fieldRecordSize : Int16
var numFields : Int32
// ArrayRef<FieldRecord> getFields()一個(gè)數(shù)組
var fields : StructMetadataDescFiledRecord // 存儲(chǔ)的一塊連續(xù)內(nèi)存空間的首地址
}

// class FieldRecord
struct StructMetadataDescFiledRecord {
var flags : Int32
var MangledTypeName : StructMetadataDescName<CChar>
var FieldName : StructMetadataDescName<CChar>
}
在上面獲取類型及屬性個(gè)數(shù)的基礎(chǔ)上調(diào)用:
//unsafeBitCast:謹(jǐn)慎使用,有更好的方法再來替換 按位強(qiáng)制轉(zhuǎn)換
let ptr = unsafeBitCast(YYTeacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
// 存儲(chǔ)的一塊連續(xù)內(nèi)存空間的首地址
var fieldsPtr = ptr.pointee.desc.pointee.Fields.get()
// 通過類型轉(zhuǎn)換再移動(dòng)步長的方式來獲取每個(gè)屬性
// 因?yàn)橐呀?jīng)轉(zhuǎn)換了類型,即已知類型,所以這里移動(dòng)的步長為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)
優(yōu)化:上面的方式,只能通過修改步長來一次獲取到一個(gè)屬性,如何一次性得到每一個(gè)屬性?
struct StructMetadataDescFiledRecordT<Element> {
var element : Element
mutating func element(at i : Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) { ptr in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(ptr).assumingMemoryBound(to:Element.self).advanced(by: i))
}
}
}
StructMetadataDescFileds中的fields字段改為:
var fields : StructMetadataDescFiledRecordT<StructMetadataDescFiledRecord>
調(diào)用時(shí):
let fieldCount = fieldsPtr.pointee.numFields
for i in 0..<fieldCount {
let filedRecordPtr = fieldsPtr.pointee.fields.element(at: i)
let fieldName = String(cString: filedRecordPtr.pointee.FieldName.get())
print("屬性名\(i)" + fieldName)
}
獲取屬性值,原理如下:

let fieldsCount = ptr.pointee.desc.pointee.NumFields
// 用來表示一塊連續(xù)的內(nèi)存空間
// start : structmetadata的首地址 + 偏移量---->連續(xù)內(nèi)存空間的首地址
// count : 連續(xù)內(nèi)存空間中存的個(gè)數(shù)
var bufferPtr = UnsafeBufferPointer(start: UnsafeRawPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self).advanced(by: numericCast(offset))).assumingMemoryBound(to: Int32.self), count: Int(fieldsCount))
// 結(jié)構(gòu)體實(shí)例var t1 = YYTeacher() t1的內(nèi)存地址
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反射的原理。
注意:源碼調(diào)試的過程中所有dlopen錯(cuò)誤都是由于路徑錯(cuò)誤(dyld、lldb.framework以及文件路徑)。
- 將原來的LLDB.framework壓縮為LLDB.framework.zip,再將此LLDB.framework刪掉,然后將另外的LLDB.framework添加到該文件夾下。
- 將源碼中的lldb修改為liblldb.dylib放到.vscode文件夾下的lib文件夾中將原來的liblldb.dylib替換
-
元類型(Metadata)、AnyClass、Self
AnyObject:類的實(shí)例(一定不能為nil),類的類型,僅類能實(shí)現(xiàn)的協(xié)議。AnyObject!表示為可選類型
// 此時(shí)代表YYTeacher類的實(shí)例對(duì)象
var t : AnyObject = YYTeacher()
// 此時(shí)代表YYTeacher類的類型
var t1 : AnyObject = YYTeacher.self
// 此時(shí)也代表一個(gè)類型 NSNumber類型
var age : AnyObject = 10 as NSNumber
// 此時(shí)代表僅class才能實(shí)現(xiàn)的協(xié)議
protocol TestPro : AnyObject {}
//如果這里改為struct實(shí)現(xiàn)該協(xié)議會(huì)報(bào)錯(cuò)
class TestCls : TestPro {}
Any:代表任意類型,比AnyObject更廣泛,包括function類型和Optional類型
// 此時(shí)如果使用AnyObject則會(huì)報(bào)錯(cuò)
var arr : [Any] = [1, true, "a"]
AnyClass:任意實(shí)例對(duì)象的類型,通過查看源碼可知typealias AnyClass = AnyObject.Type,即任意類的元類型,任意類的類型都隱式遵守這個(gè)協(xié)議。
T.self:如果T是實(shí)例,則返回的是它本身;如果T是類,則返回的是其Metadata。
T.Type:一種類型,如果T是類,T.self是T.Type類型(Metadata);如果T是實(shí)例,T.self是實(shí)例的類型。
//這個(gè)self就是YYTeacher.Type類型
var t = YYTeacher.self
// 此時(shí)self是YYTeacher類型
var t1 = t.self
type(of:):用來獲取一個(gè)值運(yùn)行時(shí)期的動(dòng)態(tài)類型(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的參數(shù)類型為Any,age作為參數(shù)傳進(jìn)去,static type為Any
test(age)
特殊情況:有協(xié)議&泛型的情況下,type(of:)無法拿到真實(shí)的類型,需要作as Any處理:
protocol YYProtocol {
}
class YYTeacher: YYProtocol {
var age = 10
}
let t = YYTeacher()
let t1 : YYProtocol = YYTeacher()
func test<T>(_ value : T) {
let valueType = type(of: value as Any)
print(valueType)
}
test(t) //無as Any時(shí),YYTeacher;有as Any時(shí),YYTeacher
test(t1)//無as Any時(shí),YYProtocol;有as Any時(shí),YYTeacher
- 擴(kuò)展:
寄存器就是用來放東西的,既可以放地址,也可以放地址中的值。
x8:取的是寄存器的地址==po withUnsafePointer(to: &t){print($0)}
[x8]:取的是寄存器中的值==po t