Modelmatic
为 Swift 模型对象提供自动 JSON 序列化和反序列化。
Modelmatic 自动化您的应用程序模型层的 JSON 序列化和反序列化。您不需要在 Swift 类中手动维护映射,Modelmatic 读取在 Xcode 的 Core Data 模型编辑器中定义的映射和值转换。
请注意,Modelmatic 当前版本仅适用于不依赖于 Core Data 的模型对象。在下一次版本中,将增加对 NSManagedObject
子类的支持。
图片由 Christopher T. Howlett 提供,Noun Project
用法
Modelmatic 允许您指定 JSON 字典中键值对和模型对象的对应属性之间的自定义映射。例如,假设您正在处理以下 JSON(来自 Modelmatic 示例应用程序)
{
"version" : "2",
"batchSize" : "10",
"authors" : [
{
"firstName" : "William",
"lastName" : "Shakespeare",
"born" : "1564-04-01",
"author_id" : "101",
"imageURL" : "https:\/\/www.foo.com?id=xyz123",
"books" : [
{
"tags" : "drama,fantasy",
"title" : "The Tempest",
"year" : "2013",
"book_id" : "3001"
},
...
步骤 1:定义模型
要使用 Modelmatic,您首先使用 Xcode 的 Core Data 模型编辑器来建模您的数据。不要担心,您不需要使用 Core Data 的其他方面,只需数据模型以及它的子集功能。
步骤 2:创建 Swift 类
如果你的模型复杂且频繁变更,建议使用 mogenerator 从模型编辑器中指定的元数据生成模型类(并按需更新)。否则,最简单的方法是从头创建所需的类。以下是一个示例
import Foundation
import Modelmatic
@objc (MDLAuthor)
class Author: ModelObject
{
// Name of the Core Data entity
static let entityName = "Author"
// Mapped to 'author_id' in the corresponding attribute's User Info dictionary
@objc var authorId: NSNumber!
@objc var firstName: String?
@objc var lastName: String?
@objc var dateOfBirth: NSDate?
@objc var imageURL: UIImage?
// Modeled relationship to 'Book' entity
@objc var books: [Book]?
}
关键点
- 导入
Modelmatic
。 - 继承自
ModelObject
。 - 使用
@objc(ObjCClassName)
前缀类以避免潜在的范围命名问题,并且可以获得更好的 Objective-C 集成。 - 使用
@objc
前缀属性。 - 定义一个名为
entityName
的静态 let 常量以指定 Core Data 模型文件中关联的实体名称。 authorId
在模型中映射到author_id
(参见属性定义的 用户信息 字典)。- Modelmatic 自动映射所有其他属性,包括在
books
属性中的嵌套对象。
定制映射
Modelmatic 自动将你在 Core Data 模型中指定的属性或关系的名称与 JSON 字典中的对应键相匹配。例如,对于名称为 firstName
的属性,Modelmatic 将尝试在 JSON 字典中使用 firstName
作为键,并将其映射到 Author
中的 firstName
属性。
然而,该框架也允许你根据需要指定自定义映射。例如,Author
类有如下属性
var authorId: NSNumber!
在模型文件中提供了自定义映射,将 authorId
属性绑定到 JSON 键路径 author_id
,如下所示
要添加自定义映射,请在模型编辑器中选择一个属性或关系,并将其 用户信息 字典中的条目添加。键应该是 jsonKeyPath,值应该是用于 JSON 字典中的键或键路径(点分隔的属性路径)。在编码和解码过程中,Modelmatic 将自动将你的对象的属性(由其属性或关系名称定义)与其指定的自定义键路径之间进行映射,以访问 JSON 值。
定义关系
Core Data 允许您在实体之间定义一对一和多对一关系。Modelmatic 会自动为已定义关系的嵌套对象创建和填充。例如,Modelmatic 的示例应用程序定义了从 Author
实体到 Book
实体的多对一关系。要创建一个带有其嵌套书籍数组的 Author
实例,您只需使用以下 JSON 字典初始化一个 Author
对象:
let author = Author(dictionary: $0, entity: entity)
例如,给定以下 JSON,前面的调用将创建并填充一个包含两个 Book
对象数组的 Author
实例,其 author
属性设置为指向 Author
实例(
{
"author_id" : "106"
"firstName" : "Mark",
"lastName" : "Twain",
"books" : [
{
"book_id" : "3501",
"title" : "A Connecticut Yankee in King Arthur's Court",
"year" : "2014"
},
{
"book_id" : "3502",
"title" : "The Prince and the Pauper",
"year" : "2015"
}
],
}
属性类型
Modelmatic 使用在 NSKeyValueCoding
(KVC) 协议中定义的方法来设置模型对象的属性值。KVC 可以设置任何 Objective-C 类型的属性,但处理纯 Swift 类型(尤其是结构和枚举类型)的能力有限。然而,桥接的标准库类型(如 String
、Array
、Dictionary
以及标量类型(如 Int
、Double
、Bool
等)均由 KVC 自动处理。但有一个显著的问题:Swift 标量包裹在可选类型中。例如,KVC 将无法设置以下属性
var rating: Int?
如果您的 ModelObject
子类使用 KVC 无法直接处理的 Swift 类型,您可以为同名的计算属性提供自定义处理,前缀为 kvc_
。例如,为了让 rating
属性在 Modelmatic 中工作,添加以下代码:
@objc var kvc_rating: Int {
get { return rating ?? 0 }
set { rating = Optional(newValue) }
}
如果 Modelmatic 无法直接设置属性(在这种情况下为 rating
属性),则将自动调用前缀为 kvc_
的变体(例如,本例中的 kvc_rating
)。注意:确保原始属性(例如本例中的 rating
)没有前缀 @objc
,而包装属性(例如 kvc_rating
)有前缀。
指定值转换
在您的 Core Data 模型文件中,您可以指定属性类型为 Transformable
。如果这样做,您可以为自定义转换器提供名称。例如,Modelmatic 示例应用程序中的 Author
类有其可转换属性 dateOfBirth
,类型为 NSDate
。Modelmatic 自动使用指定的 NSValueTransformer
子类实例在访问属性时转换值。
这是 Example 应用程序中 DateTransformer
类的完整代码:
import Foundation
@objc (MDLDateTransformer)
class DateTransformer: NSValueTransformer
{
static let transformerName = "Date"
override class func transformedValueClass() -> AnyClass { return NSString.self }
override class func allowsReverseTransformation() -> Bool { return true }
override func transformedValue(value: AnyObject?) -> AnyObject? {
guard let date = value as? NSDate else { return nil }
return serializedDateFormatter.stringFromDate(date)
}
override func reverseTransformedValue(value: AnyObject?) -> AnyObject? {
guard let stringVal = value as? String else { return nil }
return serializedDateFormatter.dateFromString(stringVal)
}
}
private let serializedDateFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter
}()
在 Example 应用程序中,DateTransformer
类由 AuthorObjectStore
类中的以下代码行注册:
NSValueTransformer.setValueTransformer(DateTransformer(), forName: String(DateTransformer.transformerName))
步骤 3:加载模型
在您的应用中(只需在应用的生命周期中进行一次),进行如下操作以将 Core Data 模型文件加载到内存中
let modelName = "Authors"
guard let modelURL = NSBundle(forClass: type(of: self).URLForResource(modelName, withExtension: "momd"),
model = NSManagedObjectModel(contentsOfURL: modelURL) else {
print("Unable to load model \(modelName)")
return
}
您可能希望将模型引用存储在类属性中。
步骤 4:编码和解码模型对象
一旦获得 JSON 数据,您可以通过以下方式反序列化它(注意deserializeJson
包裹对NSJSONSerialization
的调用)
guard let data = data, dict = try? data.deserializeJson() else {
return
}
要构造您的模型类的一个实例,只需提供反序列化值的字典,以及实体描述
let authors = Author(dictionary: $0, entity: entity)
这将构造并填充一个Author
实例,以及您在模型中定义的任何具有关系的嵌套对象(如果 JSON 包含数据)。然后,您只需处理您的模型对象。每当您想序列化对象或对象组时,只需按照以下步骤操作
// Encode the author
let authorDict = author.dictionaryRepresentation
// Serialize data
if let data = try? dict.serializeAsJson(pretty: true) {
// Do something with the data...
}
程序化设置相关对象
Modelmatic 提供了方法,以使程序化设置模型的一对一或一对多关系属性的对象变得更加容易。虽然删除对象很容易(只需将一对一属性设置为零,或使用数组方法从数组中删除对象),但这些属性的设置或添加可能稍微复杂一些。这是因为 Modelmatic 会自动设置您在模型中定义的任何反向关系的属性值,以便子对象具有对其父对象的引用。
虽然反向关系不是必需的,但它们通常很方便。请确保使用 weak
生命周期限定符引用父对象。
即使您当前没有使用反向关系,也建议使用 ModelObject
提供的方便方法修改关系值。这样,如果以后改变主意,您无需改变代码来添加支持设置父引用。
多对多关系
ModelObject
提供两种修改多对多关系的方法,如下例所示
// Adding an object to a to-many relationship
let author = Author(dictionary: authorDict, entity: authorEntity)
let book = Book(dictionary: bookDict, entity: bookEntity)
do {
// Adds a book to the author's 'books' array, and sets the book's 'author' property
try author.add(modelObject: book, forKey: "books")
}
catch MappingError.unknownRelationship(let name) {
print("Unknown relationship \(name)")
}
// Adding an array of objects to a to-many relationship
let books = [Book(dictionary: bookDict2, entity: bookEntity),
Book(dictionary: bookDict3, entity: bookEntity)]
do {
// Adds two books to the author's 'books' array, setting each book's 'author' property
try author.add(modelObject: books, forKey: "books")
}
catch MappingError.unknownRelationship(let name) {
print("Unknown relationship \(name)")
}
一对一关系
提供另一个方法来设置一对一关系的值,如下所示
// Set the value of a to-one relationship
let book = Book(dictionary: bookDict1, entity: bookEntity)
let pricing = Pricing(dictionary: ["retailPrice": expectedPrice], entity: pricingEntity)
do {
// Sets the book's 'pricing' property, and sets the pricing's 'book' property
try book.set(modelObject: pricing, forKey: "pricing")
}
catch MappingError.unknownRelationship(let name) {
print("Unknown relationship \(name)")
}
扁平属性
待办事项:添加描述。
运行示例应用程序
要运行示例项目,首先克隆存储库,然后从示例目录运行pod install
。
要求
- Swift 2.3 和 iOS 8.3(或以上)
- Core Data(CoreData.framework)
安装
Modelmatic可通过CocoaPods获得。要安装它,只需将以下行添加到您的Podfile中
pod "Modelmatic"
变更记录
作者
Jonathan Lehr, [email protected]
许可证
Modelmatic 采用 MIT 许可证。更多信息请参见 LICENSE 文件。