在iOS项目开发过程中,我们经常会用到将从服务器获取的 Json 转 model 的 *** 作,我们可以使用 Swift 提供的setValuesForKeys
或者 Objective-C 提供的setValuesForKeysWithDictionary
方法来完成这一 *** 作。
使用上面两个方法只能将字典转换成 model,如果 Json 最外层是个数组,那么我们就必须在循环中使用这个方法,这非常不方便, 而且还有个条件,就是 model 中的所有属性名必须跟字典中的 key 完全对应,这样就会遇到另外一个问题,如果我们字典中的一个 key 与系统关键字重名,那我们在 model 就不能使用这个 key 作为属性名了。
为了解决上面的问题,我们会使用一些第三方库去完成字典转模型的 *** 作,例如 MJExtension 。由于它是一个 OC 的库,所以在 Swift 项目中需要引入桥接文件。在 Swift 中使用其 API 时其实是很不 swift 的。所以现在我们就用 Swift 3.0 来写一个 swift style 的 Json 转模型的库吧。
例如我们有这样的两个 model:
class User: NSObject { var name: String? var age = 0 var desc: String?}class Repos: NSObject { var Title: String? var owner: User? var vIEwers: [User]?}
最终我们想实现这样的调用:
let repos = Json ~> Repos.self // 将一个字典转成一个Repos的实例 let vIEwers = vIEwers => User.self //将一个数组转换成User的数组
~>
和 =>
是自定义的运算符,主要是为了调用方便。它们的定义是这样的:
public func ~><T: NSObject>(lhs: Any,rhs: T.Type) -> T?public func =><T: NSObject>(lhs: Any,rhs: T.Type) -> [T]?
这里给出我的实现 ModelSwift。大家可以先看看我的实现然后试着写出自己的实现。好了,现在就让我们开始吧。
要解决的问题由于将数组转成模型数组,其实要做的工作跟将字典转模型是一样的,只是做了个循环而已。所以我们首先要解决的问题是:如何在 Swift 将字典转成模型。这里我们是使用 KVC就可以了。我们使用 NSObject 的 setValue(_ value: Any?,forKey key: String)
方法来给对象设置值。
从上面要实现的效果来看,我们在使用前并不用先实例化一个对象。所以我们要解决的第二个问题是:如何通过类型来实例化一个对象。
另一个要解决的问题是字典中的 key 与关键字重名,或者我们想使用自己的名字。所以我们要实现自己的映射的策略。
还有一个问题是,如果我们服务器返回的字典数据中包含另外一个字典数组,对应我们的 model 中就是一个对象包含另外一个对象的数组。那么我们怎样才能知道这个数组中对象的类型呢?
实现思路对于上面提到的第一问题我在上面已经给出了解决方案,就是让我们的 model 继承 NSObject,然后使用 setValue(_ value: Any?,forKey key: String)
方法来给对象设置值。这里的 value
其实是要根据 model 中的属性名去字典中获取的。如果我们能拿到 model 所有的属性名,那么给 model 设置值的 *** 作就完成了。那么如何获取到 model 的属性名呢?这就必须得使用到 Swift 中的反射机制了。
Swift 的反射机制是基于一个叫 Mirror 的 struct
来实现的。对于 Mirror 的详细结构大家可以按住 cmd
点进去查看。这里我们主要关注的是 public typealias Child = (label: String?,value: Any)
这个 typealias,它其实是一个元祖,label
就表示我们的属性名,是 Optional 的。value
表示的是属性的值。这里 label
为什么是 Optional 的?如果你仔细考虑下,其实这是非常有意义的,并不是所有支持反射的数据结构都包含有名字的子节点。 Mirror 会以属性的名字做为 label
,但是 Collection 只有下标,没有名字。Tuple 同样也可能没有给它们的条目指定名字。
Mirror 有个 children
的存储属性,它的定义是这样的:
public let children: Mirror.Children
这里的 Mirror.Children
也是一个 typealias,它是这样定义的:
public typealias Children = AnyCollection<Mirror.Child>
可以看到它是 Child 的集合。所以我们可以通过 Mirror 的 children
属性来获得 model 的所有属性名。
我们写个类来测试一下:
class Person: NSObject { var name = "" var age = 0 var frIEnds: [Person]?}let mirror = Mirror(reflecting: Person())for case let (label?,value) in mirror.children { print ("\(label) = \(value)")}
运行结果是如下:
name = age = 0frIEnds = nil
Mirror 还有一个类型为 Any.Type
的 subjectType
存储属性,表示该映射对象的类型,例如上面的 mirror.subjectType
就是 User
。使用 subjectType
就可以获得对象的类型以及其所有属性的类型。为了实现这个效果,我们可以写出下面的代码:
func subjectType(of subject: Any) -> Any.Type { let mirror = Mirror(reflecting: subject) return mirror.subjectType}func children(of subject: Any) { let mirror = Mirror(reflecting: subject) for case let(label?,value) in mirror.children { print ("\(label) = \(subjectType(of: value))") }}children(of: Person())
打印结果是这样的:
name = Stringage = IntfrIEnds = Optional<Array<Person>>
我原本想使用这个方法来得到 model 中包含的另外对象的类型和数组中对象的类型,例如 Person 中有 father
和 frIEnds
属性:
class Person: NSObject { var name = "" var age = 100 var father: Person? var frIEnds: [Person]?}
但是发现结果是 Optional<Person>
和 Optional<Array<Person>>
。所以我们还是得显示地指出一个 model 中包含的其他对象的类型,以及数组中对象的类型。在后面我会给出自己的实现。大家可以给出自己的实现。
要使用 Mirror 来获得反射对象的所有属性名,就必须先使用 init(reflecting subject: Any)
来创建一个 Mirror。而创建 Mirror 就必须传入一个 subject(在这里我们主要传入一个NSObject类型的对象)。所以我们的首要任务就是通过类型来实例化一个对象。
有些同学可能有疑问了:我要转换成 Person 的对象,我直接传入一个
Person 的实例就行了啊。如果你看看我们 josn 转模型的方法定义就能明白了。func ~><T: NSObject>(lhs: Any,rhs: T.Type) -> T?
还是以上面的 Person 为例,我们看看这样的调用:
Person.self().age// 结果是:100
所以我们通过一个类的 self()
方法可以得到一个类的实例。其实我们还可以通过 AnyClass 来实例化对象。AnyClass 是类的类型,其定义是这样的:
public typealias AnyClass = AnyObject.Type
我们通过类的self
属性可以得到类的类型:
Person.self //结果是:Person.Type
得到类的类型后,通过调用其 init()
方法就可以创建一个实例了:
Person.self.init().age// 结果是:100
写个简单的 josn 转模型使用类型创建对象的类中的init方法前面必须是 required 的,因为这么创建方式是使用Meta type来创建的。由于我们 Json 转模型的 model 是继承自 NSObject 的,所以不用在每个类中显示地实现。
有了上面的基础就可以来实现我们的 josn 转模型了。首先我们来写出 ~>
的定义,并通过类来创建一个对象
infix operator ~>func ~><T: NSObject>(lhs: Any,rhs: T.Type) -> T? { guard let Json = lhs as? [String: Any],!Json.isEmpty else { return nil } let obj = T.self() let mirror = Mirror(reflecting: obj) for case let(label?,value) in mirror.children { print ("\(label) = \(value)") } return obj}class Person: NSObject { var name = "" var age = 0 overrIDe var description: String { return "name = \(name),age = \(age)" }}let Json: [String: Any] = ["name": "jewelz","age": 100]let p = Json ~> Person.self// 打印结果:// name = // age = 0
通过上面的几行代码我们确实成功的创建了一个 Person 的实例了。下一步就是给实例设置值了。我们在上面的 for
循环中添加如下代码:
// 从字典中获取值if let value = Json[label] { obj.setValue(value,forKey: label)}
整个代码就是这样的:
infix operator ~>func ~><T: NSObject>(lhs: Any,_) in mirror.children { // 从字典中获取值 if let value = Json[label] { obj.setValue(value,forKey: label) } } return obj}let p = Json ~> Person.selfprint(p!)//结果:name = jewelz,age = 100
有了上面 ~>
的实现,=>
的实现就很简单了:
infix operator =>func =><T: NSObject>(lhs: Any,rhs: T.Type) -> [T]? { guard let array = lhs as? [Any],!array.isEmpty else { return nil } return array.flatMap{class User: NSObject { var name: String? var age = 0 var desc: String?}class Repos: NSObject { var Title: String? var owner: User? var vIEwers: [User]?}~> rhs }}
上面只是实现了一个简单 josn 转模型,其实在实际项目中要解决的问题还有很多。现在再来看看我在文章开头给出的 User 类和 Respo 类:
desc
只简单的用上面的实现是无法得到想要的结果的。对于 User 类来说,description
属性对应 Json 的
public protocol Reflectable: class { var reflectedobject: [String: Any.Type] { get }}key,所以我们还要进行 model 的属性与 Json 的键的映射。这里的思路就是将 model 的属性名作为 key,以要替换的 Json 的键作为 value 存入字典中。我们可以拓展 NSObject ,添加一个计算属性并提供一个空实现。不过这样的倾入性太大,毕竟不是所有的类都需要做这个映射。所以最后的方式是 POP。比如我们可以制定这样一个协议:
owner
在需要做映射的类中去实现该协议。
对于更复杂的 Repos 类来说,要做的事情更多。比如 owner
的类型怎么知道?vIEwers
这个对象怎么完成赋值?Optional<User>
数组中的类型是什么,怎样才能完成赋值? 虽然通过上面提到的 Mirro 可以得到所有的类型,但得到的是 Optional<Array<User>>
以及 setValue(_ value: Any?,forKey key: String)
。我的解决的办法就跟上面做属性名替换是一样的。这里就不详细地说明了,大家可以各显神通。写出自己的实现。
通过上面的几个步骤,我们就能很快的实现一个简单的 Json 转模型的需求了。总结起来就是以下几点:
所有要转换的 model 继承 NSObject
使用类的类型来实例化对象
通过反射获得对象的所有属性名
通过 方法来给属性设置值
对于在最后提出的几个问题,我这里就不一一详细地说明了。大家可以点这里看看我的实现。大家可以使用 CocoaPods 或者 Carthage 将 ModelSwift 集成到项目中。如果在使用中有什么问题可以 issue 我,也可以给个 star 持续关注。
总结以上是内存溢出为你收集整理的教你如何用Swift写个json转模型的开源库全部内容,希望文章能够帮你解决教你如何用Swift写个json转模型的开源库所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)