测试已测试 | ✓ |
语言语言 | SwiftSwift |
许可证 | MIT |
发布上次发布 | 2017年4月 |
SwiftSwift 版本 | 3.0-GM-CANDIDATE |
SPM支持 SPM | ✓ |
由 Logan Wright 维护。
欢迎来到 Genome 3.0。这个库旨在满足以下目标
let
)Genome 基于而非直接基于 JSON 构建 Node。这使得 Genome 通过少量努力就可以轻松地与任何数据类型一起工作。
所有映射操作都构建在 Node 的核心之上。
默认情况下,与 JSON 工作得很好
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data else { return }
do {
let model = try Model(node: data)
completion(model)
} catch {
completion(error)
}
}
task.resume()
如果您的数据是嵌套的,您可以使用 Node 进一步处理。
let json = try rawJSONData.makeNode()
guard let items = json["root", "items"] else { return }
let models = try [Item](node: items)
您会注意到上述代码中使用了一个初始化后的数组,这在使用 Genome 时是完全没有问题的。
如果您正在使用 SwiftPM 在 Linux 上进行工作,强烈建议您使用类型安全的 JSON 库,如 此库。
我们可以用同样的方式创建我们的 JSON 数据
let jsonData = try Data(node: item)
api.post(jsonData) { response in ... }
所有未来的 Cocoapods 开发都将使用 SwiftPM 完成。Cocoapods 和 Carthage 的支持将被维护,但在开发中不使用。以下是一些有用的命令
# make xcode project
swift package generate-xcodeproj
# build project
swift build
# test project
swift test
要使用 SwiftPM,请将其添加到您的 Package.swift
.Package(url: "https://github.com/LoganWright/Genome.git", majorVersion: 3)
让我们考虑以下假设的 JSON
[
"name" : "Rover",
"nickname" : "RoRo", // Optional Value
"type" : "dog"
]
以下是如何为该 JSON 创建模型的示例
enum PetType: String {
case dog
case cat
case unknown
}
struct Pet: MappableObject {
let name: String
let type: PetType
let nickname: String?
init(map: Map) throws {
name = try map.extract("name")
nickname = try map.extract("nickname")
type = try map.extract("type") { PetType(rawValue: $0) ?? .unknown }
}
func sequence(map: Map) throws {
try name ~> map["name"]
try type ~> map["type"].transformToNode { $0.rawValue }
try nickname ~> map["nickname"]
}
}
一旦完成,我们可以这样构建
let pet = try Pet(node: json)
它还会与集合一起工作
let pets = try [Pet](node: jsonArray)
让我们构建一个简单的例子,来获取NASA的每日照片。请注意,这是一个同步API,为了简洁,使用了Data。建议使用异步且恰当的HTTP客户端,如URLSession。
struct Photo: BasicMappable {
private(set) var title: String = ""
private(set) var mediaType: String = ""
private(set) var explanation: String = ""
private(set) var concepts: [String] = []
private(set) var imageUrl: NSURL!
mutating func sequence(_ map: Map) throws {
try title <~ map["title"]
try mediaType <~ map ["media_type"]
try explanation <~ map["explanation"]
try concepts <~ map["concepts"]
try imageUrl <~ map["url"]
.transformFromNode { NSURL(string: $0) }
}
}
struct NASA {
static let url = URL(string: "https://api.nasa.gov/planetary/apod?concept_tags=True&api_key=DEMO_KEY")!
static func fetchPhoto() throws -> Photo {
let data = try Data(contentsOf: NASA.url)
return try Photo(node: data)
}
}
现在我们可以这样调用
let photo = try NASA.fetchPhoto()
警告:请首先阅读有关同步性和API的第一段内容。
MappableObject
这是本库的核心协议选项之一。它将是大多数标准映射操作的首选。
它有两个要求
init(map: Map) throws
这是用于映射对象的初始化器。如果您喜欢,可以手动调用它,但如果使用任何内置便捷初始化器,则会自动调用。否则,如果需要初始化一个Map
,请使用
let map = Map(node: someNode, in: someContext)
它有两个主要要求
sequence(map: Map) throws
在两个主要情况下会调用sequence
函数。它被标记为mutating
,因为这将在fromNode
操作上修改值。然而,如果您只使用toNode
,则不会进行任何修改,可以移除mutating
关键字。(如在上述例子中所示)
在映射到使用任何便利初始化器的Node时。在实例化对象后,将调用sequence
。这使得无法初始化常量或使用双向操作符的对象能够完成映射。
如果您直接使用
init(map: Map)
进行初始化,那么如果对象需要,您将负责手动调用sequence
。
它被标记为mutating
,因为它会修改值。
请注意,如果您只映射到Node,则不会进行任何修改。
当访问对象的makeNode()
时,将调用序列操作来收集值到一个Node
包中。
~>
这是本库中使用的核心操作之一。~
符号表示连接,而<
和>
符号分别表示值的流向。当声明为~>
时,表示仅从值映射到Node。
您还可以使用以下操作符:
操作符 | 方向 | 例子 | 修改 |
---|---|---|---|
<~> |
到和从Node | try name <~> map["name"] |
✓ |
~> |
仅到Node | try clientId ~> map["client_id"] |
𝘅 |
<~ |
仅从Node | try updatedAt <~ map["updated_at"] |
✓ |
transform
Genome提供了各种转换值的方式。这些是类型安全的,并将由编译器进行检查。
这些是可链的,如下所示:
try type <~> map["type"]
.transformFromNode {
return PetType(rawValue: $0)
}
.transformToNode {
return $0.rawValue
}
注意:目前,在某些情况下,转换需要绝对的选项性符合性。例如,Optionals变为Optionals,ImplicitlyUnwrappedOptionals变为ImplicitlyUnwrappedOptionals等。
fromNode
当使用let
常量时,您需要调用一个即时设置值的转换器。在这种情况下,您将调用fromNode
,并传递任何接受一个NodeConvertibleType
(一个标准Node类型)并返回值的闭包。
transformFromNode
如果需要将节点输入转换以适应您自己的类型,请使用此命令。在我们上面的例子中,我们需要将原始节点转换为我们的枚举。这也可以附加到 <~
操作符的映射中。
transformToNode
如果需要将给定的值转换为更适合数据格式的形式,请使用此命令。这也可以附加到 ~>
操作符的映射中。
try
为什么每行都有 try
关键字!每个映射操作如果不正确指定,都将失败。最好首先处理这些可能性。
例如,如果设置的属性是非可选的,在 Node
中发现在 nil
,则操作应该抛出一个容易捕获的错误。
Genome 可用的不同功能
Genome 的构造方式意味着您无需处理 Node
以便在您的网络服务中进行反序列化和序列化。如果需要,它仍然可以直接使用。
Genome 适用于 final
类和结构,但也支持继承。遗憾的是,由于泛型、协议和 Self
的某些限制,它需要一些额外的努力。
Object
库提供了 Object
类型来满足大多数基于继承的映射操作。只需要简单地将 Object
作为子类,就可以正常使用了。
class MyClass : Object {}
注意:如果您使用
Realm
或其他已使用Object
的库,不要忘记在 Swift 中这些是模块命名空间的。如果是这种情况,您应该这样声明您的类:class MyClass : Genome.Object {}
BasicMappable
为了支持灵活的自定义,Genome 为协议提供了各种映射选项。您的对象可以符合以下任何一个。尽管每个初始化器都标记为 throws
,但如果您确定它将成功,则不需要初始化器 throw
。在这种情况下,您可以安全地省略 throws
关键字。
协议 | 必须初始化器 |
---|---|
BasicMappable | init() throws |
MappableObject | init(map: Map) throws |
这些都是便利协议,最终都继承自 MappableBase
。如果您想要定义自己的实现,库的其他功能仍然适用。
NodeConvertibleType
这是库的真正根源。即使上面提到的 MappableBase
也继承自这个核心类型。它有两个要求:
public protocol NodeConvertibleType {
init(node: Node, in context: Context) throws
func makeNode(context: Context) throws -> Node
}
所有基本类型,如 Int
、String
等,都符合此协议,这为定义库提供了最大的灵活性。它也为 fewer overloads 的出现铺平了道路,当集合的 NodeConvertible
也符合此协议时。
如果您正在使用图书馆中建立的默认实例化方案,您很可能需要使用此函数进行初始化。
public init(node: Node, in context: Context = EmptyNode) throws
现在我们可以轻松地安全地创建一个对象。
do {
let rover = try Pet(node: nodeRover)
print(rover)
} catch {
print(error)
}
如果我们只关心是否能够创建一个对象,我们也可以这样做
let rover = try? Pet(node: nodeRover)
print(rover) // Rover is type: `Pet?`
上下文
被定义为空协议,任何可能需要访问的对象都可以遵守并通过它传递。
如果您正在使用 基础
,您可以通过将它们转换成节点首先来转换 Any
、[String: Any]
和 [Any]
类型。Node(any: yourTypeHere)
。
您也可以无需映射直接实例化集合。
let people = try [People](node: someNode)
如果您希望使用 核心数据
,而不是从 NSManagedObject
继承,请从 ManagedObject
继承。
快乐的映射!