| 测试已测试 | ✓ |
| 语言语言 | 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"] |
✓ |
transformGenome提供了各种转换值的方式。这些是类型安全的,并将由编译器进行检查。
这些是可链的,如下所示:
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 继承。
快乐的映射!