精华内容
参与话题
问答
  • 刚开始时感觉有点扯淡 ,但是慢慢的机会发现 原来swift会让你的程序开发变得如此美妙,下面就是我理解的MVC中的M 修改 import 语句来引入 UIKit 代替 Foundation: import UIKit 默认 Swift 文件引入 ...


    Oc中会有很多第三方,而且数据模型比较好理解 。但是在接触swift之后。刚开始时感觉有点扯淡 ,但是慢慢的机会发现 原来swift会让你的程序开发变得如此美妙,下面就是我理解的MVC中的M层


    修改 import 语句来引入 UIKit 代替 Foundation:

     import UIKit

    默认 Swift 文件引入 Foundation 框架所以可以直接使用其中的数据结构。因为要使用 UIKit 的类,所以需要 import UIKit。导入 UIKit 同时让你能访问 Foundation,所以可以移除多余的 import Foundation





    class Meal {
         // MARK: Properties
    
         var name: String
         var photo: UIImage?
         var rating: Int
     }


    init?(name: String, photo: UIImage?, rating: Int) {
        // Initialize stored properties.
        self.name = name
        self.photo = photo
        self.rating = rating
    
        // Initialization should fail if there is no name or if the rating is negative.
        if name.isEmpty || rating < 0 {
            return nil
        }
    }

    展开全文
  • 几乎所有App项目都设计到UITableView的开发,so 在cell与model的数据捆绑的时候,楼主选择了在Adapter进行。,并返回cell给cellForRowAtIndexPath代理。
  • JSON是移动端开发常用的应用数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。 但在编程的时候,处理JSON是一件麻烦事...

    背景

    JSON是移动端开发常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。

    但在编程的时候,处理JSON是一件麻烦事。在不引入任何轮子的情况下,我们通常需要先把JSON转为Dictionary,然后还要记住每个数据对应的Key,用这个Key在Dictionary中取出对应的Value来使用。这个过程我们会犯各种错误:

    • Key拼写错了;
    • 路径写错了;
    • 类型搞错了;
    • 没拿到值懵逼了;
    • 某一天和服务端约定的某个字段变更了,没能更新所有用到它的地方;
    • ...

    为了解决这些问题,很多处理JSON的开源库应运而生。在Swift中,这些开源库主要朝着两个方向努力:

    • 保持JSON语义,直接解析JSON,但通过封装使调用方式更优雅、更安全;
    • 预定义Model类,将JSON反序列化为类实例,再使用这些实例;

    对于1,使用最广、评价最好的库非 SwiftyJSON 莫属,它很能代表这个方向的核心。它本质上仍然是根据JSON结构去取值,使用起来顺手、清晰。但也正因如此,这种做法没能妥善解决上述的几个问题,因为Key、路径、类型仍然需要开发者去指定;

    对于2,我个人觉得这是更合理的方式。由于Model类的存在,JSON的解析和使用都受到了定义的约束,只要客户端和服务端约定好了这个Model类,客户端定义后,在业务中使用数据时就可以享受到语法检查、属性预览、属性补全等好处,而且一旦数据定义变更,编译器会强制所有用到的地方都改过来才能编译通过,非常安全。这个方向上,开源库们做的工作,主要就是把JSON文本反序列化到Model类上了。这一类JSON库有 ObjectMapper、JSONNeverDie、HandyJSON 等。而 HandyJSON 是其中使用最舒服的一个库,本文将介绍用 HandyJSON 来进行Model和JSON间的互相转换。

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

    为什么用HandyJSON

    在Swift中把JSON反序列化到Model类,在HandyJSON出现以前,主要使用两种方式:

    • 让Model类继承自NSObject,然后class_copyPropertyList()方法获取属性名作为Key,从JSON中取得Value,再通过Objective-C runtime支持的KVC机制为类属性赋值;如JSONNeverDie;
    • 支持纯Swift类,但要求开发者实现Mapping函数,使用重载的运算符进行赋值,如ObjectMapper;

    这两者都有显而易见的缺点。前者要求Model继承自NSObject,非常不优雅,且直接否定了用struct来定义Model的方式;后者的Mapping函数要求开发者自定义,在其中指明每个属性对应的JSON字段名,代码侵入大,且仍然容易发生拼写错误、维护困难等问题。

    而HandyJSON另辟蹊径,采用Swift反射+内存赋值的方式来构造Model实例,规避了上述两个方案遇到的问题。

    把JSON转换为Model

    简单类型

    某个Model类想支持通过HandyJSON来反序列化,只需要在定义时,实现HandyJSON协议,这个协议只要求实现一个空的init()函数。

    比如我们和服务端约定了一个Animal数据,里面有name/id/num字段,那么我们这样定义Animal类:

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

    然后假设我们从服务端拿到这样一个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提供了默认构造函数,所以就不需要再实现空的init()函数了。但需要注意,如果你为strcut指定了别的构造函数,那么就需要保留一个空的实现。

    
    
    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) 

    比较复杂的类型

    HandyJSON支持在类定义里使用各种形式的基本属性,包括可选(?),隐式解包可选(!),数组(Array),字典(Dictionary),Objective-C基本类型(NSString、NSNumber),各种类型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面这个看起来比较复杂的类型:

    
    
    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() {} 

    一样轻松转换:

    
    
    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类也实现了HandyJSON协议,就一样可以转换:

    
    
    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中某个节点

    有时候服务端返回给我们的JSON文本包含了大量的状态信息,和Model无关,比如statusCode,debugMessage等,或者有用的数据是在某个节点以下,那么我们可以指定反序列化哪个节点:

    
    
    1. class Cat: HandyJSON { 
    2.     var id: Int64! 
    3.     var name: String! 
    4.  
    5.     required init() {} 
    6.  
    7.  
    8. // 服务端返回了这个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

    有继承关系的Model类

    如果某个Model类继承自另一个Model类,只需要这个父Model类实现HandyJSON协议就可以:

    
    
    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、解析方式。我们经常会有这样的需求:

    • 某个Model中,我们不想使用和服务端约定的key作为属性名,想自己定一个;
    • 有些类型如enum、tuple是无法直接从JSON中解析出来的,但我们在Model类中有这样的属性;

    HandyJSON协议提供了一个可选的mapping()函数,我们可以在其中指定某个字段用什么Key、或者用什么方法从JSON中解析出它的值。如我们有一个Model类和一个服务端返回的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是对应不上的;而对于parent这个属性来说,它是一个元组,做不到从JSON中的"Tom/Lily"解析出来。所以我们要定义一个Mapping函数来做这两个支持:

    
    
    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类的转换。如此方便,这也是将其命名为Handy的由来。

    把Model转换为JSON文本

    HandyJSON还提供了把Model类序列化为JSON文本的能力,简直无情。

    基本类型

    如果只需要进行序列化,那么在定义Model类时,不需要做任何特殊的改动。任何一个类的实例,直接调用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) 

    复杂类型

    即使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) 

    总结

    有了HandyJSON的支持,现在我们可以开心地在Swift中使用JSON了。这个库支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建议,可以去 https://github.com/alibaba/handyjson 提issue.

    为何开发HandyJSON

    我所在iOS团队是从去年11月份切Swift的。我们服务端和客户端数据交互格式一直用的是JSON,而当时Swift中处理JSON名气比较大的库貌似只有SwiftyJSON,工程切到Swift后,我们也用了这个库。用上之后,需求是满足了,但是对一些复杂的Model,代码写得看起来非常糟糕,因为每次取值都需要 json["akey"]["bkey"]["ckey"].value 形式,写的时候对着文档没觉得啥问题,但过后在脱离文档的情况下,通篇都是字符串表达的key,很难从代码中感觉出Model结构。所以我们都会把一段sample数据写在注释里。但仍然比较凌乱,另外key写错了debug起来也费劲,一个大小写问题有时候debug半天。

    于是我们进化了一下,先写好Model,然后Model类中写convert函数,也用上了KVC遍历key赋值。写起来舒服多了,但还是麻烦,而且要求每一个类都继承自NSObject。不久后,我们认识了ObjectMapper库,二话不说,就换了上去。世界顿时干净多了。

    但还是感觉差了一点,因为ObjectMapper需要自己指明映射关系。通常JSON中key和Model中字段名都是一致的,每次都要额外写一坨东西,总觉得多余,字段有改动的时候也费劲。新来刚接触Swift的同事,也表示不太舒服,因为他们之前使用的JSON反序列化库,无论Java中还是Objective-C中,都是自然使用Model字段名去取值的。

    所以就想着研究一下,Swift中能不能做到这种效果。

    HandyJSON的设计思路

    Swift中存在的限制

    无论是Java或者Objective-C中的JSON反序列化库,通常都是,在运行时获取Model的字段名集合,遍历该集合,拿Key去JSON中取值并完成赋值。这些步骤,Java依赖反射机制可以实现,Objective-C通过class_copyPropertyList方法加上KVC机制,也能轻松实现。而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,不能直接赋值 

    而且迄今,苹果官网文档上对实现反射机制的Mirror类仍然是这么描述: Mirrors are used by playgrounds and the debugger,态度非常含糊,似乎不太鼓励,但生产中很多类库都用上了。只能说,苹果不会轻易撤下这个能力,但期待它对这个能力做出改进(比如支持运行时赋值),是希望渺茫的。

    如何绕过限制

    最简单的方式,就是在Swift中定义Model时继承NSObject,让这个Model的实例存在于objc运行时中,上述的class_copyPropertyList方法和KVC就能用上了。目前看见的Swift中不需要指明映射关系的JSON库,都是这种方式。

    然后就是以ObjectMapper为代表的库,通过运算符重载,在指定映射关系时完成赋值。走这一类实现的库也非常多了。

    但我想做到的是,既支持运行在Swift运行时的纯Swift类,又不需要显示指定每一个字段的映射关系。那么,不能走反射赋值,那就直接写入内存吧。

    具体实现

    Swift中,一个类实例的内存布局是有规律的:

    • 32位机器上,类前面有4+8个字节存储meta信息,64位机器上,有8+8个字节;
    • 内存中,字段从前往后有序排列;
    • 如果该类继承自某一个类,那么父类的字段在前;
    • Optional会增加一个字节来存储.None/.Some信息;
    • 每个字段需要考虑内存对齐;

    这方面基本没有官方的资料参考,上述规律一些是从网上其他大神的总结中收集,一些从Clang的一些说明文档中挖掘,还有一些是自己在playground里试出来的。开始心里没什么底,但把HandyJSON实现出来使用这么久了,还没出过状况,可以认为是靠谱的。

    有法子计算内存布局,剩下的事情就比较简单了。对一个实例:

    • 获取它的起始指针,移动到有效起点;
    • 通过Mirror获取每一个字段的字段名和字段类型;
    • 根据字段名在JSON中取值,转换为和字段一样的类型,通过指针写入;
    • 根据本字段类型的占位大小和下一个字段类型计算下一个字段的对齐起点;
    • 移动指针,继续处理;

    获取类实例的起始指针

    Swift中,获取struct实例起始指针和获取class实例起始指针的方法是不一样的,和语言版本也相关。在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暴露了两个接口用于计算类型占位大小:MemoryLayout.size(ofValue: T)和MemoryLayout.size。这两者都没办法直接用,因为:

    • 对于每个属性,我们目前只持有它的起始指针,而不是它的实例,第一个接口用不上;
    • 对于每个属性,我们是在运行时中获取到它的类型,已经没办法再实例化出泛型类型MemoryLayout<T>来计算size。所以,我引入了HandyJSON类,在扩展中实现函数:
    
    
    1. protocol HandyJSON { 
    2.  
    3. extension HandyJSON { 
    4.     static func size() -> Int { 
    5.         return MemoryLayout<Self>.size 
    6.     } 

    于是,对于每一个实现HandyJSON协议的Model类T,直接调用 T.size() 就能获取到T的size了。

    内存对齐的影响

    类实例的属性并不是直接按照各自占位大小依次往下排列的,不然事情就简单了。和C/C++一样,Swift中实例内存布局也考虑了内存对齐。翻阅了Swift的docs和LLVM的一些资料,MemoryLayout提供了一个接口:MemoryLayout.alignment,对齐的规则为,每个字段的起始地址必须为alignment值的整数倍。细节的出处我已经忘记。当时进行了一些复杂类型的测试后,认定它符合事实。所以HandyJSON中计算下一个字段起始地址的函数为:

    
    
    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) 

    其他情况

    基本类型按照上述方法处理就可以了,还有可选类型、数组类型、字典类型,通过遍历、递归解析等方式,处理方法也类似。如数组:

    
    
    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泛型实参类型,然后构造出该类型的一个数组,完成赋值就可以了。

    结语

    主要流程就是这样了,也比较简单,剩下处理继承、组合等情况,只是实现问题,就不再赘述了。总觉得自己对Swift指针这一套设施理解还不是很到位,也许有更好的用法,比如说,完全不需要空的init()函数就可以初始化出一个类的实例。有同学在这方面有更深入理解,有什么意见或者建议的,欢迎交流~




    作者:周卓
    来源:51CTO
    展开全文
  • JSON是移动端开发常用的应用数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。 但在编程的时候,处理JSON是一件麻烦...

    背景

    JSON是移动端开发常用的应用层数据交换协议。最常见的场景便是,客户端向服务端发起网络请求,服务端返回JSON文本,然后客户端解析这个JSON文本,再把对应数据展现到页面上。

    但在编程的时候,处理JSON是一件麻烦事。在不引入任何轮子的情况下,我们通常需要先把JSON转为Dictionary,然后还要记住每个数据对应的Key,用这个Key在Dictionary中取出对应的Value来使用。这个过程我们会犯各种错误:

    • Key拼写错了;
    • 路径写错了;
    • 类型搞错了;
    • 没拿到值懵逼了;
    • 某一天和服务端约定的某个字段变更了,没能更新所有用到它的地方;
    • ...

    为了解决这些问题,很多处理JSON的开源库应运而生。在Swift中,这些开源库主要朝着两个方向努力:

    1. 保持JSON语义,直接解析JSON,但通过封装使调用方式更优雅、更安全;
    2. 预定义Model类,将JSON反序列化为类实例,再使用这些实例;

    对于1,使用最广、评价最好的库非 SwiftyJSON 莫属,它很能代表这个方向的核心。它本质上仍然是根据JSON结构去取值,使用起来顺手、清晰。但也正因如此,这种做法没能妥善解决上述的几个问题,因为Key、路径、类型仍然需要开发者去指定;

    对于2,我个人觉得这是更合理的方式。由于Model类的存在,JSON的解析和使用都受到了定义的约束,只要客户端和服务端约定好了这个Model类,客户端定义后,在业务中使用数据时就可以享受到语法检查、属性预览、属性补全等好处,而且一旦数据定义变更,编译器会强制所有用到的地方都改过来才能编译通过,非常安全。这个方向上,开源库们做的工作,主要就是把JSON文本反序列化到Model类上了。这一类JSON库有ObjectMapperJSONNeverDieHandyJSON 等。而 HandyJSON 是其中使用最舒服的一个库,本文将介绍用 HandyJSON 来进行Model和JSON间的互相转换。

    为什么用HandyJSON

    在Swift中把JSON反序列化到Model类,在HandyJSON出现以前,主要使用两种方式:

    1. 让Model类继承自NSObject,然后class_copyPropertyList()方法获取属性名作为Key,从JSON中取得Value,再通过Objective-C runtime支持的KVC机制为类属性赋值;如JSONNeverDie

    2. 支持纯Swift类,但要求开发者实现Mapping函数,使用重载的运算符进行赋值,如ObjectMapper

    这两者都有显而易见的缺点。前者要求Model继承自NSObject,非常不优雅,且直接否定了用struct来定义Model的方式;后者的Mapping函数要求开发者自定义,在其中指明每个属性对应的JSON字段名,代码侵入大,且仍然容易发生拼写错误、维护困难等问题。

    HandyJSON另辟蹊径,采用Swift反射+内存赋值的方式来构造Model实例,规避了上述两个方案遇到的问题。

    把JSON转换为Model

    简单类型

    某个Model类想支持通过HandyJSON来反序列化,只需要在定义时,实现HandyJSON协议,这个协议只要求实现一个空的init()函数。

    比如我们和服务端约定了一个Animal数据,里面有name/id/num字段,那么我们这样定义Animal类:

    class Animal: HandyJSON {
        var name: String?
        var id: String?
        var num: Int?
    
        required init() {}
    }

    然后假设我们从服务端拿到这样一个JSON文本:

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

    引入HandyJSON以后,我们就可以这样来做反序列化了:

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

    简单吧~

    支持Struct

    如果Model的定义是struct,由于Swift中struct提供了默认构造函数,所以就不需要再实现空的init()函数了。但需要注意,如果你为strcut指定了别的构造函数,那么就需要保留一个空的实现。

    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)
    }

    比较复杂的类型

    HandyJSON支持在类定义里使用各种形式的基本属性,包括可选(?),隐式解包可选(!),数组(Array),字典(Dictionary),Objective-C基本类型(NSString、NSNumber),各种类型的嵌套([Int]?、[String]?、[Int]!、...)等等。比如下面这个看起来比较复杂的类型:

    class Cat: HandyJSON {
        var id: Int64!
        var name: String!
        var friend: [String]?
        var weight: Double?
        var alive: Bool = true
        var color: NSString?
    
        required init() {}
    }

    一样轻松转换:

    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类中的某个属性是另一个自定义的Model类,那么只要那个Model类也实现了HandyJSON协议,就一样可以转换:

    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中某个节点

    有时候服务端返回给我们的JSON文本包含了大量的状态信息,和Model无关,比如statusCodedebugMessage等,或者有用的数据是在某个节点以下,那么我们可以指定反序列化哪个节点:

    class Cat: HandyJSON {
        var id: Int64!
        var name: String!
    
        required init() {}
    }
    
    // 服务端返回了这个JSON,我们想解析的只有data里的cat
    let jsonString = "{\"code\":200,\"msg\":\"success\",\"data\":{\"cat\":{\"id\":12345,\"name\":\"Kitty\"}}}"
    
    // 那么,我们指定解析 "data.cat",通过点来表达路径
    if let cat = JSONDeserializer<Cat>.deserializeFrom(json: jsonString, designatedPath: "data.cat") {
        print(cat.name)
    }

    有继承关系的Model类

    如果某个Model类继承自另一个Model类,只需要这个父Model类实现HandyJSON协议就可以:

    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还提供了一个扩展能力,就是允许自行定义Model类某个字段的解析Key、解析方式。我们经常会有这样的需求:

    • 某个Model中,我们不想使用和服务端约定的key作为属性名,想自己定一个;
    • 有些类型如enumtuple是无法直接从JSON中解析出来的,但我们在Model类中有这样的属性;

    HandyJSON协议提供了一个可选的mapping()函数,我们可以在其中指定某个字段用什么Key、或者用什么方法从JSON中解析出它的值。如我们有一个Model类和一个服务端返回的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是对应不上的;而对于parent这个属性来说,它是一个元组,做不到从JSON中的"Tom/Lily"解析出来。所以我们要定义一个Mapping函数来做这两个支持:

    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 字段用这个方法去解析
            mapper.specify(property: &parent) { (rawString) -> (String, String) in
                let parentNames = rawString.characters.split{$0 == "/"}.map(String.init)
                return (parentNames[0], parentNames[1])
            }
        }
    }

    就这样,HandyJSON完美地帮我们进行了JSON到Model类的转换。真是太Handy了。

    把Model转换为JSON文本

    HandyJSON还提供了把Model类序列化为JSON文本的能力,简直无情。

    基本类型

    如果只需要进行序列化,那么在定义Model类时,不需要做任何特殊的改动。任何一个类的实例,直接调用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)
    
    // 序列化为简单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)
    }
    
    // 序列化为简单字典
    if let dict = JSONSerializer.serialize(model: cat).toSimpleDictionary() {
        print("dictionary: ", dict)
    }

    复杂类型

    即使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)
    }

    总结

    有了HandyJSON的支持,现在我们可以开心地在Swift中使用JSON了。这个库还在更新中,已经支持了Swift 2.2+, Swift 3.0+。如果大家有什么需求或者建议,快去https://github.com/alibaba/handyjson 给作者(哈哈没错就是我)提issue吧~~

    展开全文
  • 现在有一个需求:需要加载本地plist文件中的数据,数据格式如下图所示: 即:数组里存放2个数组,然后这2个数组又... fileprivate lazy var sections = [[SettingModel]]() 第一种方式:双层for循环 le...

    现在有一个需求:需要加载本地plist文件中的数据,数据格式如下图所示:

    在这里插入图片描述

    即:数组里存放2个数组,然后这2个数组又分别存放几个字典。

    要加载这些数据你可能首先会想到的就是第一种方式:代码如下

    首先定义一个数组存放数据:

    
        fileprivate  lazy var sections = [[SettingModel]]()
    

    第一种方式:双层for循环

          
            let path = Bundle.main.path(forResource: "settingPlist", ofType: "plist")
            let array = NSArray(contentsOfFile: path ?? "") ?? []
            
            for obj in array
            {
                let arr = obj as! [[String:AnyObject]]
                var sectionArr = [SettingModel]()
                for dict in arr
                {
                    let settingModel = SettingModel(JSON: dict)
                    guard let model = settingModel else {
                        continue
                    }
                    sectionArr.append(model)
                }
                sections.append(sectionArr)
    
           }
    

    当然这种方式可以实现需求,但是却没很好的利用swift中的map函数和flatMap函数

    第二种方式:map函数与flatMap结合使用,具体代码如下所示:

    
            sections =  array.map{ obj in
                            (obj as! [Any]).flatMap{ dict in
                             return SettingModel(JSON: (dict) as! [String:Any])
                }
            }
    

    当然此处的map也可以替换为flatMap,由于定义的属性sections是非可选类型[[SettingModel]],所以第二个则必须使用flatMap,避免此处返回的是可选类型,如果使用map则返回可选类型。

    第三种方式:使用compactMap,具体代码如下所示:

        sections =  array.compactMap{ obj in
                        (obj as! [Any]).compactMap{ dict in
                         return SettingModel(JSON: (dict) as! [String:Any])
            }
        }
    

    总结:优先使用第三种方式

    在swift中,map,flatMap及compactMap函数还有更加强大功能,大家可以自行学习了解一下。

    展开全文
  • 本篇主要整理了Swift中对于字符串、数组和 Model 的读写操作,其中还包括了序列化反序列化以及归档解档的知识。
  • Swift JSON

    2016-12-01 16:58:59
    在Objective-C中从服务器返回的json中拿数据是很麻烦、很不安全的事情,服务端稍稍出一点差池,就可能引起客服端的crash,这个就是为什么很多开源model层代码流行的原因。 但是很可惜在swift中,这个问题不但延续了...
  • 下面是我的新建的Swift学习交流群,欢迎大家一起来共同学习Swift。 ... ViewModel层,就是View和Model层的粘合剂 View层就是ViewController Model层就是用于处理数据的层 这样简单的描述了一下,大
  • Swift 构造器探究

    千次阅读 2015-04-24 19:52:50
    甚至在Model层只有对应属性的get,set访问器。而在Swift中无论是对于结构体(Structure)还是类(Class),如果其中存在存储属性(stored property),那么必须在合适的地方给它赋初始值,也就是初始化。不能让它们成为不...
  • SwiftUI是什么 SwiftUI在苹果开发者官网上的介绍是这样说的: 用一句话说就是,SwiftUI是苹果目前最新的全...苹果这次是把View层和Model层结合到了一起,即所谓的“所见皆所得”,比如这样: import SwiftUI struct
  • swift学习笔记二

    千次阅读 2015-06-27 21:55:51
    新建一个类,作为计算器的 Model ,这里将类命名为 CalculatorBrain import Foundation class CalculatorBrain { //1 private enum Op { case Operand(Double) case UnaryOperation(String, Double -> ...
  • swift 的 JSON很麻烦

    2016-03-11 15:26:37
    在Objective-C中从服务器返回的json中拿数据是很麻烦、很不安全的事情,服务端稍稍出一点差池,就可能引起客服端的crash,这个就是为什么很多开源model层代码流行的原因。 但是很可惜在swift中,这个问题不但延续了...
  • 谈谈 MVX 中的 Model

    2018-08-02 10:35:07
    在大多数 iOS 的项目中,Model 只是一个单纯的数据结构,你可以看到的绝大部分模型都是这样的: Swift struct User { enum Gender: String { case male = &amp;quot;male&amp;quot; case female = ...
  • Core Data是一个管理model层的框架 Core Data 框架提供以下特殊服务: 在基本文本处理上提供tracking和内置的undo , redo功能 change管理,维护object 间关系一致性 Object的lazy loading,
  • 在使用Alamofire进行网络请求的时候,相信大部分的同学都会封装一个抽象的NetworkLayer,如"APIManager" 或者 "NetworkModel"等等。但是位置业务功能增加,会渐渐混合各种请求,不够清晰,而Moya能很好地解决这类...
  • 在iOS开发中,后台返回的数据大多是JSON格式,对应地会被网络框架解析成Swift中的Dictionary、Array。由于数据类型的复杂、字段的繁多,直接使用Dictionary、Array会比较麻烦,比如items[0]["user"]["name"]这样的...
  • iOS开源库源码解析之Mantle

    千次阅读 2016-06-01 00:04:11
    来自Leo的原创博客,转载请著名出处我的StackOverflow这个源码解析系列的文章 AsnycDispalyKit SDWebImage Mantle(本文) AFNetworking(3.0) MBProgressHud ...而Model层的第三方库常用的库有以下几个
  • 加了一个DataModel, 加了一个AllWeekLists作为中间(方便测试商业逻辑) 我写的DataModel如下: // // DataModel.swift // AddPartChecklist // // Created by 黄豪杰 on 2017/7/18. // Copyright © 2017年 黄...
  • 原文:《A dumb UI is a good UI: Using MVP in iOS with swift》 链接:http://iyadagha.com/using-mvp-ios-swift/ ... 通常,View层由UIKit中的元素组成,这些元素由代码定义或xib文件定义,Model层包含应用程序的...
  • iOS判断iPhone型号

    2019-10-06 15:10:41
    链接:...http://stackoverflow.com/questions/26028918/ios-how-to-determine-iphone-model-in-swift 这种是在较高层次获取设备类型,返回的...

空空如也

1 2
收藏数 25
精华内容 10
关键字:

model层 swift