自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

HandyJSON:Swift語言JSON轉(zhuǎn)Model工具庫

移動開發(fā) 移動應(yīng)用
JSON是移動端開發(fā)常用的應(yīng)用層數(shù)據(jù)交換協(xié)議。最常見的場景便是,客戶端向服務(wù)端發(fā)起網(wǎng)絡(luò)請求,服務(wù)端返回JSON文本,然后客戶端解析這個JSON文本,再把對應(yīng)數(shù)據(jù)展現(xiàn)到頁面上。

背景

JSON是移動端開發(fā)常用的應(yīng)用層數(shù)據(jù)交換協(xié)議。最常見的場景便是,客戶端向服務(wù)端發(fā)起網(wǎng)絡(luò)請求,服務(wù)端返回JSON文本,然后客戶端解析這個JSON文本,再把對應(yīng)數(shù)據(jù)展現(xiàn)到頁面上。

但在編程的時候,處理JSON是一件麻煩事。在不引入任何輪子的情況下,我們通常需要先把JSON轉(zhuǎn)為Dictionary,然后還要記住每個數(shù)據(jù)對應(yīng)的Key,用這個Key在Dictionary中取出對應(yīng)的Value來使用。這個過程我們會犯各種錯誤:

  • Key拼寫錯了;
  • 路徑寫錯了;
  • 類型搞錯了;
  • 沒拿到值懵逼了;
  • 某一天和服務(wù)端約定的某個字段變更了,沒能更新所有用到它的地方;
  • ...

為了解決這些問題,很多處理JSON的開源庫應(yīng)運而生。在Swift中,這些開源庫主要朝著兩個方向努力:

  • 保持JSON語義,直接解析JSON,但通過封裝使調(diào)用方式更優(yōu)雅、更安全;
  • 預(yù)定義Model類,將JSON反序列化為類實例,再使用這些實例;

對于1,使用最廣、評價***的庫非 SwiftyJSON 莫屬,它很能代表這個方向的核心。它本質(zhì)上仍然是根據(jù)JSON結(jié)構(gòu)去取值,使用起來順手、清晰。但也正因如此,這種做法沒能妥善解決上述的幾個問題,因為Key、路徑、類型仍然需要開發(fā)者去指定;

對于2,我個人覺得這是更合理的方式。由于Model類的存在,JSON的解析和使用都受到了定義的約束,只要客戶端和服務(wù)端約定好了這個Model類,客戶端定義后,在業(yè)務(wù)中使用數(shù)據(jù)時就可以享受到語法檢查、屬性預(yù)覽、屬性補全等好處,而且一旦數(shù)據(jù)定義變更,編譯器會強制所有用到的地方都改過來才能編譯通過,非常安全。這個方向上,開源庫們做的工作,主要就是把JSON文本反序列化到Model類上了。這一類JSON庫有 ObjectMapper、JSONNeverDie、HandyJSON 等。而 HandyJSON 是其中使用最舒服的一個庫,本文將介紹用 HandyJSON 來進行Model和JSON間的互相轉(zhuǎn)換。

項目地址:https://github.com/alibaba/handyjson

為什么用HandyJSON

在Swift中把JSON反序列化到Model類,在HandyJSON出現(xiàn)以前,主要使用兩種方式:

  • 讓Model類繼承自NSObject,然后class_copyPropertyList()方法獲取屬性名作為Key,從JSON中取得Value,再通過Objective-C runtime支持的KVC機制為類屬性賦值;如JSONNeverDie;
  • 支持純Swift類,但要求開發(fā)者實現(xiàn)Mapping函數(shù),使用重載的運算符進行賦值,如ObjectMapper;

這兩者都有顯而易見的缺點。前者要求Model繼承自NSObject,非常不優(yōu)雅,且直接否定了用struct來定義Model的方式;后者的Mapping函數(shù)要求開發(fā)者自定義,在其中指明每個屬性對應(yīng)的JSON字段名,代碼侵入大,且仍然容易發(fā)生拼寫錯誤、維護困難等問題。

而HandyJSON另辟蹊徑,采用Swift反射+內(nèi)存賦值的方式來構(gòu)造Model實例,規(guī)避了上述兩個方案遇到的問題。

把JSON轉(zhuǎn)換為Model

簡單類型

某個Model類想支持通過HandyJSON來反序列化,只需要在定義時,實現(xiàn)HandyJSON協(xié)議,這個協(xié)議只要求實現(xiàn)一個空的init()函數(shù)。

比如我們和服務(wù)端約定了一個Animal數(shù)據(jù),里面有name/id/num字段,那么我們這樣定義Animal類:

  1. class Animal: HandyJSON { 
  2.     var name: String? 
  3.     var id: String? 
  4.     var num: Int
  5.  
  6.     required init() {} 

然后假設(shè)我們從服務(wù)端拿到這樣一個JSON文本: 

  1. let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" 

引入HandyJSON以后,我們就可以這樣來做反序列化了:

  1. if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) { 
  2.     print(animal.name
  3.     print(animal.id) 
  4.     print(animal.num) 

簡單吧~

支持Struct

如果Model的定義是struct,由于Swift中struct提供了默認構(gòu)造函數(shù),所以就不需要再實現(xiàn)空的init()函數(shù)了。但需要注意,如果你為strcut指定了別的構(gòu)造函數(shù),那么就需要保留一個空的實現(xiàn)。

  1. struct Animal: HandyJSON { 
  2.     var name: String? 
  3.     var id: String? 
  4.     var num: Int
  5.  
  6. let jsonString = "{\"name\":\"cat\",\"id\":\"12345\",\"num\":180}" 
  7.  
  8. if let animal = JSONDeserializer<Animal>.deserializeFrom(json: jsonString) { 
  9.     print(animal) 

比較復(fù)雜的類型

HandyJSON支持在類定義里使用各種形式的基本屬性,包括可選(?),隱式解包可選(!),數(shù)組(Array),字典(Dictionary),Objective-C基本類型(NSString、NSNumber),各種類型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面這個看起來比較復(fù)雜的類型:

  1. class Cat: HandyJSON { 
  2.     var id: Int64! 
  3.     var name: String! 
  4.     var friend: [String]? 
  5.     var weight: Double
  6.     var alive: Bool = true 
  7.     var color: NSString? 
  8.  
  9.     required init() {} 

一樣輕松轉(zhuǎn)換:

  1. let jsonString = "{\"id\":1234567,\"name\":\"Kitty\",\"friend\":[\"Tom\",\"Jack\",\"Lily\",\"Black\"],\"weight\":15.34,\"alive\":false,\"color\":\"white\"}" 
  2.  
  3. if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) { 
  4.     print(cat.xxx) 

嵌套的Model類

如果Model類中的某個屬性是另一個自定義的Model類,那么只要那個Model類也實現(xiàn)了HandyJSON協(xié)議,就一樣可以轉(zhuǎn)換:

  1. class Component: HandyJSON { 
  2.     var aInt: Int
  3.     var aString: String? 
  4.  
  5.     required init() {} 
  6.  
  7. class Composition: HandyJSON { 
  8.     var aInt: Int
  9.     var comp1: Component? 
  10.     var comp2: Component? 
  11.  
  12.     required init() {} 
  13.  
  14. let jsonString = "{\"num\":12345,\"comp1\":{\"aInt\":1,\"aString\":\"aaaaa\"},\"comp2\":{\"aInt\":2,\"aString\":\"bbbbb\"}}" 
  15.  
  16. if let composition = JSONDeserializer<Composition>.deserializeFrom(json: jsonString) { 
  17.     print(composition) 

指定反序列化JSON中某個節(jié)點

有時候服務(wù)端返回給我們的JSON文本包含了大量的狀態(tài)信息,和Model無關(guān),比如statusCode,debugMessage等,或者有用的數(shù)據(jù)是在某個節(jié)點以下,那么我們可以指定反序列化哪個節(jié)點:

  1. class Cat: HandyJSON { 
  2.     var id: Int64! 
  3.     var name: String! 
  4.  
  5.     required init() {} 
  6.  
  7.  
  8. // 服務(wù)端返回了這個JSON,我們想解析的只有data里的cat 
  9. let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}" 
  10.  
  11. // 那么,我們指定解析 "data.cat",通過點來表達路徑 
  12. if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString, designatedPath: "data.cat") { 
  13.     print(cat.name

有繼承關(guān)系的Model類

如果某個Model類繼承自另一個Model類,只需要這個父Model類實現(xiàn)HandyJSON協(xié)議就可以:

  1. class Animal: HandyJSON { 
  2.     var id: Int
  3.     var color: String? 
  4.  
  5.     required init() {} 
  6.  
  7.  
  8. class Cat: Animal { 
  9.     var name: String? 
  10.  
  11.     required init() {} 
  12.  
  13. let jsonString = "{\"id\":12345,\"color\":\"black\",\"name\":\"cat\"}" 
  14.  
  15. if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString) { 
  16.     print(cat) 

自定義解析方式

HandyJSON還提供了一個擴展能力,就是允許自行定義Model類某個字段的解析Key、解析方式。我們經(jīng)常會有這樣的需求:

  • 某個Model中,我們不想使用和服務(wù)端約定的key作為屬性名,想自己定一個;
  • 有些類型如enum、tuple是無法直接從JSON中解析出來的,但我們在Model類中有這樣的屬性;

HandyJSON協(xié)議提供了一個可選的mapping()函數(shù),我們可以在其中指定某個字段用什么Key、或者用什么方法從JSON中解析出它的值。如我們有一個Model類和一個服務(wù)端返回的JSON串:

  1. class Cat: HandyJSON { 
  2.     var id: Int64! 
  3.     var name: String! 
  4.     var parent: (String, String)? 
  5.  
  6.     required init() {} 
  7.  
  8. let jsonString = "{\"cat_id\":12345,\"name\":\"Kitty\",\"parent\":\"Tom/Lily\"}" 

可以看到,Cat類的id屬性和JSON文本中的Key是對應(yīng)不上的;而對于parent這個屬性來說,它是一個元組,做不到從JSON中的"Tom/Lily"解析出來。所以我們要定義一個Mapping函數(shù)來做這兩個支持:

  1. class Cat: HandyJSON { 
  2.     var id: Int64! 
  3.     var name: String! 
  4.     var parent: (String, String)? 
  5.  
  6.     required init() {} 
  7.  
  8.     func mapping(mapper: HelpingMapper) { 
  9.         // 指定 id 字段用 "cat_id" 去解析 
  10.         mapper.specify(property: &id, name"cat_id"
  11.  
  12.         // 指定 parent 字段用這個方法去解析 
  13.         mapper.specify(property: &parent) { (rawString) -> (String, String) in 
  14.             let parentNames = rawString.characters.split{$0 == "/"}.map(String.init) 
  15.             return (parentNames[0], parentNames[1]) 
  16.         } 
  17.     } 

就這樣,HandyJSON***地幫我們進行了JSON到Model類的轉(zhuǎn)換。如此方便,這也是將其命名為Handy的由來。

把Model轉(zhuǎn)換為JSON文本

HandyJSON還提供了把Model類序列化為JSON文本的能力,簡直無情。

基本類型

如果只需要進行序列化,那么在定義Model類時,不需要做任何特殊的改動。任何一個類的實例,直接調(diào)用HandyJSON的序列化方法去序列化,就能得到JSON字符串了。

  1. class Animal { 
  2.     var name: String? 
  3.     var height: Int
  4.  
  5.     init(name: String, height: Int) { 
  6.         self.name = name 
  7.         self.height = height 
  8.     } 
  9.  
  10. let cat = Animal(name"cat", height: 30) 
  11.  
  12. // 序列化為簡單JSON文本 
  13. if let jsonStr = JSONSerializer.serialize(model: cat).toJSON() { 
  14.     print("simple json string: ", jsonStr) 
  15.  
  16. // 序列化為格式化的JSON文本 
  17. if let prettifyJSON = JSONSerializer.serialize(model: cat).toPrettifyJSON() { 
  18.     print("prettify json string: ", prettifyJSON) 
  19.  
  20. // 序列化為簡單字典 
  21. if let dict = JSONSerializer.serialize(model: cat).toSimpleDictionary() { 
  22.     print("dictionary: ", dict) 

復(fù)雜類型

即使Model類中有別的Model類啥的,都一樣支持。

  1. enum Gender { 
  2.     case Male 
  3.     case Female 
  4.  
  5. struct Subject { 
  6.     var id: Int64? 
  7.     var name: String? 
  8.  
  9.     init(id: Int64, name: String) { 
  10.         self.id = id 
  11.         self.name = name 
  12.     } 
  13.  
  14. class Student { 
  15.     var name: String? 
  16.     var gender: Gender? 
  17.     var subjects: [Subject]? 
  18.  
  19. let student = Student() 
  20. student.name = "Jack" 
  21. student.gender = .Female 
  22. student.subjects = [Subject(id: 1, name"math"), Subject(id: 2, name"English"), Subject(id: 3, name"Philosophy")] 
  23.  
  24. if let jsonStr = JSONSerializer.serialize(model: student).toJSON() { 
  25.     print("simple json string: ", jsonStr) 
  26. if let prettifyJSON = JSONSerializer.serialize(model: student).toPrettifyJSON() { 
  27.     print("prettify json string: ", prettifyJSON) 
  28. if let dict = JSONSerializer.serialize(model: student).toSimpleDictionary() { 
  29.     print("dictionary: ", dict) 

總結(jié)

有了HandyJSON的支持,現(xiàn)在我們可以開心地在Swift中使用JSON了。這個庫支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建議,可以去 https://github.com/alibaba/handyjson 提issue.

為何開發(fā)HandyJSON

我所在iOS團隊是從去年11月份切Swift的。我們服務(wù)端和客戶端數(shù)據(jù)交互格式一直用的是JSON,而當時Swift中處理JSON名氣比較大的庫貌似只有SwiftyJSON,工程切到Swift后,我們也用了這個庫。用上之后,需求是滿足了,但是對一些復(fù)雜的Model,代碼寫得看起來非常糟糕,因為每次取值都需要 json["akey"]["bkey"]["ckey"].value 形式,寫的時候?qū)χ臋n沒覺得啥問題,但過后在脫離文檔的情況下,通篇都是字符串表達的key,很難從代碼中感覺出Model結(jié)構(gòu)。所以我們都會把一段sample數(shù)據(jù)寫在注釋里。但仍然比較凌亂,另外key寫錯了debug起來也費勁,一個大小寫問題有時候debug半天。

于是我們進化了一下,先寫好Model,然后Model類中寫convert函數(shù),也用上了KVC遍歷key賦值。寫起來舒服多了,但還是麻煩,而且要求每一個類都繼承自NSObject。不久后,我們認識了ObjectMapper庫,二話不說,就換了上去。世界頓時干凈多了。

但還是感覺差了一點,因為ObjectMapper需要自己指明映射關(guān)系。通常JSON中key和Model中字段名都是一致的,每次都要額外寫一坨東西,總覺得多余,字段有改動的時候也費勁。新來剛接觸Swift的同事,也表示不太舒服,因為他們之前使用的JSON反序列化庫,無論Java中還是Objective-C中,都是自然使用Model字段名去取值的。

所以就想著研究一下,Swift中能不能做到這種效果。

HandyJSON的設(shè)計思路

Swift中存在的限制

無論是Java或者Objective-C中的JSON反序列化庫,通常都是,在運行時獲取Model的字段名集合,遍歷該集合,拿Key去JSON中取值并完成賦值。這些步驟,Java依賴反射機制可以實現(xiàn),Objective-C通過class_copyPropertyList方法加上KVC機制,也能輕松實現(xiàn)。而Swift會卡在***一步:無法賦值。

Swift的反射是只讀的,就是說,我們能在運行時獲取一個Model實例的所有字段、字段值,但卻無法給它賦值。事實上,我們拿到的value是原值的一個只讀拷貝,即使獲取到這個拷貝的地址寫入新值,也是無效的。

  1. class Animal { 
  2.     var name: String? 
  3.  
  4. Mirror(reflecting: Animal()).children.forEach { (child) in 
  5.     print(child.label ?? "", child.value) // working correctly 
  6.     child.value = "cat" // error,不能直接賦值 

而且迄今,蘋果官網(wǎng)文檔上對實現(xiàn)反射機制的Mirror類仍然是這么描述: Mirrors are used by playgrounds and the debugger,態(tài)度非常含糊,似乎不太鼓勵,但生產(chǎn)中很多類庫都用上了。只能說,蘋果不會輕易撤下這個能力,但期待它對這個能力做出改進(比如支持運行時賦值),是希望渺茫的。

如何繞過限制

最簡單的方式,就是在Swift中定義Model時繼承NSObject,讓這個Model的實例存在于objc運行時中,上述的class_copyPropertyList方法和KVC就能用上了。目前看見的Swift中不需要指明映射關(guān)系的JSON庫,都是這種方式。

然后就是以O(shè)bjectMapper為代表的庫,通過運算符重載,在指定映射關(guān)系時完成賦值。走這一類實現(xiàn)的庫也非常多了。

但我想做到的是,既支持運行在Swift運行時的純Swift類,又不需要顯示指定每一個字段的映射關(guān)系。那么,不能走反射賦值,那就直接寫入內(nèi)存吧。

具體實現(xiàn)

Swift中,一個類實例的內(nèi)存布局是有規(guī)律的:

  • 32位機器上,類前面有4+8個字節(jié)存儲meta信息,64位機器上,有8+8個字節(jié);
  • 內(nèi)存中,字段從前往后有序排列;
  • 如果該類繼承自某一個類,那么父類的字段在前;
  • Optional會增加一個字節(jié)來存儲.None/.Some信息;
  • 每個字段需要考慮內(nèi)存對齊;

這方面基本沒有官方的資料參考,上述規(guī)律一些是從網(wǎng)上其他大神的總結(jié)中收集,一些從Clang的一些說明文檔中挖掘,還有一些是自己在playground里試出來的。開始心里沒什么底,但把HandyJSON實現(xiàn)出來使用這么久了,還沒出過狀況,可以認為是靠譜的。

有法子計算內(nèi)存布局,剩下的事情就比較簡單了。對一個實例:

  • 獲取它的起始指針,移動到有效起點;
  • 通過Mirror獲取每一個字段的字段名和字段類型;
  • 根據(jù)字段名在JSON中取值,轉(zhuǎn)換為和字段一樣的類型,通過指針寫入;
  • 根據(jù)本字段類型的占位大小和下一個字段類型計算下一個字段的對齊起點;
  • 移動指針,繼續(xù)處理;

獲取類實例的起始指針

Swift中,獲取struct實例起始指針和獲取class實例起始指針的方法是不一樣的,和語言版本也相關(guān)。在Swift3中:

  1. // 獲取struct實例起始指針 
  2. mutating func headPointerOfStruct() -> UnsafeMutablePointer<Byte> { 
  3.  
  4.     return withUnsafeMutablePointer(to: &self) { 
  5.         return UnsafeMutableRawPointer($0).bindMemory(to: Byte.self, capacity: MemoryLayout<Self>.stride) 
  6.     } 
  7.  
  8. // 獲取class實例起始指針 
  9. mutating func headPointerOfClass() -> UnsafeMutablePointer<Byte> { 
  10.  
  11.     let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque() 
  12.     let mutableTypedPointer = opaquePointer.bindMemory(to: Byte.self, capacity: MemoryLayout<Self>.stride) 
  13.     return UnsafeMutablePointer<Byte>(mutableTypedPointer) 

通過Mirror獲取字段名、類型

  1. Mirror(reflecting: Animal()).children.forEach { (child) in 
  2.     print(child.label ?? "") // 獲取字段名 
  3.     print(type(of: child.value)) // 獲取字段類型 

計算Model的每個屬性字段占位大小

Swift3暴露了兩個接口用于計算類型占位大?。篗emoryLayout.size(ofValue: T)和MemoryLayout.size。這兩者都沒辦法直接用,因為:

  • 對于每個屬性,我們目前只持有它的起始指針,而不是它的實例,***個接口用不上;
  • 對于每個屬性,我們是在運行時中獲取到它的類型,已經(jīng)沒辦法再實例化出泛型類型MemoryLayout<T>來計算size。所以,我引入了HandyJSON類,在擴展中實現(xiàn)函數(shù):
  1. protocol HandyJSON { 
  2.  
  3. extension HandyJSON { 
  4.     static func size() -> Int { 
  5.         return MemoryLayout<Self>.size 
  6.     } 

于是,對于每一個實現(xiàn)HandyJSON協(xié)議的Model類T,直接調(diào)用 T.size() 就能獲取到T的size了。

內(nèi)存對齊的影響

類實例的屬性并不是直接按照各自占位大小依次往下排列的,不然事情就簡單了。和C/C++一樣,Swift中實例內(nèi)存布局也考慮了內(nèi)存對齊。翻閱了Swift的docs和LLVM的一些資料,MemoryLayout提供了一個接口:MemoryLayout.alignment,對齊的規(guī)則為,每個字段的起始地址必須為alignment值的整數(shù)倍。細節(jié)的出處我已經(jīng)忘記。當時進行了一些復(fù)雜類型的測試后,認定它符合事實。所以HandyJSON中計算下一個字段起始地址的函數(shù)為:

  1. // Returns the offset to the next integer that is greater than 
  2. // or equal to Value and is a multiple of Align. Align must be 
  3. // non-zero. 
  4. static func offsetToAlignment(value: Int, align: Int) -> Int { 
  5.     let m = value % align 
  6.     return m == 0 ? 0 : (align - m) 

其他情況

基本類型按照上述方法處理就可以了,還有可選類型、數(shù)組類型、字典類型,通過遍歷、遞歸解析等方式,處理方法也類似。如數(shù)組:

  1. extension Array: ArrayTypeProtocol { 
  2.     static func getWrappedType() -> Any.Type { 
  3.         return Element.self 
  4.     } 
  5.  
  6.     static func castArrayType(arr: [Any]) -> Array<Element> { 
  7.         return arr.map({ (p) -> Element in 
  8.             return p as! Element 
  9.         }) 
  10.     } 

獲取到Array泛型實參類型,然后構(gòu)造出該類型的一個數(shù)組,完成賦值就可以了。

結(jié)語

主要流程就是這樣了,也比較簡單,剩下處理繼承、組合等情況,只是實現(xiàn)問題,就不再贅述了??傆X得自己對Swift指針這一套設(shè)施理解還不是很到位,也許有更好的用法,比如說,完全不需要空的init()函數(shù)就可以初始化出一個類的實例。有同學(xué)在這方面有更深入理解,有什么意見或者建議的,歡迎交流~

參考

  • 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
責(zé)任編輯:龐桂玉 來源: 移動開發(fā)前線
相關(guān)推薦

2016-04-11 09:58:53

iOSJSONModel

2015-10-28 09:55:39

Swift解析生產(chǎn)庫

2015-08-14 11:37:37

Swift語言中文版

2016-06-07 14:42:18

Swift設(shè)計

2014-07-22 09:01:53

SwiftJSON

2014-08-20 10:40:29

Xcode 6

2017-02-27 16:28:00

2022-02-25 09:03:49

工具Swift項目

2015-04-17 16:07:11

swiftOC

2010-06-30 11:16:50

SQL Server

2014-06-03 10:44:20

Swift開發(fā)語言

2015-01-12 13:04:39

Swift開源項目匯總

2015-12-07 09:46:26

swift編程開源

2014-06-04 10:52:56

Swift蘋果iOS

2023-08-28 08:40:23

Sonic開發(fā)JSON

2022-10-13 21:07:48

數(shù)據(jù)庫SQL Server

2021-04-21 00:10:12

對象JSON插件

2014-06-06 14:25:03

iOS 8SwiftWWDC2014

2014-07-16 09:41:12

Swift傳統(tǒng)編程

2014-07-29 10:49:23

Swift
點贊
收藏

51CTO技術(shù)棧公眾號