HandyJSON:Swift語(yǔ)言JSON轉(zhuǎn)Model工具庫(kù)
背景
JSON是移動(dòng)端開(kāi)發(fā)常用的應(yīng)用層數(shù)據(jù)交換協(xié)議。最常見(jiàn)的場(chǎng)景便是,客戶端向服務(wù)端發(fā)起網(wǎng)絡(luò)請(qǐng)求,服務(wù)端返回JSON文本,然后客戶端解析這個(gè)JSON文本,再把對(duì)應(yīng)數(shù)據(jù)展現(xiàn)到頁(yè)面上。
但在編程的時(shí)候,處理JSON是一件麻煩事。在不引入任何輪子的情況下,我們通常需要先把JSON轉(zhuǎn)為Dictionary,然后還要記住每個(gè)數(shù)據(jù)對(duì)應(yīng)的Key,用這個(gè)Key在Dictionary中取出對(duì)應(yīng)的Value來(lái)使用。這個(gè)過(guò)程我們會(huì)犯各種錯(cuò)誤:
- Key拼寫(xiě)錯(cuò)了;
- 路徑寫(xiě)錯(cuò)了;
- 類型搞錯(cuò)了;
- 沒(méi)拿到值懵逼了;
- 某一天和服務(wù)端約定的某個(gè)字段變更了,沒(méi)能更新所有用到它的地方;
- ...
為了解決這些問(wèn)題,很多處理JSON的開(kāi)源庫(kù)應(yīng)運(yùn)而生。在Swift中,這些開(kāi)源庫(kù)主要朝著兩個(gè)方向努力:
- 保持JSON語(yǔ)義,直接解析JSON,但通過(guò)封裝使調(diào)用方式更優(yōu)雅、更安全;
- 預(yù)定義Model類,將JSON反序列化為類實(shí)例,再使用這些實(shí)例;
對(duì)于1,使用最廣、評(píng)價(jià)***的庫(kù)非 SwiftyJSON 莫屬,它很能代表這個(gè)方向的核心。它本質(zhì)上仍然是根據(jù)JSON結(jié)構(gòu)去取值,使用起來(lái)順手、清晰。但也正因如此,這種做法沒(méi)能妥善解決上述的幾個(gè)問(wèn)題,因?yàn)镵ey、路徑、類型仍然需要開(kāi)發(fā)者去指定;
對(duì)于2,我個(gè)人覺(jué)得這是更合理的方式。由于Model類的存在,JSON的解析和使用都受到了定義的約束,只要客戶端和服務(wù)端約定好了這個(gè)Model類,客戶端定義后,在業(yè)務(wù)中使用數(shù)據(jù)時(shí)就可以享受到語(yǔ)法檢查、屬性預(yù)覽、屬性補(bǔ)全等好處,而且一旦數(shù)據(jù)定義變更,編譯器會(huì)強(qiáng)制所有用到的地方都改過(guò)來(lái)才能編譯通過(guò),非常安全。這個(gè)方向上,開(kāi)源庫(kù)們做的工作,主要就是把JSON文本反序列化到Model類上了。這一類JSON庫(kù)有 ObjectMapper、JSONNeverDie、HandyJSON 等。而 HandyJSON 是其中使用最舒服的一個(gè)庫(kù),本文將介紹用 HandyJSON 來(lái)進(jìn)行Model和JSON間的互相轉(zhuǎn)換。
項(xiàng)目地址:https://github.com/alibaba/handyjson
為什么用HandyJSON
在Swift中把JSON反序列化到Model類,在HandyJSON出現(xiàn)以前,主要使用兩種方式:
- 讓Model類繼承自NSObject,然后class_copyPropertyList()方法獲取屬性名作為Key,從JSON中取得Value,再通過(guò)Objective-C runtime支持的KVC機(jī)制為類屬性賦值;如JSONNeverDie;
- 支持純Swift類,但要求開(kāi)發(fā)者實(shí)現(xiàn)Mapping函數(shù),使用重載的運(yùn)算符進(jìn)行賦值,如ObjectMapper;
這兩者都有顯而易見(jiàn)的缺點(diǎn)。前者要求Model繼承自NSObject,非常不優(yōu)雅,且直接否定了用struct來(lái)定義Model的方式;后者的Mapping函數(shù)要求開(kāi)發(fā)者自定義,在其中指明每個(gè)屬性對(duì)應(yīng)的JSON字段名,代碼侵入大,且仍然容易發(fā)生拼寫(xiě)錯(cuò)誤、維護(hù)困難等問(wèn)題。
而HandyJSON另辟蹊徑,采用Swift反射+內(nèi)存賦值的方式來(lái)構(gòu)造Model實(shí)例,規(guī)避了上述兩個(gè)方案遇到的問(wèn)題。
把JSON轉(zhuǎn)換為Model
簡(jiǎn)單類型
某個(gè)Model類想支持通過(guò)HandyJSON來(lái)反序列化,只需要在定義時(shí),實(shí)現(xiàn)HandyJSON協(xié)議,這個(gè)協(xié)議只要求實(shí)現(xiàn)一個(gè)空的init()函數(shù)。
比如我們和服務(wù)端約定了一個(gè)Animal數(shù)據(jù),里面有name/id/num字段,那么我們這樣定義Animal類:
- class Animal: HandyJSON {
- var name: String?
- var id: String?
- var num: Int?
- required init() {}
- }
然后假設(shè)我們從服務(wù)端拿到這樣一個(gè)JSON文本:
- let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"
引入HandyJSON以后,我們就可以這樣來(lái)做反序列化了:
- if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
- print(animal.name)
- print(animal.id)
- print(animal.num)
- }
簡(jiǎn)單吧~
支持Struct
如果Model的定義是struct,由于Swift中struct提供了默認(rèn)構(gòu)造函數(shù),所以就不需要再實(shí)現(xiàn)空的init()函數(shù)了。但需要注意,如果你為strcut指定了別的構(gòu)造函數(shù),那么就需要保留一個(gè)空的實(shí)現(xiàn)。
- struct Animal: HandyJSON {
- var name: String?
- var id: String?
- var num: Int?
- }
- let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}"
- if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) {
- print(animal)
- }
比較復(fù)雜的類型
HandyJSON支持在類定義里使用各種形式的基本屬性,包括可選(?),隱式解包可選(!),數(shù)組(Array),字典(Dictionary),Objective-C基本類型(NSString、NSNumber),各種類型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面這個(gè)看起來(lái)比較復(fù)雜的類型:
- class Cat: HandyJSON {
- var id: Int64!
- var name: String!
- var friend: [String]?
- var weight: Double?
- var alive: Bool = true
- var color: NSString?
- required init() {}
- }
一樣輕松轉(zhuǎn)換:
- let jsonString = "{\"id\":1234567,\"name\":\"Kitty\",\"friend\":[\"Tom\",\"Jack\",\"Lily\",\"Black\"],\"weight\":15.34,\"alive\":false,\"color\":\"white\"}"
- if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
- print(cat.xxx)
- }
嵌套的Model類
如果Model類中的某個(gè)屬性是另一個(gè)自定義的Model類,那么只要那個(gè)Model類也實(shí)現(xiàn)了HandyJSON協(xié)議,就一樣可以轉(zhuǎn)換:
- class Component: HandyJSON {
- var aInt: Int?
- var aString: String?
- required init() {}
- }
- class Composition: HandyJSON {
- var aInt: Int?
- var comp1: Component?
- var comp2: Component?
- required init() {}
- }
- let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}"
- if let composition = JSONDeserializer<Composition>.deserializeFrom(json: jsonString) {
- print(composition)
- }
指定反序列化JSON中某個(gè)節(jié)點(diǎn)
有時(shí)候服務(wù)端返回給我們的JSON文本包含了大量的狀態(tài)信息,和Model無(wú)關(guān),比如statusCode,debugMessage等,或者有用的數(shù)據(jù)是在某個(gè)節(jié)點(diǎn)以下,那么我們可以指定反序列化哪個(gè)節(jié)點(diǎn):
- class Cat: HandyJSON {
- var id: Int64!
- var name: String!
- required init() {}
- }
- // 服務(wù)端返回了這個(gè)JSON,我們想解析的只有data里的cat
- let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}"
- // 那么,我們指定解析 "data.cat",通過(guò)點(diǎn)來(lái)表達(dá)路徑
- if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString, designatedPath: "data.cat") {
- print(cat.name)
- }
有繼承關(guān)系的Model類
如果某個(gè)Model類繼承自另一個(gè)Model類,只需要這個(gè)父Model類實(shí)現(xiàn)HandyJSON協(xié)議就可以:
- class Animal: HandyJSON {
- var id: Int?
- var color: String?
- required init() {}
- }
- class Cat: Animal {
- var name: String?
- required init() {}
- }
- let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}"
- if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) {
- print(cat)
- }
自定義解析方式
HandyJSON還提供了一個(gè)擴(kuò)展能力,就是允許自行定義Model類某個(gè)字段的解析Key、解析方式。我們經(jīng)常會(huì)有這樣的需求:
- 某個(gè)Model中,我們不想使用和服務(wù)端約定的key作為屬性名,想自己定一個(gè);
- 有些類型如enum、tuple是無(wú)法直接從JSON中解析出來(lái)的,但我們?cè)贛odel類中有這樣的屬性;
HandyJSON協(xié)議提供了一個(gè)可選的mapping()函數(shù),我們可以在其中指定某個(gè)字段用什么Key、或者用什么方法從JSON中解析出它的值。如我們有一個(gè)Model類和一個(gè)服務(wù)端返回的JSON串:
- class Cat: HandyJSON {
- var id: Int64!
- var name: String!
- var parent: (String, String)?
- required init() {}
- }
- let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}"
可以看到,Cat類的id屬性和JSON文本中的Key是對(duì)應(yīng)不上的;而對(duì)于parent這個(gè)屬性來(lái)說(shuō),它是一個(gè)元組,做不到從JSON中的"Tom/Lily"解析出來(lái)。所以我們要定義一個(gè)Mapping函數(shù)來(lái)做這兩個(gè)支持:
- class Cat: HandyJSON {
- var id: Int64!
- var name: String!
- var parent: (String, String)?
- required init() {}
- func mapping(mapper: HelpingMapper) {
- // 指定 id 字段用 "cat_id" 去解析
- mapper.specify(property: &id, name: "cat_id")
- // 指定 parent 字段用這個(gè)方法去解析
- mapper.specify(property: &parent) { (rawString) -> (String, String) in
- let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
- return (parentNames[0], parentNames[1])
- }
- }
- }
就這樣,HandyJSON***地幫我們進(jìn)行了JSON到Model類的轉(zhuǎn)換。如此方便,這也是將其命名為Handy的由來(lái)。
把Model轉(zhuǎn)換為JSON文本
HandyJSON還提供了把Model類序列化為JSON文本的能力,簡(jiǎn)直無(wú)情。
基本類型
如果只需要進(jìn)行序列化,那么在定義Model類時(shí),不需要做任何特殊的改動(dòng)。任何一個(gè)類的實(shí)例,直接調(diào)用HandyJSON的序列化方法去序列化,就能得到JSON字符串了。
- class Animal {
- var name: String?
- var height: Int?
- init(name: String, height: Int) {
- self.name = name
- self.height = height
- }
- }
- let cat = Animal(name: "cat", height: 30)
- // 序列化為簡(jiǎn)單JSON文本
- if let jsonStr = JSONSerializer.serialize(model: cat).toJSON() {
- print("simple json string: ", jsonStr)
- }
- // 序列化為格式化的JSON文本
- if let prettifyJSON = JSONSerializer.serialize(model: cat).toPrettifyJSON() {
- print("prettify json string: ", prettifyJSON)
- }
- // 序列化為簡(jiǎn)單字典
- if let dict = JSONSerializer.serialize(model: cat).toSimpleDictionary() {
- print("dictionary: ", dict)
- }
復(fù)雜類型
即使Model類中有別的Model類啥的,都一樣支持。
- enum Gender {
- case Male
- case Female
- }
- struct Subject {
- var id: Int64?
- var name: String?
- init(id: Int64, name: String) {
- self.id = id
- self.name = name
- }
- }
- class Student {
- var name: String?
- var gender: Gender?
- var subjects: [Subject]?
- }
- let student = Student()
- student.name = "Jack"
- student.gender = .Female
- student.subjects = [Subject(id: 1, name: "math"), Subject(id: 2, name: "English"), Subject(id: 3, name: "Philosophy")]
- if let jsonStr = JSONSerializer.serialize(model: student).toJSON() {
- print("simple json string: ", jsonStr)
- }
- if let prettifyJSON = JSONSerializer.serialize(model: student).toPrettifyJSON() {
- print("prettify json string: ", prettifyJSON)
- }
- if let dict = JSONSerializer.serialize(model: student).toSimpleDictionary() {
- print("dictionary: ", dict)
- }
總結(jié)
有了HandyJSON的支持,現(xiàn)在我們可以開(kāi)心地在Swift中使用JSON了。這個(gè)庫(kù)支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建議,可以去 https://github.com/alibaba/handyjson 提issue.
為何開(kāi)發(fā)HandyJSON
我所在iOS團(tuán)隊(duì)是從去年11月份切Swift的。我們服務(wù)端和客戶端數(shù)據(jù)交互格式一直用的是JSON,而當(dāng)時(shí)Swift中處理JSON名氣比較大的庫(kù)貌似只有SwiftyJSON,工程切到Swift后,我們也用了這個(gè)庫(kù)。用上之后,需求是滿足了,但是對(duì)一些復(fù)雜的Model,代碼寫(xiě)得看起來(lái)非常糟糕,因?yàn)槊看稳≈刀夹枰? json["akey"]["bkey"]["ckey"].value 形式,寫(xiě)的時(shí)候?qū)χ臋n沒(méi)覺(jué)得啥問(wèn)題,但過(guò)后在脫離文檔的情況下,通篇都是字符串表達(dá)的key,很難從代碼中感覺(jué)出Model結(jié)構(gòu)。所以我們都會(huì)把一段sample數(shù)據(jù)寫(xiě)在注釋里。但仍然比較凌亂,另外key寫(xiě)錯(cuò)了debug起來(lái)也費(fèi)勁,一個(gè)大小寫(xiě)問(wèn)題有時(shí)候debug半天。
于是我們進(jìn)化了一下,先寫(xiě)好Model,然后Model類中寫(xiě)convert函數(shù),也用上了KVC遍歷key賦值。寫(xiě)起來(lái)舒服多了,但還是麻煩,而且要求每一個(gè)類都繼承自NSObject。不久后,我們認(rèn)識(shí)了ObjectMapper庫(kù),二話不說(shuō),就換了上去。世界頓時(shí)干凈多了。
但還是感覺(jué)差了一點(diǎn),因?yàn)镺bjectMapper需要自己指明映射關(guān)系。通常JSON中key和Model中字段名都是一致的,每次都要額外寫(xiě)一坨東西,總覺(jué)得多余,字段有改動(dòng)的時(shí)候也費(fèi)勁。新來(lái)剛接觸Swift的同事,也表示不太舒服,因?yàn)樗麄冎笆褂玫腏SON反序列化庫(kù),無(wú)論Java中還是Objective-C中,都是自然使用Model字段名去取值的。
所以就想著研究一下,Swift中能不能做到這種效果。
HandyJSON的設(shè)計(jì)思路
Swift中存在的限制
無(wú)論是Java或者Objective-C中的JSON反序列化庫(kù),通常都是,在運(yùn)行時(shí)獲取Model的字段名集合,遍歷該集合,拿Key去JSON中取值并完成賦值。這些步驟,Java依賴反射機(jī)制可以實(shí)現(xiàn),Objective-C通過(guò)class_copyPropertyList方法加上KVC機(jī)制,也能輕松實(shí)現(xiàn)。而Swift會(huì)卡在***一步:無(wú)法賦值。
Swift的反射是只讀的,就是說(shuō),我們能在運(yùn)行時(shí)獲取一個(gè)Model實(shí)例的所有字段、字段值,但卻無(wú)法給它賦值。事實(shí)上,我們拿到的value是原值的一個(gè)只讀拷貝,即使獲取到這個(gè)拷貝的地址寫(xiě)入新值,也是無(wú)效的。
- class Animal {
- var name: String?
- }
- Mirror(reflecting: Animal()).children.forEach { (child) in
- print(child.label ?? "", child.value) // working correctly
- child.value = "cat" // error,不能直接賦值
- }
而且迄今,蘋(píng)果官網(wǎng)文檔上對(duì)實(shí)現(xiàn)反射機(jī)制的Mirror類仍然是這么描述: Mirrors are used by playgrounds and the debugger,態(tài)度非常含糊,似乎不太鼓勵(lì),但生產(chǎn)中很多類庫(kù)都用上了。只能說(shuō),蘋(píng)果不會(huì)輕易撤下這個(gè)能力,但期待它對(duì)這個(gè)能力做出改進(jìn)(比如支持運(yùn)行時(shí)賦值),是希望渺茫的。
如何繞過(guò)限制
最簡(jiǎn)單的方式,就是在Swift中定義Model時(shí)繼承NSObject,讓這個(gè)Model的實(shí)例存在于objc運(yùn)行時(shí)中,上述的class_copyPropertyList方法和KVC就能用上了。目前看見(jiàn)的Swift中不需要指明映射關(guān)系的JSON庫(kù),都是這種方式。
然后就是以O(shè)bjectMapper為代表的庫(kù),通過(guò)運(yùn)算符重載,在指定映射關(guān)系時(shí)完成賦值。走這一類實(shí)現(xiàn)的庫(kù)也非常多了。
但我想做到的是,既支持運(yùn)行在Swift運(yùn)行時(shí)的純Swift類,又不需要顯示指定每一個(gè)字段的映射關(guān)系。那么,不能走反射賦值,那就直接寫(xiě)入內(nèi)存吧。
具體實(shí)現(xiàn)
Swift中,一個(gè)類實(shí)例的內(nèi)存布局是有規(guī)律的:
- 32位機(jī)器上,類前面有4+8個(gè)字節(jié)存儲(chǔ)meta信息,64位機(jī)器上,有8+8個(gè)字節(jié);
- 內(nèi)存中,字段從前往后有序排列;
- 如果該類繼承自某一個(gè)類,那么父類的字段在前;
- Optional會(huì)增加一個(gè)字節(jié)來(lái)存儲(chǔ).None/.Some信息;
- 每個(gè)字段需要考慮內(nèi)存對(duì)齊;
這方面基本沒(méi)有官方的資料參考,上述規(guī)律一些是從網(wǎng)上其他大神的總結(jié)中收集,一些從Clang的一些說(shuō)明文檔中挖掘,還有一些是自己在playground里試出來(lái)的。開(kāi)始心里沒(méi)什么底,但把HandyJSON實(shí)現(xiàn)出來(lái)使用這么久了,還沒(méi)出過(guò)狀況,可以認(rèn)為是靠譜的。
有法子計(jì)算內(nèi)存布局,剩下的事情就比較簡(jiǎn)單了。對(duì)一個(gè)實(shí)例:
- 獲取它的起始指針,移動(dòng)到有效起點(diǎn);
- 通過(guò)Mirror獲取每一個(gè)字段的字段名和字段類型;
- 根據(jù)字段名在JSON中取值,轉(zhuǎn)換為和字段一樣的類型,通過(guò)指針寫(xiě)入;
- 根據(jù)本字段類型的占位大小和下一個(gè)字段類型計(jì)算下一個(gè)字段的對(duì)齊起點(diǎn);
- 移動(dòng)指針,繼續(xù)處理;
獲取類實(shí)例的起始指針
Swift中,獲取struct實(shí)例起始指針和獲取class實(shí)例起始指針的方法是不一樣的,和語(yǔ)言版本也相關(guān)。在Swift3中:
- // 獲取struct實(shí)例起始指針
- mutating func headPointerOfStruct() -> UnsafeMutablePointer<Byte> {
- return withUnsafeMutablePointer(to: &self) {
- return UnsafeMutableRawPointer($0).bindMemory(to: Byte.self, capacity: MemoryLayout<Self>.stride)
- }
- }
- // 獲取class實(shí)例起始指針
- mutating func headPointerOfClass() -> UnsafeMutablePointer<Byte> {
- let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
- let mutableTypedPointer = opaquePointer.bindMemory(to: Byte.self, capacity: MemoryLayout<Self>.stride)
- return UnsafeMutablePointer<Byte>(mutableTypedPointer)
- }
通過(guò)Mirror獲取字段名、類型
- Mirror(reflecting: Animal()).children.forEach { (child) in
- print(child.label ?? "") // 獲取字段名
- print(type(of: child.value)) // 獲取字段類型
- }
計(jì)算Model的每個(gè)屬性字段占位大小
Swift3暴露了兩個(gè)接口用于計(jì)算類型占位大小:MemoryLayout.size(ofValue: T)和MemoryLayout.size。這兩者都沒(méi)辦法直接用,因?yàn)椋?/p>
- 對(duì)于每個(gè)屬性,我們目前只持有它的起始指針,而不是它的實(shí)例,***個(gè)接口用不上;
- 對(duì)于每個(gè)屬性,我們是在運(yùn)行時(shí)中獲取到它的類型,已經(jīng)沒(méi)辦法再實(shí)例化出泛型類型MemoryLayout<T>來(lái)計(jì)算size。所以,我引入了HandyJSON類,在擴(kuò)展中實(shí)現(xiàn)函數(shù):
- protocol HandyJSON {
- }
- extension HandyJSON {
- static func size() -> Int {
- return MemoryLayout<Self>.size
- }
- }
于是,對(duì)于每一個(gè)實(shí)現(xiàn)HandyJSON協(xié)議的Model類T,直接調(diào)用 T.size() 就能獲取到T的size了。
內(nèi)存對(duì)齊的影響
類實(shí)例的屬性并不是直接按照各自占位大小依次往下排列的,不然事情就簡(jiǎn)單了。和C/C++一樣,Swift中實(shí)例內(nèi)存布局也考慮了內(nèi)存對(duì)齊。翻閱了Swift的docs和LLVM的一些資料,MemoryLayout提供了一個(gè)接口:MemoryLayout.alignment,對(duì)齊的規(guī)則為,每個(gè)字段的起始地址必須為alignment值的整數(shù)倍。細(xì)節(jié)的出處我已經(jīng)忘記。當(dāng)時(shí)進(jìn)行了一些復(fù)雜類型的測(cè)試后,認(rèn)定它符合事實(shí)。所以HandyJSON中計(jì)算下一個(gè)字段起始地址的函數(shù)為:
- // Returns the offset to the next integer that is greater than
- // or equal to Value and is a multiple of Align. Align must be
- // non-zero.
- static func offsetToAlignment(value: Int, align: Int) -> Int {
- let m = value % align
- return m == 0 ? 0 : (align - m)
- }
其他情況
基本類型按照上述方法處理就可以了,還有可選類型、數(shù)組類型、字典類型,通過(guò)遍歷、遞歸解析等方式,處理方法也類似。如數(shù)組:
- extension Array: ArrayTypeProtocol {
- static func getWrappedType() -> Any.Type {
- return Element.self
- }
- static func castArrayType(arr: [Any]) -> Array<Element> {
- return arr.map({ (p) -> Element in
- return p as! Element
- })
- }
- }
獲取到Array泛型實(shí)參類型,然后構(gòu)造出該類型的一個(gè)數(shù)組,完成賦值就可以了。
結(jié)語(yǔ)
主要流程就是這樣了,也比較簡(jiǎn)單,剩下處理繼承、組合等情況,只是實(shí)現(xiàn)問(wèn)題,就不再贅述了。總覺(jué)得自己對(duì)Swift指針這一套設(shè)施理解還不是很到位,也許有更好的用法,比如說(shuō),完全不需要空的init()函數(shù)就可以初始化出一個(gè)類的實(shí)例。有同學(xué)在這方面有更深入理解,有什么意見(jiàn)或者建議的,歡迎交流~
參考
- https://appventure.me/2015/10/24/swift-reflection-api-what-you-can-do/
- https://www.raywenderlich.com/119881/enums-structs-and-classes-in-swift
- http://vizlabxt.github.io/blog/2014/12/23/Swift-Memory/
- https://realm.io/news/russ-bishop-unsafe-swift/
- http://sketchytech.blogspot.jp/2014/10/becoming-less-afraid-of-unsafe-mutable.html
- http://southpeak.github.io/blog/2014/07/06/ios-swift-cpointer-2/
- https://onevcat.com/2015/01/swift-pointer/
- https://appventure.me/2015/10/17/advanced-practical-enum-examples/
- https://github.com/Hearst-DD/ObjectMapper