Mirror是Swift中的反射機(jī)制凄敢,反射就是可以動(dòng)態(tài)的獲取類(lèi)型以及成員信息,同時(shí)也可以在運(yùn)行時(shí)動(dòng)態(tài)的調(diào)用方法和屬性等短蜕。
1. Mirror 簡(jiǎn)介
Mirror是Swift中的反射機(jī)制的實(shí)現(xiàn)癣朗,它的本質(zhì)是一個(gè)結(jié)構(gòu)體。
創(chuàng)建 Mirror 最簡(jiǎn)單的方式就是使用 reflecting 構(gòu)造器:
public init(reflecting subject: Any)
正如你所見(jiàn)捏萍,對(duì)象的類(lèi)型是 Any
。這是 Swift 中最通用的類(lèi)型空闲。Swift 中的任何東西至少都是 Any
類(lèi)型的令杈。這樣一來(lái) mirror
就可以兼容 struct
, class
, enum
, Tuple
, Array
, Dictionary
, set
等。
下面列舉了 Mirror 可用的屬性 / 方法:
- let children: Children:對(duì)象的子節(jié)點(diǎn)碴倾。
- displayStyle: Mirror.DisplayStyle?:對(duì)象的展示類(lèi)型
- let subjectType: Any.Type:對(duì)象的類(lèi)型
- func superclassMirror() -> Mirror?:對(duì)象父類(lèi)的 mirror
displayStyle
public enum DisplayStyle {
case Struct
case Class
case Enum
case Tuple
case Optional
case Collection
case Dictionary
case Set
}
它會(huì)返回 DisplayStyle enum 的其中一種情況逗噩。如果你想要對(duì)某種不支持的類(lèi)型進(jìn)行反射,你會(huì)得到一個(gè)空的 Optional 值跌榔。
例如:
正如之前我們知道的异雁,反射只要求對(duì)象是 Any 類(lèi)型,而且Swift 標(biāo)準(zhǔn)庫(kù)中還有很多類(lèi)型為 Any 的東西沒(méi)有被列舉在上面的 DisplayStyle enum 中僧须。如果試圖反射它們中間的某一個(gè)又會(huì)發(fā)生什么呢纲刀?比如 closure。
let closure = { (a: Int) -> Int in return a * 2 }
let aMirror = Mirror(reflecting: closure)
print("\(aMirror)----------\(aMirror.displayStyle)")
//Mirror for (Int) -> Int----------nil
children
這會(huì)返回一個(gè)包含了對(duì)象所有的子節(jié)點(diǎn)的 AnyForwardCollection<Child>担平。這些子節(jié)點(diǎn)不單單限于 Array 或者 Dictionary 中的條目示绊。諸如 struct 或者 class 中所有的屬性也是由 AnyForwardCollection<Child> 這個(gè)屬性返回的子節(jié)點(diǎn)。AnyForwardCollection 協(xié)議意味著這是一個(gè)支持遍歷的 Collection 類(lèi)型暂论。
public class Store {
let storesToDisk: Bool = true
}
public class BookmarkStore: Store {
let itemCount: Int = 10
}
public struct Bookmark {
enum Group {
case Tech
case News
}
private let store = {
return BookmarkStore()
}()
let title: String?
let url: NSURL
let keywords: [String]
let group: Group
}
let aBookmark = Bookmark(title: "Appventure", url: NSURL(string: "appventure.me")!, keywords: ["Swift", "iOS", "OSX"], group: .Tech)
let aMirror = Mirror(reflecting: aBookmark)
for case let (label?, value) in aMirror.children {
print (label, value)
}
//輸出:
//store TestDemo3.ViewController.BookmarkStore
//title Optional("Appventure")
//url appventure.me
//keywords ["Swift", "iOS", "OSX"]
//group Tech
SubjectType
這是對(duì)象的類(lèi)型:
print(aMirror.subjectType)
//輸出 : Bookmark
print(Mirror(reflecting: 5).subjectType)
//輸出 : Int
print(Mirror(reflecting: "test").subjectType)
//輸出 : String
print(Mirror(reflecting: NSNull()).subjectType)
//輸出 : NSNull
SuperclassMirror
這是我們對(duì)象父類(lèi)的 mirror面褐。如果這個(gè)對(duì)象不是一個(gè)類(lèi),它會(huì)是一個(gè)空的 Optional 值取胎。如果對(duì)象的類(lèi)型是基于類(lèi)的展哭,你會(huì)得到一個(gè)新的 Mirror:
// 試試 struct
print(Mirror(reflecting: aBookmark).superclassMirror)
// 輸出: nil
// 試試 class
print(Mirror(reflecting: aBookmark.store).superclassMirror)
// 輸出: Optional(Mirror for Store)
2.反射 Mirror 使用
1.Mirror 轉(zhuǎn)換對(duì)象為字典
- 定義如下對(duì)象:
struct Person {
var name: String = "YDW"
var isMale: Bool = true
var birthday: Date = Date()
}
class Animal: NSObject {
private var eat: String = "吃"
var age: Int = 0
var optionValue: String?
}
class Cat: Animal {
var like: [String] = ["mouse", "fish"]
var master = Person()
}
- 遍歷出字典:
func mapDic(mirror: Mirror) -> [String: Any] {
var dic: [String: Any] = [:]
for child in mirror.children {
// 如果沒(méi)有l(wèi)abel就會(huì)被拋棄
if let label = child.label {
let propertyMirror = Mirror(reflecting: child.value)
dic[label] = child.value
}
}
// 添加父類(lèi)屬性
if let superMirror = mirror.superclassMirror {
let superDic = mapDic(mirror: superMirror)
for p in superDic {
dic[p.key] = p.value
}
}
return dic
}
- 使用 Mirror 轉(zhuǎn)換對(duì)象并打印結(jié)果, 可以看到可以打印出私有屬性:
// Mirror使用
let objc = Cat()
let mirror = Mirror(reflecting: objc)
let mirrorDic = mapDic(mirror: mirror)
print(mirrorDic)
// 打印結(jié)果
["master": TestDemo3.ViewController.Person(name: "YDW", isMale: true, birthday: 2022-07-15 03:51:59 +0000), "eat": "吃", "optionValue": nil, "age": 0, "like": ["mouse", "fish"]]
- 在實(shí)際運(yùn)用中扼菠,可以將應(yīng)用于元組參數(shù)傳遞(比如網(wǎng)絡(luò)請(qǐng)求傳參摄杂,傳入元組,網(wǎng)絡(luò)請(qǐng)求時(shí)轉(zhuǎn)換為字典)循榆,優(yōu)點(diǎn):外部使用知道具體傳入什么參數(shù)析恢,參數(shù)更改不影響方法錯(cuò)誤。
// 外部參數(shù)定義
var params = (title: "name", comment: "Mirror")
// 網(wǎng)絡(luò)層統(tǒng)一轉(zhuǎn)換為字典秧饮,進(jìn)行網(wǎng)路請(qǐng)求
let paramsDic = mapDic(mirror: Mirror(reflecting: params))
print(paramsDic)
// 打印結(jié)果
["title": "name", "comment": "Mirror"]
- 需要注意是只能傳入基本類(lèi)型映挂,并且元組參數(shù)要命名,如果直接使用(“name”,“Mirror”)則會(huì)變成下面這種情況:
// 外部參數(shù)定義
var params = ("name","Mirror")
// 網(wǎng)絡(luò)層統(tǒng)一轉(zhuǎn)換為字典盗尸,進(jìn)行網(wǎng)路請(qǐng)求
let paramsDic = mapDic(mirror: Mirror(reflecting: params))
print(paramsDic)
// 打印
[".1": "Mirror", ".0": "name"]
- JSON 解析
- 定義一個(gè) YDWTeacher 類(lèi):
class YDWTeacher {
var age = 18
var name = "YDW"
}
- 運(yùn)用 Mirror 來(lái)解析柑船,實(shí)現(xiàn)代碼如下:
// JSON解析
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 {
// 遞歸調(diào)用
keyValue[keyName] = test(children.value)
} else {
print("children.label 為空")
}
}
return keyValue
}
// 使用
var t = YDWTeacher()
print(test(t))
- 運(yùn)行代碼,打印結(jié)果如下:
["name": "YDW","age": 18]
- 如果想在工程中大量的使用上述的 JSON 解析泼各,可以將 Mirror JSON 解析抽取成一個(gè)協(xié)議鞍时,然后提供一個(gè)默認(rèn)實(shí)現(xiàn),讓類(lèi)遵守協(xié)議,如下:
// 定義JSON解析協(xié)議
protocol CustomJSONMap {
func jsonMap() -> Any
}
// 提供默認(rèn)實(shí)現(xiàn)
extension CustomJSONMap{
func jsonMap() -> Any{
let mirror = Mirror(reflecting: self)
// 遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
// 字典逆巍,用于存儲(chǔ)json數(shù)據(jù)
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("當(dāng)前-\(children.value)-沒(méi)有遵守協(xié)議")
}
}
return keyValue
}
}
// 讓類(lèi)遵守協(xié)議(注意:類(lèi)中屬性的類(lèi)型也需要遵守協(xié)議及塘,否則無(wú)法解析)
class YDWTeacher: CustomJSONMap {
var age = 18
var name = "YDW"
}
// 使用
var t = YDWTeacher()
print(t.jsonMap())
- 運(yùn)行,可以發(fā)現(xiàn)并沒(méi)達(dá)到我們預(yù)想的結(jié)果锐极,這是因?yàn)?YDWTeacher 的屬性的類(lèi)型也需要遵守 CustomJSONMap 協(xié)議:
當(dāng)前-18-沒(méi)有遵守協(xié)議
當(dāng)前-YDW-沒(méi)有遵守協(xié)議
- 修改如下笙僚,這樣就可以達(dá)到我們預(yù)想的結(jié)果:
// Int、String遵守協(xié)議
extension Int: CustomJSONMap{}
extension String: CustomJSONMap{}
// 打印結(jié)果
["name": "YDW", "age": 18]
3.錯(cuò)誤處理
- 為了讓封裝的JSON 解析更加完善灵再,除了對(duì)正常返回的處理肋层,還需要對(duì)其中的錯(cuò)誤進(jìn)行處理,在上面的封裝中翎迁,采用的是 print 打印的栋猖,這樣并不規(guī)范,也不好維護(hù)及管理鸳兽。那么如何在 swift 中正確的表達(dá)錯(cuò)誤呢掂铐?
- Swift 中,提供了 Error 協(xié)議來(lái)標(biāo)識(shí)當(dāng)前應(yīng)用程序發(fā)生錯(cuò)誤的情況揍异,其中 Error 的定義如下:
public protocol Error { }
- 可以看到:Error 是一個(gè)空協(xié)議全陨,其中沒(méi)有任何實(shí)現(xiàn),這也就意味著可以遵守該協(xié)議衷掷,然后自定義錯(cuò)誤類(lèi)型辱姨,因此不管是 struct、Class戚嗅、enum雨涛,都可以遵循這個(gè) Error 來(lái)表示一個(gè)錯(cuò)誤。
- 那么上面封裝的 JSON 解析修改錯(cuò)誤處理懦胞,首先定義一個(gè) JSONMapError 錯(cuò)誤枚舉替久,將默認(rèn)實(shí)現(xiàn)的 print 替換成枚舉類(lèi)型:
// 定義錯(cuò)誤類(lèi)型
enum JSONMapError: Error{
case emptyKey
case notConformProtocol
}
extension CustomJSONMap {
func jsonMap() throws -> Any {
let mirror = Mirror(reflecting: self)
// 遞歸終止條件
guard !mirror.children.isEmpty else {
return self
}
// 字典,用于存儲(chǔ)json數(shù)據(jù)
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
}
}
- 封裝完成躏尉,使用如下:
// 使用時(shí)需要加上try
var t = YDWTeacher()
do{
_ = try t.jsonMap()
}catch JSONMapError.emptyKey{
print("???emptyKey????")
}catch JSONMapError.notConformProtocol{
print("???notConformProtocol?????")
}catch{
}
4.獲取類(lèi)型蚯根,屬性個(gè)數(shù)及其值
- 定義一個(gè)用戶類(lèi):
class YDWUser {
var name: String = ""
var nickName: String?
var age: Int?
var emails: [String]?
}
- 然后創(chuàng)建一個(gè)用戶對(duì)象,并通過(guò)反射獲取這個(gè)對(duì)象的信息:
// 創(chuàng)建一個(gè)User實(shí)例對(duì)象
let user = YDWUser()
user.name = "DW"
user.age = 18
user.emails = ["12345@qq.com", "56789@qq.com"]
// 將user對(duì)象進(jìn)行反射
let hMirror = Mirror(reflecting: user)
print("對(duì)象類(lèi)型:\(hMirror.subjectType)")
print("對(duì)象子元素個(gè)數(shù):\(hMirror.children.count)")
print("--- 對(duì)象子元素的屬性名和屬性值分別如下 ---")
for case let (label?, value) in hMirror.children {
print("屬性:\(label) 值:\(value)")
}
- 打印如下:
對(duì)象類(lèi)型:YDWUser
對(duì)象子元素個(gè)數(shù):4
--- 對(duì)象子元素的屬性名和屬性值分別如下 ---
屬性:name 值:DW
屬性:nickName 值:nil
屬性:age 值:Optional(18)
屬性:emails 值:Optional(["12345@qq.com", "56789@qq.com"])
5.通過(guò)屬性名(字符串)獲取對(duì)應(yīng)的屬性值胀糜,并對(duì)值做類(lèi)型判斷(包括是否為空)
- 定義兩個(gè)方法:getValueByKey() 是用來(lái)根據(jù)傳入的屬性名字符串來(lái)獲取對(duì)象中對(duì)應(yīng)的屬性值颅拦,unwrap() 是用來(lái)給可選類(lèi)型拆包的(對(duì)于非可選類(lèi)型則返回原值):
// 根據(jù)屬性名字符串獲取屬性值
func getValueByKey(obj: AnyObject, key: String) -> Any {
let hMirror = Mirror (reflecting: obj)
for case let (label?, value) in hMirror.children {
if label == key {
return unwrap(any: value)
}
}
return NSNull ()
}
// 將可選類(lèi)型(Optional)拆包
func unwrap(any: Any ) -> Any {
let mi = Mirror (reflecting: any)
if mi.displayStyle! != .optional {
return any
}
if mi.children.count == 0 {
return any
}
let (_, some) = mi.children.first!
return some
}
- 用上例的 YDWUser 對(duì)象做測(cè)試:
// 創(chuàng)建一個(gè)User實(shí)例對(duì)象
let user = YDWUser()
user.name = "DW"
user.age = 18
user.emails = ["12345@qq.com", "56789@qq.com"]
// 通過(guò)屬性名字符串獲取對(duì)應(yīng)的值
let name = getValueByKey(obj: user, key: "name")
let nickName = getValueByKey(obj: user, key: "nickName")
let age = getValueByKey(obj: user, key: "age")
let emails = getValueByKey(obj: user, key: "emails")
let tel = getValueByKey(obj: user, key: "tel")
print(name, nickName, age, emails, tel)
// 對(duì)于獲取到的值進(jìn)行類(lèi)型判斷
if name is NSNull {
print("name這個(gè)屬性不存在")
} else if (name as? AnyObject) == nil {
print("name這個(gè)屬性是個(gè)可選類(lèi)型,且為nil")
} else if name is String {
print("name這個(gè)屬性String類(lèi)型教藻,其值為:\(name)")
}
if tel is NSNull {
print("tel這個(gè)屬性不存在")
} else if (tel as? AnyObject) == nil {
print("tel這個(gè)屬性是個(gè)可選類(lèi)型距帅,且為nil")
} else if tel is String {
print("tel這個(gè)屬性String類(lèi)型,其值為:\(tel)")
}
- 運(yùn)行程序括堤,打印如下:
DW nil 18 ["12345@qq.com", "56789@qq.com"] <null>
name這個(gè)屬性String類(lèi)型碌秸,其值為:DW
tel這個(gè)屬性不存在
3.Swift動(dòng)態(tài)性
- 所謂動(dòng)態(tài):就是在運(yùn)行階段才知道自己的類(lèi)是什么
- 父類(lèi)對(duì)象指向子類(lèi)對(duì)象绍移,是動(dòng)態(tài)特性,因?yàn)樵谶\(yùn)行的時(shí)候讥电,才知道這個(gè)變量真正的類(lèi)型是子類(lèi)
- 具有方法動(dòng)態(tài)派發(fā)(函數(shù)表派發(fā)登夫,消息派發(fā))
- 繼承自O(shè)C的類(lèi)Swift類(lèi),具有動(dòng)態(tài)特性
- dynamic修飾的方法和屬性允趟,具有動(dòng)態(tài)特性
- Swift中的反射機(jī)制,是動(dòng)態(tài)特性
靜態(tài)派發(fā)
在編譯期的時(shí)候鸦致,編譯器就知道要為某個(gè)方法調(diào)用某種實(shí)現(xiàn)
動(dòng)態(tài)派發(fā)
對(duì)于每個(gè)方法的調(diào)用潮剪,編譯器必須在方法列表中查找執(zhí)行方法的實(shí)現(xiàn),比如在運(yùn)行時(shí)判斷是選擇父類(lèi)的實(shí)現(xiàn)分唾,還是子類(lèi)的實(shí)現(xiàn)抗碰。對(duì)于對(duì)象的內(nèi)存都是在運(yùn)行時(shí)分配的,因此只能在運(yùn)行時(shí)執(zhí)行檢查
編譯型語(yǔ)言的函數(shù)派發(fā)方式
- 直接派發(fā)(Direct Dispatch)
編譯后確定了方法的調(diào)用地址(靜態(tài)派發(fā))绽乔,匯編代碼中弧蝇,直接跳到了方法的地址執(zhí)行,生成的匯編指令最少折砸,速度最快
例如C語(yǔ)言看疗,C++默認(rèn)也是直接派發(fā)
由于缺乏動(dòng)態(tài)性,無(wú)法實(shí)現(xiàn)多態(tài) - 函數(shù)表派發(fā)(Table Dispatch)
在運(yùn)行時(shí)通過(guò)函數(shù)查找需要執(zhí)行的方法睦授,多一次查表的過(guò)程两芳,速度比直接派發(fā)慢
C++的虛函數(shù)(Virtual Table),維護(hù)一個(gè)虛函數(shù)表去枷,對(duì)象創(chuàng)建的時(shí)候會(huì)保存虛表的指針怖辆,調(diào)用方之前,會(huì)從對(duì)象中取出虛表地址删顶,根據(jù)編譯時(shí)的方法偏移量從虛表取出方法的地址竖螃,跳到方法的地址執(zhí)行 - 消息派發(fā)(Message Dispatch)
objc消息發(fā)送機(jī)制 - 性能:直接派發(fā) > 函數(shù)表派發(fā) > 消息機(jī)制派發(fā)
直接派發(fā)
- 全局函數(shù)
- 使用
static
聲明的方法 - 使用
final
聲明的所有方法,使用final
聲明的類(lèi)里面的所有方法 - 使用
private
聲明的方法和屬性逗余,會(huì)隱式final
聲明 - 值類(lèi)型的方法特咆,比如
struct
和enum
中的方法 - 在
extension
中沒(méi)有使用@objc
修飾的實(shí)例方法
函數(shù)表派發(fā)
- 只有引用類(lèi)型才是函數(shù)表派發(fā)
- 在Swift中,類(lèi)的方法默認(rèn)使用函數(shù)表派發(fā)的方式
- Swift對(duì)于協(xié)議
Protocol
默認(rèn)使用的是函數(shù)表派發(fā)猎荠,協(xié)議可以為struct提供多態(tài)的支持 - Swift的函數(shù)表叫做
witness table
(C++叫做virtual table)
1.每個(gè)子類(lèi)都有自己的表結(jié)構(gòu)
2.對(duì)于類(lèi)中每個(gè)重寫(xiě)的方法坚弱,都有不同的函數(shù)指針
3.當(dāng)子類(lèi)添加新方法時(shí),這些方法指針會(huì)添加到函數(shù)表的末尾
4.在運(yùn)行時(shí)使用此表來(lái)調(diào)用函數(shù)的實(shí)現(xiàn) (函數(shù)表指針 + 函數(shù)偏移 就能找到對(duì)應(yīng)的方法)
消息派發(fā)
- 用
dynamic
修飾的方法是消息派發(fā) - 對(duì)于繼承自NSObject的Swift類(lèi)关摇,消息派發(fā)方式
注意:@objc
修飾方法荒叶,只是把方法暴露給objc
,是函數(shù)表派發(fā)
例子
- 普通的實(shí)例方法,使用函數(shù)表派發(fā)
- @objc聲明的方法输虱,使用函數(shù)表派發(fā)
- 對(duì)于重寫(xiě)了OC的方法些楣。使用消息派發(fā)
- extension中的方法,直接派發(fā)
- dynamic修飾的方法,使用消息派發(fā)
//舉例說(shuō)明:
class CustomView: UIView {
/// static修飾:直接派發(fā)
static func staticMehod() {}
/// private: 直接派發(fā)
private func privateMethod() {}
/// final修飾:直接派發(fā)
final func finalMethod() {}
/// 直接派發(fā)
static func staticMethod() {}
/// 普通的實(shí)例方法 函數(shù)表派發(fā)
func commonMethod() {}
/// @objc修飾 函數(shù)表派發(fā)
@objc func method1() {}
/// dynamic修飾: 消息派發(fā)
@objc dynamic
func method2() {}
/// 重寫(xiě)了OC的方法: 消息派發(fā)
override func layoutSubviews() {
super.layoutSubviews()
}
}