Mirror反射
Mirror
(反射):可以動態(tài)獲取類型工扎、成員信息徘钥,在運?時可以調(diào)??法、屬性等?為的特性肢娘。
對于?個純Swift
類來說呈础,并不?持直接像OC
那樣使用Runtime
操作。但Swift
標準庫依然提供了反射機制橱健,用來訪問成員信息而钞。
訪問成員信息
class LGTeacher {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
let mirror = Mirror(reflecting: t)
for pro in mirror.children{
print("\(pro.label):\(pro.value)")
}
//輸出以下內(nèi)容:
//Optional("age"):18
//Optional("name"):Zang
上述代碼中,
Mirror
反射的是實例對象的成員信息拘荡,傳入的參數(shù)必須是實例對象
傳入一個類對象
Mirror(reflecting: LGTeacher)
臼节,編譯報錯
傳入類對象傳入一個類的類型
Mirror(reflecting: LGTeacher.self)
,獲取不到任何成員信息
傳入類的類型
查看Mirror定義
Mirror
是一個結構體
Mirror
Mirror
的init
方法珊皿,接收一個Any
類型參數(shù)
init
Children
是一個AnyCollection
辐怕,接收一個泛型Mirror.Child
Children
Mirror.Child
是一個元組類型
Mirror.Child
JSON解析
class LGTeacher {
var age: Int = 18
var name: String = "Zang"
}
func test(_ obj : Any) -> Any {
let mirror = Mirror(reflecting: obj)
guard !mirror.children.isEmpty else {
return obj
}
var keyValue: [String: Any] = [:]
for children in mirror.children {
if let keyName = children.label {
keyValue[keyName] = test(children.value)
}
else {
print("children.label 為空")
}
}
return keyValue
}
var t = LGTeacher()
print(test(t))
//輸出以下內(nèi)容:
//["name": "Zang", "age": 18]
上述代碼中袱巨,成功的將實例對象
t
轉為字典并輸出鸵隧,但在實際開發(fā)中鹃两,這樣的代碼寫的相對丑陋,下面就來對它做一個簡單的封裝
抽取協(xié)議
我們預期在每一個屬性下都能調(diào)用
JSON解析
的方法驶兜,所以可以將它抽取成一個協(xié)議扼仲,然后提供一個默認實現(xiàn),讓類遵守協(xié)議
protocol CustomJSONMap {
func jsonMap() -> Any
}
extension CustomJSONMap{
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? CustomJSONMap {
if let keyName = children.label {
keyValue[keyName] = value.jsonMap()
}
else {
print("key是nil")
}
}
else {
print("當前-\(children.value)-沒有遵守協(xié)議")
}
}
return keyValue
}
}
class LGTeacher : CustomJSONMap {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
print(t.jsonMap())
//輸出以下內(nèi)容:
//當前-18-沒有遵守協(xié)議
//當前-Zang-沒有遵守協(xié)議
//[:]
上述代碼中抄淑,因為
age
和name
分別為Int
和String
類型屠凶,這些類并沒有遵守CustomJSONMap
協(xié)議,所以無法輸出正確結果
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
class LGTeacher: CustomJSONMap {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
print(t.jsonMap())
//輸出以下內(nèi)容:
//["age": 18, "name": "Zang"]
修改代碼肆资,增加
Int
和String
類型的extension
矗愧,讓它們也遵守CustomJSONMap
協(xié)議,打印結果符合預期
上述代碼在實際開發(fā)中用來做
JSON解析
還有很大差距迅耘。代碼本身并不完善贱枣,也無法支持復雜的嵌套類型监署,所以它只是基于Mirror
做的簡單案例颤专,用來理解Mirror
到底能做些什么。
錯誤處理
Error協(xié)議
Swift
提供Error
協(xié)議來標識當前應?程序發(fā)?錯誤的情況钠乏,不管是struct
栖秕、Class
、enum
都可以通過遵循這個協(xié)議來表示?個錯誤晓避。Error
的定義如下:
public protocol Error{
}
上述
JSON解析
案例中簇捍,有兩個未遵守協(xié)議
和key是nil
只壳,下面演示如何通過Error
協(xié)議處理這兩種異常情況
上述代碼中,將returnJSONMapError.emptyKey
和JSONMapError.notConformProtocol
進行return
暑塑,以此代替之前兩個jsonMap
方法來說,由于返回值是Any
類型事格,故此我們無法區(qū)分返回結果是解析成功的字典惕艳,還是錯誤的枚舉
對于異常情況,可以使用
throw
關鍵字將錯誤拋出驹愚,將代碼中的return
改為throw
throw上圖使用
throw
編譯報錯远搪,因為方法還沒有聲明成throws
。需要在方法返回值前面增加throws
關鍵字逢捺,告訴方法有錯誤拋出
throws方法使用
throws
關鍵字修飾谁鳍,調(diào)用該方法的代碼編譯報錯。對于有錯誤拋出的方法劫瞳,需要在調(diào)用方法前使用try
關鍵字
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
protocol CustomJSONMap {
func jsonMap() throws-> Any
}
extension CustomJSONMap{
func jsonMap() throws-> 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? CustomJSONMap {
if let keyName = children.label {
keyValue[keyName] = try value.jsonMap()
}
else {
throw JSONMapError.emptyKey
}
}
else {
throw JSONMapError.notConformProtocol
}
}
return keyValue
}
}
extension Int : CustomJSONMap{}
extension String : CustomJSONMap{}
class LGTeacher : CustomJSONMap {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
print(try t.jsonMap())
到這?一個完整的
Swift
錯誤表達?式就完成了
try關鍵字
使?
try
關鍵字是Swift
中錯誤處理最簡便的方式倘潜,相當于帥鍋。將異常向上拋出柠新,拋給上層函數(shù)窍荧。使?try
關鍵字有兩個注意點:
try?
:返回?個可選類型,這?的結果要么是成功恨憎,返回具體結果蕊退。要么是錯誤,返回nil
憔恳。這種方式我們不關心具體是哪?類錯誤瓤荔,統(tǒng)?返回nil
try!
:表示你對這段代碼有絕對的?信,這?代碼絕對不會發(fā)?錯誤
將
Int
和String
遵守CustomJSONMap
協(xié)議的代碼注釋钥组,打印try? t.jsonMap()
输硝,直接返回nil
,看不出具體錯誤原因
try?相同代碼程梦,使用
try! t.jsonMap()
測試点把,程序直接閃退
try!使用
try t.jsonMap()
,將異常拋給上層函數(shù)屿附,如果全程沒有函數(shù)處理異常郎逃,最終拋給main
函數(shù)也沒辦法處理,程序直接閃退
try
do...catch
Swift
中do...catch
是錯誤處理的另一種方式
//extension Int : CustomJSONMap{}
//extension String : CustomJSONMap{}
class LGTeacher : CustomJSONMap {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
do{
try t.jsonMap()
}catch{
print(error)
}
//輸出以下內(nèi)容:
//notConformProtocol
上述代碼中挺份,通過
do
作用域捕獲異常褒翰,通過catch
作用域處理異常,最終打印出錯誤類型notConformProtocol
LocalError協(xié)議
如果使?
Error
協(xié)議不能詳盡表達錯誤信息,可以使?LocalError
協(xié)議优训,定義如下:
public protocol LocalizedError : Error {
//錯誤的描述
/// A localized message describing what error occurred.
var errorDescription: String? { get }
//失敗的原因
/// A localized message describing the reason for the failure.
var failureReason: String? { get }
//恢復的建議
/// A localized message describing how one might recover from the failure.
var recoverySuggestion: String? { get }
//給開發(fā)者的幫助
/// A localized message providing "help" text if the user requests help.
var helpAnchor: String? { get }
}
修改
JSON解析
案例朵你,使用LocalizedError
協(xié)議,打印具體的錯誤描述
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
extension JSONMapError: LocalizedError{
var errorDescription: String?{
switch self {
case .emptyKey:
return "key為空"
case .notConformProtocol:
return "沒有遵守協(xié)議"
}
}
}
//extension Int : CustomJSONMap{}
//extension String : CustomJSONMap{}
class LGTeacher : CustomJSONMap {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
do{
try t.jsonMap()
}catch{
print(error.localizedDescription)
}
//輸出以下內(nèi)容:
//沒有遵守協(xié)議
上述代碼中揣非,為
JSONMapError
增加extension
擴展抡医,并遵守LocalizedError
協(xié)議,在catch
作用域中打印error.localizedDescription
早敬,最終輸出錯誤描述:沒有遵守協(xié)議
CustomError協(xié)議
CustomNSError
相當于OC
中的NSError
魂拦,有三個默認屬性:
public protocol CustomNSError : Error {
/// The domain of the error.
static var errorDomain: String { get }
/// The error code within the given domain.
var errorCode: Int { get }
/// The user-info dictionary.
var errorUserInfo: [String : Any] { get }
}
修改
JSON解析
案例,使用CustomNSError
協(xié)議搁嗓,打印錯誤碼
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
extension JSONMapError: CustomNSError{
var errorCode: Int{
switch self {
case .emptyKey:
return -1
case .notConformProtocol:
return -2
}
}
}
//extension Int : CustomJSONMap{}
//extension String : CustomJSONMap{}
class LGTeacher : CustomJSONMap {
var age: Int = 18
var name: String = "Zang"
}
var t = LGTeacher()
do{
try t.jsonMap()
}catch{
print("\(String(describing: (error as? CustomNSError)?.errorCode))")
}
//輸出以下內(nèi)容:
//Optional(-2)
上述代碼中芯勘,為
JSONMapError
增加extension
擴展,并遵守CustomNSError
協(xié)議腺逛,在catch
作用域中使用as?
關鍵字將error
強轉為CustomNSError
類型荷愕,并打印errorCode
,最終輸出錯誤碼:Optional(-2)
Mirror源碼解析
@_silgen_name關鍵字
@_silgen_name
關鍵字用來改變當前方法的調(diào)用方式在
swift
項目中棍矛,創(chuàng)建test.c
安疗,里面聲明lg_add
函數(shù),傳入兩個參數(shù)够委,返回參數(shù)相加的結果
lg_add在
main.swift
中荐类,定義swift_lg_add
方法,參數(shù)茁帽、返回值和test.c
的lg_add
一致玉罐,并加入@_silgen_name("lg_add")
關鍵字聲明,嘗試調(diào)用swift_lg_add
方法潘拨,發(fā)現(xiàn)最終會調(diào)用lg_add
swift_lg_add
源碼解析
打開
Mirror.swift
吊输,可以看到Mirror
是一個結構體類型
Mirror結構體有一個初始化方法,傳入一個
Any
铁追。這里判斷subject
是否符合CustomReflectable
類型季蚂,符合由customMirror
確定屬性,不符合由系統(tǒng)生成
init首先看一下由系統(tǒng)生成的
Mirror
琅束,來到internalReflecting
方法定義扭屁,首先通過_getNormalizedType
方法獲取類型,再通過_getChildCount
方法獲取屬性大小涩禀,最后通過遍歷將屬性存儲到集合中
internalReflecting搜索
_getNormalizedType
方法料滥,進入ReflectionMirror.swift
文件。該方法用到@_silgen_name
關鍵字聲明埋泵,最終會調(diào)用swift_reflectionMirror_normalizedType
方法
_getNormalizedType來到
swift_reflectionMirror_normalizedType
方法定義幔欧,有三個參數(shù),value
是Mirror
實例丽声,type
是調(diào)用時通過type(of:)
獲取動態(tài)類型礁蔗,T
是自己傳入的類型。最后調(diào)用call
方法返回impl
的type
屬性雁社,而impl
是ReflectionMirrorImpl
結構
swift_reflectionMirror_normalizedType來到
call
方法定義浴井,將方法T
和passedValue
參數(shù),傳給unwrapExistential
方法拿到type
霉撵。如果傳入的passedType
不為空磺浙,就將傳入的passedType
賦值給type
call來到
unwrapExistential
方法定義,通過getDynamicType
方法獲取當前的動態(tài)類型徒坡。通過內(nèi)部方法調(diào)用撕氧,最終發(fā)現(xiàn)獲取類型的本質(zhì)是依賴Metadata
元數(shù)據(jù)。
unwrapExistential找到
Metadata
喇完,由于類結構的Metadata
過于復雜伦泥,我們下面以結構體TargetStructMetadata
為例,它繼承于TargetValueMetadata
TargetStructMetadata來到
TargetValueMetadata
定義锦溪,它繼承于TargetMetadata
不脯,除了繼承下來的kind
屬性,還有一個ConstTargetMetadataPointer
類型的description
屬性刻诊,而description
屬性記錄的就是有關元數(shù)據(jù)的描述防楷。ConstTargetMetadataPointer
實際是一個泛型,需要重點分析的是TargetValueTypeDescription
TargetValueMetadata下面介紹元數(shù)據(jù)的描述则涯,進入
TargetValueTypeDescription
定義复局,它繼承于TargetTypeContextDescription
TargetValueTypeDescription進入
TargetTypeContextDescription
定義,里面有一個TargetRelativeDirectPointer
類型的Name
屬性粟判,用于記錄當前類型的名稱
TargetTypeContextDescription進入
TargetRelativeDirectPointer
定義肖揣,它是RelativeDirectPointer
類型的別名
TargetRelativeDirectPointer進入
RelativeDirectPointer
定義,它是一個模板類浮入,繼承于RelativeDirectPointerImpl
龙优,傳入類型T
、Nullable
為true
事秀、Offset
為int32_t
RelativeDirectPointer進入
RelativeDirectPointerImpl
定義彤断,它有一個int32_t
類型的RelativeOffset
屬性
RelativeDirectPointerImpl還包含
ValueTy
、PointerTy
易迹,其中ValueTy
代表T
的類型宰衙,PointerTy
代表T
的指針類型
ValueTy、PointerTy回到
RelativeDirectPointer
定義睹欲,獲取值ValueTy
供炼、獲取指針PointerTy
調(diào)用的都是this->get()
方法
獲取ValueTy一屋、PointerTy進入
get
方法,又回到RelativeDirectPointerImpl
定義袋哼,方法內(nèi)調(diào)用applyRelativeOffset
方法
get進入
applyRelativeOffset
方法冀墨,返回base + extendOffset
,base
是當前指針地址涛贯,extendOffset
是相對地址(偏移地址),最終相加得到偏移后的地址存儲的是類型
applyRelativeOffset回到
call
函數(shù)诽嘉,可以看到很多依賴都是基于call
函數(shù)。比如獲取Class
數(shù)據(jù)時弟翘,通過callClass
方法得到一個ClassImpl
虫腋。如果是元組類型,會得到一個TupleImpl
稀余。如果是結構體悦冀,會得到StructImpl
。
call以結構體為例睛琳,進入
StructImpl
定義雏门,繼承于ReflectionMirrorImpl
。其中isReflectable
方法掸掏,也是通過Metadata
獲取getDescriotion
茁影,找到isReflectable
判斷是否支持反射
isReflectable
count
方法,同樣通過Metadata
獲取getDescriotion
丧凤,找到NumFields
獲取屬性總數(shù)
count
subscipt
方法募闲,獲取屬性名稱和屬性值。通過getFieldOffests()[i]
得到一個屬性的偏移值愿待。通過getFiledAt
方法拿到屬性名稱浩螺,bytes
是當前value
地址,加上fieldOffset
偏移地址仍侥,就是屬性的值
subscipt進入
getFiledAt
方法要出,通過baseDesc
的Fields.get()
獲取fields
,將fields
賦值給FieldDescriptor
類型的descriptor
农渊,通過descriptor.geyFields[index]
獲取一個屬性field
患蹂,最終通過field.getFieldName()
拿到屬性值的名稱
getFiledAt
Fields
也是一個相對指針的存儲,類型是FieldDescriptor
FieldDescriptor
getFieldName
方法調(diào)用的是FieldName.get()
,在FieldRecord
類里
FieldRecord下面通過
Swift
代碼仿寫Mirror
的實現(xiàn)進行原理解析
原理解析
定義
RelativePointer
結構體砸紊,仿照RelativeDirectPointerImpl
的get
方法传于,實現(xiàn)當前指針的偏移
struct RelativePointer<T> {
var offset: Int32
mutating func get() -> UnsafeMutablePointer<T>{
let offset = self.offset
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).advanced(by: numericCast(offset)).assumingMemoryBound(to: T.self))
}
}
}
offset
:偏移地址UnsafeMutablePointer
:轉換為UnsafeMutablePointer
類型指針UnsafeRawPointer(p)
:當前this
advanced
:移動步長numericCast
:位的強轉assumingMemoryBound
:假定內(nèi)存綁定為T
的類型
定義
FieldRecordT
結構體,實現(xiàn)類似getFiledAt
方法醉顽,在連續(xù)內(nèi)存空間中沼溜,移動步長,拿到每一個FieldRecord
struct FieldRecordT<Element> {
var element: Element
mutating func element(at i: Int) -> UnsafeMutablePointer<Element> {
return withUnsafePointer(to: &self) { p in
return UnsafeMutablePointer(mutating: UnsafeRawPointer(p).assumingMemoryBound(to: Element.self).advanced(by: i))
}
}
}
定義
StructMetadata
結構體游添,相當于TargetStructMetadata
struct StructMetadata{
var kind: Int
var typeDescriptor: UnsafeMutablePointer<StructDescriptor>
}
定義
StructDescriptor
結構體系草,相當于TargetValueTypeDescription
struct StructDescriptor {
let flags: Int32
let parent: Int32
var name: RelativePointer<CChar>
var AccessFunctionPtr: RelativePointer<UnsafeRawPointer>
var Fields: RelativePointer<FieldDescriptor>
var NumFields: Int32
var FieldOffsetVectorOffset: Int32
}
定義
FieldDescriptor
結構體通熄,相當于FieldDescriptor
struct FieldDescriptor {
var MangledTypeName: RelativePointer<CChar>
var Superclass: RelativePointer<CChar>
var kind: UInt16
var fieldRecordSize: Int16
var numFields: Int32
//連續(xù)的存儲空間
var fields: FieldRecordT<FieldRecord>
}
定義
FieldRecord
結構體,相當于FieldRecord
struct FieldRecord {
var Flags: Int32
var MangledTypeName: RelativePointer<CChar>
var FieldName: RelativePointer<CChar>
}
定義
LGTeacher
結構體
struct LGTeacher{
var age = 18
var name = "Zang"
}
使用
unsafeBitCast
內(nèi)存按位轉換找都,將LGTeacher
的類型綁定到StructMetadata
let ptr = unsafeBitCast(LGTeacher.self as Any.Type, to: UnsafeMutablePointer<StructMetadata>.self)
獲取類型
let namePtr = ptr.pointee.typeDescriptor.pointee.name.get()
print("類型:\(String(cString: namePtr))")
//輸出以下結果:
//類型:LGTeacher
獲取屬性大小
let fieldDescriptorPtr = ptr.pointee.typeDescriptor.pointee.Fields.get()
print("屬性大写奖妗:\(ptr.pointee.typeDescriptor.pointee.NumFields)")
//輸出以下結果:
//屬性大小:2
遍歷出所有屬性名稱
for index in 0..<ptr.pointee.typeDescriptor.pointee.NumFields {
let recordPtr = fieldDescriptorPtr.pointee.fields.element(at: Int(index))
let valOffset=recordPtr.pointee.FieldName.get().pointee
print("屬性\(index):\(String(cString: recordPtr.pointee.FieldName.get()))")
}
//輸出以下結果:
//屬性0:age
//屬性1:name