反射(Reflection)介紹
對于 C#、 Java開發人員來說,肯定都對反射這個概念相當熟悉。所謂反射就是可以動态擷取類型、成員資訊,同時在運作時(而非編譯時)可以動态調用任意方法、屬性等行為的特性。 以 Java上的兩個知名架構( hibernate和 spring)為例。 hibernate的屬性映射就是通過反射來指派的, spring的 bean的建立就是根據配置的 class來反射建構的。
Objective-C 的 Runtime
在使用ObjC開發時很少強調其反射概念,因為ObjC的Runtime要比其他語言中的反射強大的多。在ObjC中可以很簡單的實作字元串和類型的轉換(NSClassFromString()),實作動态方法調用(performSelector: withObject:),動态指派(KVC)等等。
Swift中的反射
在Swift中并不提倡使用Runtime,而是像其他語言一樣使用反射(Reflect)。當然,目前Swift中的反射還沒有其他語言中的反射功能強大,不僅遠不及OC的Runtime,離Java的反射也有一定的距離。
Swift的反射機制是基于一個叫 Mirror 的 struct 來實作的,其内部有如下屬性和方法:
/// 屬性 Mirror.Children (label: String?, value: Any)
let children: Mirror.Children
/// 自定義反射
var customMirror: Mirror
/// 反射描述 一般為 Mirror for 類型
var description: String
/// 顯示類型,基本類型為nil 枚舉值: class, enum , struce, dictionary, array, set, tuple
let displayStyle: Mirror.DisplayStyle?
/// 類型
let subjectType: Any.Type
/// 父類反射, 沒有父類為nil
var superclassMirror: Mirror?
通常擷取屬性一般周遊children來擷取。
Swift反射的使用樣例
轉換對象為字典
struct Person {
var name: String = "張三"
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 {
// 如果沒有labe就會被抛棄
if let label = child.label {
let propertyMirror = Mirror(reflecting: child.value)
dic[label] = child.value
}
}
// 添加父類屬性
if let superMirror = mirror.superclassMirror {
let superDic = mapDic(mirror: superMirror)
for p in superDic {
dic[p.key] = p.value
}
}
return dic
}
列印結果, 居然可以列印出私有屬性
// Mirror使用
let objc = Cat()
let mirror = Mirror(reflecting: objc)
let mirrorDic = mapDic(mirror: mirror)
print(mirrorDic)
//列印結果
["like": ["mouse", "fish"], "optionValue": nil, "eat": "吃什麼", "age": 0, "master": __lldb_expr_48.Person(name: "張三", isMale: true, birthday: 2020-01-02 11:24:30 +0000)]
在實際運用中,可以将應用于元組參數傳遞(比如網路請求傳參,傳入元組,網絡請求時轉換為字典),優點:外部使用知道具體傳入什麼參數,參數更改不影響方法錯誤。
// 外部參數定義
var netParams = (title: "标題", comment: "評論,五星好評")
// 網絡層統一轉換為字典,進行網路請求
let parmsDic = mapDic(mirror: Mirror(reflecting: netParams))
print(parmsDic)
// 列印結果
["title": "标題", "comment": "評論,五星好評"]
但是需要注意,隻能傳入基本類型。且元組參數要命名,如果直接使用("标題","評論,五星好評")則會變成下面這種情況。
// 外部參數定義
var netParams = ("标題","評論,五星好評")//(title: "标題", comment: "評論,五星好評")
// 網絡層統一轉換為字典,進行網路請求
let parmsDic = mapDic(mirror: Mirror(reflecting: netParams))
print(parmsDic)
// 列印
[".1": "評論,五星好評", ".0": "标題"]
擷取類型,屬性個數及其值
首先定義一個使用者類:
1 2 3 4 5 6 7 | |
接着建立一個使用者對象,并通過反射擷取這個對象的資訊:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | |
控制台輸出資訊如下:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLicmbwxCdh1mcvZ2LcV2Zh1Wa9M3clN2byBXLzN3btg3Pj1mY3VzQPhXUE9EMNRlTw0ERPh3aE1UeFRlT4FkaNZXSU1UMFRUT5hTejtmRyI2cChFZmRmMiNnSywEd5ITW1VlMa5WNXl1b1kHZzQ2MMZ3bENGMShUYvwlbj5yZtlmbkN3YuQnclZnbvN2Ztl2Lc9CX6MHc0RHaiojIsJye.jpg)
通過屬性名(字元串)擷取對應的屬性值,并對值做類型判斷(包括是否為空)
首先為友善使用,這裡定義兩個方法。 getValueByKey()是用來根據傳入的屬性名字元串來擷取對象中對應的屬性值。 unwrap()是用來給可選類型拆包的(對于非可選類型則傳回原值)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
下面是實際測試樣例,同樣用上例的 User對象做測試:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | |
控制台輸出資訊如下:
通過KVC通路屬性值
KVC是 key-value coding的縮寫。它是一種間接通路對象的機制。其本質是依據 OC中 Runtime的強大動态能力來實作的。在 Swift中,隻要類繼承 NSObject即可使用 KVC。(有一個叫 KVO的,它又是基于 KVC,大家有興趣的可以自行研究下。) KVC中: key的值就是屬性名稱的字元串,傳回的 value是任意類型,需要自己轉化為需要的類型。 (注意:正由于KVC是基于Objective-C的,是以其不支援可選類型(optional)的屬性,比如上例的 var age:Int?
是以使用者類做如下改造:)
1 2 3 4 5 6 7 | |
KVC主要就是兩個方法: (1)通過key獲得對應的屬性值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
(2)通過key設定對應的屬性值
1 2 3 4 5 6 7 8 9 | |