本文「打造強大的BaseModel」的第四篇,《打造強大的BaseModel(1):讓Model自我描述》、《打造強大的BaseModel(2):讓Model實現自動對映,將字典轉化成Model》、《打造強大的BaseModel(3):讓Model實現自動歸檔》。如果你沒有看過前面三篇文章的話,建議在看這篇文章之前先去看看,熟悉一下iOS Runtime的一些東西以及純Swift型別和Objc型別的異同。
而這篇文章則討論了Swift的反射功能,與iOS Runtime不一樣,Swift的反射用了另一套API,實現機制也完全不一樣,倒是和目前其他的面嚮物件語言有些相似,比如C#和Java。不過Swift作為一門很新的語言,目前正在高速發展中,其反射功能和和主流的高階語言還沒法比,但是我相信Apple會在將來大大加強Swift的反射功能,以追上目前主流語言的腳步。
iOS Runtime目前存在的問題
關於iOS Runtime的文章有很多,一搜就能找出一大堆,但是大多數都是介紹什麼是iOS Runtime及怎麼使用Runtime。其實基於Objc的Runtime是iOS開發的黑魔法,甚至可以是說奇技淫巧,比如神奇的Method Swizzle可以交換任何iOS的系統方法,在裡面加上自己定義的一些功能。再比如訊息轉發機制,又比如說一些位於中的方法,比如class_copyIvarList等方法,可以動態獲取一個類裡面所有的方法和屬性,還有就是動態給一個類新增屬性和方法。Objc的Runtime是如此的強大,再加上KVC和KVO這兩個利器,可以實現很多你根本就想不到的功能,給iOS開發帶來極大的便捷。
使用iOS Runtime好處非常多,但缺點也是顯而易見的,主要有下面幾個:
- 基於Objc的Runtime不是型別安全的,需要開發者保證所有資料都是正確的型別。
- Runtime還是需要進行資料型別的檢查,影響了執行效率。
- Apple推出全新的Swift語言後,單純的Swift型別不再相容原先的Objc的Runtime,
其中前面兩個問題影響不大,關鍵在於第三個。基於Swift作為一門靜態語言,所有資料的型別都是在編譯時就確定好了的,但是Apple為了讓Swift相容Objc,讓Swift也使用了Runtime。這顯然會拖累Swift的執行效率,和Apple所宣稱Swift具有超越Objective-C的效能的觀點完全不符。而Swift在將來是會慢慢替代 Objective-C的成為iOS或者OSX開發的主流語言,所以為了效能,我們應該儘量使用原生的Swift資料型別,避免讓Runtime進行Swift型別->Objc型別的隱式轉換。
所以目前的問題是使用Swift原生的資料型別和想要使用Objc的Runtime有了衝突,那麼Swift語言裡有沒有類似於Objc的Runtime的一套機制,讓Swift資料型別也能實現Objc的Runtime的一些功能呢?
很遺憾,這個答案是NO,Swift目前只有有限的反射功能,完全不能和Objc的Runtime相比。
什麼是反射
反射是一種計算機處理方式。是程式可以訪問、檢測和修改它本身狀態或行為的一種能力。
上面的話來自百度百科。使用反射有什麼用,看一些iOS Runtime的文章應該會很明白。下面再列舉一下
- 動態地建立物件和屬性,
- 動態地獲取一個類裡面所有的屬性,方法。
- 獲取它的父類,或者是實現了什麼樣的介面(協議)
- 獲取這些類和屬性的訪問限制(Public 或者 Private)
- 動態地獲取執行中物件的屬性值,同時也能給它賦值(KVC)
- 動態呼叫例項方法或者類方法
- 動態的給類新增方法或者屬性,還可以交換方法(只限於Objective-C)
上面的一系列功能的細節和計算機語言的不同而不同。對於Objective-C來說,位於中的一系列方法就是完成這些功能的,嚴格來說Runtime並不是反射。而Swift真正擁有了反射功能,但是功能非常弱,目前只能訪問和檢測它本身,還不能修改。
Swift的反射
Swift的反射機制是基於一個叫Mirror的Stuct來實現的。具體的操作方式為:首先建立一個你想要反射的類的例項,再傳給Mirror的構造器來例項化一個Mirror物件,最後使用這個Mirror來獲取你想要的東西。
首先我們來寫一些測試用的類
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 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
protocol Drive{ func run() } public class Tire{ //輪胎 var brand:String? //品牌 var size:Float = 0 //大小 } public class Vehicle:Drive{ var carType:String? var tires:[Tire]? var host:String?// 主人 var brand:String?//汽車品牌 func run() { if let h = host{ print("(h)Drive a (brand) (carType) car run") } else{ print("this car is not selled") } } } public class Trunk:Vehicle{ public var packintBox:String? } public struct TranGroup{ //貨運集團 private var trunks = { return [Trunk]() }() var country:String? var turnover:Float? } //一箇中國的貨運集團 var tranGroup = TranGroup(trunks: [Trunk](), country: "China", turnover: 123456789.111) let trunk1 = Trunk() trunk1.brand = "MAN" trunk1.host = "Stan" trunk1.packintBox = "Big And Long" tranGroup.trunks.append(trunk1) let mirrorTran = Mirror(reflecting: tranGroup) print(tranGroup) //列印出 Mirror for TranGroup print(mirrorTran.subjectType) //列印出 TranGroup print(mirrorTran.displayStyle) //Optional(Swift.Mirror.DisplayStyle.Struct),是個Struct型別 print(mirrorTran.superclassMirror()) //nil,因為沒有父類 for (key,value) in mirrorTran.children{ print("(key) : (value)") } //列印結果 Optional("trunks") : [DemoConsole.Trunk] Optional("country") : Optional("China") Optional("turnover") : Optional(1.23457e+08) |
可以看出,和第一篇文章一樣,列印出個每個屬性和其值。不同的是,對於自定義物件,不能自動打出裡面的屬性內容。
在利用Swift的反射來改進BaseModel之前,讓我們來看看Mirror裡面都有什麼東西吧
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 |
public init(reflecting subject: Any) //構造器 public typealias Child = (label: String?, value: Any) public typealias Children = AnyForwardCollection public enum DisplayStyle { case Struct case Class case Enum case Tuple case Optional case Collection case Dictionary case Set } public enum AncestorRepresentation { case Generated /// 為所有 ancestor class 生成預設 mirror。 case Customized(() -> Mirror)/// 使用最近的 ancestor 的 customMirror() 實現來給它建立一個 mirror。 case Suppressed /// 禁用所有 ancestor class 的行為。Mirror 的 superclassMirror() 返回值為 nil。 } public let subjectType: Any.Type public let children: Children public let displayStyle: Mirror.DisplayStyle? @warn_unused_result public func superclassMirror() -> Mirror? |
一個一個來看
- 第一個是構造器,它傳入的引數型別是Any型別。說明Mirror支援對任意型別的反射。
- 下面定義了兩個typealias,分別是Child和Children,Child是個元組(label: String?, value: Any),label是指屬性名,是個可空值,因為不是所有支援反射的資料結構都包含有名字的子節點。 struct 會以屬性的名字做為 label,但是 Collection 只有下標,沒有名字。Tuple 同樣也可能沒有給它們的條目指定名字。是Value是個Any,也就是說屬性可以是任意型別。
- DisplayStyle是個enum,它會告訴你物件的型別。這裡面其他囊括了所有Cocoa的型別,唯一的例外是個Closure,或者Block。
- AncestorRepresentation也是個enum,這個enum用來定義被反射的物件的父類應該如何被反射。也就是說,這隻應用於 class 型別的物件。預設情況(正如你所見)下 Swift 會為每個父類生成額外的 mirror。然而,如果你需要做更復雜的操作,你可以使用 AncestorRepresentation enum 來定義父類被反射的細節。具體都有什麼樣的型別看上面的註釋。
- subjectType是個Any.Type,從上面列印出來的東西可以看出,它應該是AnyClass的名稱,不同的是AnyClass是AnyObject.Type.後面可以寫程式碼驗證
- children是個AnyForwardCollection型別,也就是個Child的集合。AnyForwardCollection是個什麼玩意呢,Apple是這麼說的
123456/// A type-erased wrapper over any collection with indices that/// support forward traversal.////// Forwards operations to an arbitrary underlying collection having the/// same `Element` type, hiding the specifics of the underlying/// `CollectionType`.
這個確實有點難以理解,我試著翻譯過來:一個型別可擦除的包裝器,適用於任何支援向前遍歷的集合,前向遍歷操作任意一個有著相同“元素”型別的底層集合,隱藏底層集合型別的細節。翻譯過來還是不明白什麼意思,也許需要大神指點,我只好把它看作一個可以遍歷的普通集合算了。 - displayStyle是DisplayStyle enum,表示反射的物件屬於什麼樣的型別
- superclassMirror()是獲取父類的Mirror,如果沒有父類,則為nil
下面看看用各種型別來看看Mirror的各個屬性可以列印出什麼
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 |
extension Mirror{ func printMirror(){ print("mirror:(self) type:(self.subjectType) displayStyle (self.displayStyle) superClassMirror (self.superclassMirror()) ") for (key,value) in self.children{ print("(key) : (value)") } } } var s = {(i:Int) -> Int in return i + i } let mir = Mirror(reflecting: s) mir.printMirror() typealias item = (key:String,value:AnyObject) let a:item = ("111",222) let mirty = Mirror(reflecting: a) mirty.printMirror() Mirror(reflecting: "1").printMirror() Mirror(reflecting: 1.1).printMirror() Mirror(reflecting: NSData()).printMirror() Mirror(reflecting: NSNull()).printMirror() enum week:Int{ case Mon = 1,Thu,Wed,Tur,Fri,Sat,Sun } Mirror(reflecting: week.Fri).printMirror() //列印結果 **mirror:Mirror for Int -> Int type:Int -> Int displayStyle nil superClassMirror nil mirror:Mirror for (String, AnyObject) type:(String, AnyObject) displayStyle Optional(Swift.Mirror.DisplayStyle.Tuple) superClassMirror nil Optional(".0") : 111 Optional(".1") : 222 mirror:Mirror for String type:String displayStyle nil superClassMirror nil mirror:Mirror for Double type:Double displayStyle nil superClassMirror nil mirror:Mirror for NSNull type:NSNull displayStyle Optional(Swift.Mirror.DisplayStyle.Class) superClassMirror Optional(Mirror for NSObject) mirror:Mirror for _NSZeroData type:_NSZeroData displayStyle Optional(Swift.Mirror.DisplayStyle.Class) superClassMirror Optional(Mirror for NSData) mirror:Mirror for week type:week displayStyle Optional(Swift.Mirror.DisplayStyle.Enum) superClassMirror nil** |
Colsure或者Block的displayStyle是nil,而typealias則轉化成了正確的型別。其他所有型別都正確在獲取並列印出來了。
Mirror類還有一個些其他的構造器,還有一些擴充套件,可能一些特殊場合會用到,這裡就省略了,總之來說,Swift的反射可以用在以下場景
- 遍歷Turple
- 對類做分析,獲取屬性和值
- 執行時分析物件的一至性
總體來說,因為功能比較弱,使用場景也比較窄。遠遠比不上Objc的Runtime,更別說Java和C#了。
但是相對於Objc的Runtime,Swift的反射是可以獲取全部屬性的,而且API相對於Objc也簡單許多。有時侯,可以用來代替Runtime中獲取所有屬性名的方法
1 2 3 4 5 6 7 8 |
func getSelfPropertySwift()->[String]{ var selfProperties = [String]() let mir = Mirror(reflecting: self.dynamicType.init()) for (key,_) in mir.children{ selfProperties.append(key!)//這裡可以獲取所有的屬性,但是隻有和Objc相容的型別才能做KVC操作 } return selfProperties } |
可以用這個來重寫description方法,程式碼看起來要簡潔不少。但是—–這個根本就沒法用。
1 2 3 4 5 6 7 8 |
var dict = [String:AnyObject]() let mir = Mirror(reflecting: self.dynamicType.init()) for (key,value) in mir.children{ if let obj = value as? AnyObject{ //注意字典只能儲存AnyObject型別。 dict[key!] = obj } } return "(self.dynamicType):(dict)" |
Mirror中的children無法識別複雜型別,對於自定義型別,或者是沒有賦值的可空型別,獲取的value是nil,這樣也就無法像第一篇文章中那樣正確地列印出各種個屬性名和值,我的如意算盤落空了。
總結
就目前而言,怪異的API,功能的缺失,讓Swift的反射功能難堪大用。我們之所以要用反射,就是要利用基於反射動態程式設計的特性,實現在執行時動態地給屬性賦值,動態呼叫方法等。不知道是Apple認為Objc的Runtime功能已經足夠強大好用,還是認為Swift的安全更重要,目前在Swift2.2版本反射還是根本沒法用。我希望未來Apple可以好好的加強Swift的反射,參考一下C#和Java的機制,設計出更強大更好用的反射功能
打造強大的BaseModel系列文章到此就完結了,在寫這些文章的過程中,我發現自己對iOS的Runtime有了更深的理解,也明白了Swift和Objc是怎麼相互操作的,可見寫文章的最大收穫還是自己。所以我還會寫更多的iOS開發技巧系列文章,請大家期待。