天天看點

Swift 反射Mirror的使用反射(Reflection)介紹Swift中的反射 Swift反射的使用樣例

反射(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

//使用者類

class

User

{

var

name:

String

""

//姓名

var

nickname:

String

?  

//昵稱

var

age:

Int

?   

//年齡

var

emails:[

String

]?  

//郵件位址

}

接着建立一個使用者對象,并通過反射擷取這個對象的資訊:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16

//建立一個User執行個體對象

let

user1 = 

User

()

user1.name = 

"hangge"

user1.age = 100

user1.emails = [

"[email protected]"

,

"[email protected]"

]

//将user對象進行反射

let

hMirror = 

Mirror

(reflecting: user1)

print

(

"對象類型:\(hMirror.subjectType)"

)

print

(

"對象子元素個數:\(hMirror.children.count)"

)

print

(

"--- 對象子元素的屬性名和屬性值分别如下 ---"

)

for

case

let

(label?, value) 

in

hMirror.children {

print

(

"屬性:\(label)     值:\(value)"

)

}

控制台輸出資訊如下:  

Swift 反射Mirror的使用反射(Reflection)介紹Swift中的反射 Swift反射的使用樣例

通過屬性名(字元串)擷取對應的屬性值,并對值做類型判斷(包括是否為空)

首先為友善使用,這裡定義兩個方法。 getValueByKey()是用來根據傳入的屬性名字元串來擷取對象中對應的屬性值。 unwrap()是用來給可選類型拆包的(對于非可選類型則傳回原值)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

//根據屬性名字元串擷取屬性值

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(value)

}

}

return

NSNull

()

}

//将可選類型(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

}

下面是實際測試樣例,同樣用上例的 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

//建立一個User執行個體對象

let

user1 = 

User

()

user1.name = 

"hangge"

user1.age = 100

user1.emails = [

"[email protected]"

,

"[email protected]"

]

//通過屬性名字元串擷取對應的值

let

name = getValueByKey(user1, key: 

"name"

)

let

nickname = getValueByKey(user1, key: 

"nickname"

)

let

age = getValueByKey(user1, key: 

"age"

)

let

emails = getValueByKey(user1, key: 

"emails"

)

let

tel = getValueByKey(user1, key: 

"tel"

)

print

(name, nickname, age, emails, tel)

//當然對于擷取到的值也可以進行類型判斷

if

name 

is

NSNull

{

print

(

"name這個屬性不存在"

)

}

else

if

(name 

as

AnyObject

) == 

nil

{

print

(

"name這個屬性是個可選類型,且為nil"

)

}

else

if

name 

is

String

{

print

(

"name這個屬性String類型,其值為:\(name)"

)

}

if

nickname 

is

NSNull

{

print

(

"nickname這個屬性不存在"

)

}

else

if

(nickname 

as

AnyObject

) == 

nil

{

print

(

"nickname這個屬性是個可選類型,且為nil"

)

}

else

if

nickname 

is

String

{

print

(

"nickname這個屬性String類型,其值為:\(nickname)"

)

}

if

tel 

is

NSNull

{

print

(

"tel這個屬性不存在"

)

}

else

if

(tel 

as

AnyObject

) == 

nil

{

print

(

"tel這個屬性是個可選類型,且為nil"

)

}

else

if

tel 

is

String

{

print

(

"tel這個屬性String類型,其值為:\(tel)"

)

}

控制台輸出資訊如下:

Swift 反射Mirror的使用反射(Reflection)介紹Swift中的反射 Swift反射的使用樣例

通過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

//使用者類

class

User

NSObject

{

var

name:

String

""

//姓名

var

nickname:

String

?  

//昵稱

var

age:

Int

= 0  

//年齡

var

emails:[

String

]?  

//郵件位址

}

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

//建立一個User執行個體對象

let

user1 = 

User

()

user1.name = 

"hangge"

user1.age = 100

user1.emails = [

"[email protected]"

,

"[email protected]"

]

//使用KVC取值

let

name = user1.valueForKey(

"name"

)

let

nickname = user1.valueForKey(

"nickname"

)

let

age = user1.valueForKey(

"age"

)

let

emails = user1.valueForKey(

"emails"

)

//let tel = user1.valueForKey("tel")

print

(name, nickname, age, emails)

//當然對于擷取到的值也可以進行類型判斷

if

name == 

nil

{

print

(

"name這個屬性是個可選類型,且為nil"

)

}

else

if

name 

is

String

{

print

(

"name這個屬性String類型,其值為:\(name)"

)

}

if

nickname == 

nil

{

print

(

"nickname這個屬性是個可選類型,且為nil"

)

}

else

if

nickname 

is

String

{

print

(

"nickname這個屬性String類型,其值為:\(nickname)"

)

}

(2)通過key設定對應的屬性值

1 2 3 4 5 6 7 8 9

//建立一個User執行個體對象

let

user1 = 

User

()

//使用KVC指派

user1.setValue(

"hangge"

, forKey: 

"name"

)

user1.setValue(100, forKey: 

"age"

)

user1.setValue([

"[email protected]"

,

"[email protected]"

], forKey: 

"emails"

)

print

(user1.name, user1.nickname, user1.age, user1.emails)

繼續閱讀