安装 | 基本使用 | 关于 BSON | Codable | 社区 | 如何帮助
一个快速的、纯 Swift MongoDB 驱动,基于 Swift NIO 构建,适用于服务器端 Swift。它具有出色的 API 和经过战斗考验的核心,支持服务器和嵌入式环境中的 MongoDB。
🕶 安装
设置 MongoDB 服务器
如果您还没有设置 MongoDB 服务器,您应该设置一个 MongoDB 服务器来开始使用 MongoKitten
对于开发,这可以在您的本地机器上进行。
为 Ubuntu,macOS 或 任何其他支持的 Linux 发行版 安装 MongoDB。
或者,使用数据库即服务(DAAS)如 MongoDB Atlas,MLab,IBM Cloud 或其他许多服务。
如果您旨在使用 MongoKitten 移动版,请向下滚动!
🚀
将 MongoKitten 添加到您的 Swift 项目中MongoKitten 支持 Swift 包管理器,用于服务器端应用程序。将 MongoKitten 添加到您的 Package.swift 文件中的依赖项
`.package(url: "https://github.com/OpenKitten/MongoKitten.git", from: "5.0.0")
另外,别忘了将 "MongoKitten"
作为依赖项添加到您的目标中。
移动功能 [Beta]
MongoKitten 现在也支持嵌入式 MongoDB 数据库(测试版)。
对于 MongoKitten 移动版本,我们依赖于 Cocoapods。目前尚不支持使用 MongoKitten,但您可以通过将以下内容添加到您的 Podfile 中开始使用。
pod 'MongoKitten'
🚲 基本用法
连接到您的数据库
import MongoKitten
let db = try Database.synchronousConnect("mongodb:///my_database")
Vapor 用户应将数据库注册为服务。
let connectionURI = "mongodb://"
services.register { container -> MongoKitten.Database in
return try MongoKitten.Database.lazyConnect(connectionURI, on: container.eventLoop)
}
对于嵌入式数据库
// WARNING: Force unwrap will crash your application on failure
let mongo = try! MobileDatabase(settings: .default())
URI注意事项
MongoKitten 尚不支持 MongoDB v3.6 连接 URIs。您需要使用旧的连接 URI 格式。
如果您不确定;以 mongodb+srv://
开头的连接字符串是 3.6 连接 URI,而以 mongodb://
开头的 URIs 是旧格式。
NIO Futures
MongoKitten 依赖于 [Swift NIO](https://github.com/apple/swift-nio) 来提供对异步操作的支持。所有与服务器通信的 MongoKitten 操作都是异步的,并返回某种类型的 EventLoopFuture。
您可以通过阅读 其自述文件 或阅读 RayWenderlich.com 上的文章 来了解所有关于 NIO 的信息,但以下是基础知识
异步操作返回一个未来。NIO 在 EventLoopFuture<T>
类型中实现未来。一个 EventLoopFuture
是一个将来会提供结果的持有者。未来的结果可以是成功的,返回类型为 T
,或者失败的,返回类型为 Swift 的 Error
。这是异步表示成功 return
或抛出的错误。
如果您正在使用 Vapor,请参阅他们的 异步文档。Vapor的异步模块在NIO之上提供了额外的助手,使得使用 EventLoopFuture<T>
的实例变得更加容易。
如果您使用Vapor或其他基于Swift-NIO的Web框架,绝对不要 在 EventLoopFuture
实例上使用 wait()
函数。
CRUD(创建、读取、更新、删除)
// The collection "users" in your database
let users = db["users"]
创建(插入)
let myUser: Document = ["username": "kitty", "password": "meow"]
let future: Future<InsertReply> = users.insert(myUser)
future.whenSuccess { _ in
print("Inserted!")
}
future.whenFailure { error in
print("Insertion failed", error)
}
读取(查找)和查询构建器
要在MongoDB中执行以下查询
{
"username": "kitty"
}
使用以下MongoKitten代码
users.findOne("username" == "kitty").whenSuccess { user: Document? in
// Do something with kitty
}
要在MongoDB中执行以下查询
{
"$or": [
{ "age": { "$lte": 16 } },
{ "age": { "$exists": false } }
]
}
使用以下MongoKitten代码
users.find("age" <= 16 || "age" == nil).forEach { user: Document in
// Print the user's name
print(user["username"] as? String)
}
您也可以自己输入查询,而不使用查询构建器,如下所示
users.findOne(["username": "kitty"])
游标
查找操作返回一个 Cursor
。游标是查询结果集的指针。您可以通过遍历结果或获取一个或所有结果来从游标获取结果。
获取结果
您可以获取所有结果作为数组
let results: EventLoopFuture<[Document]> = users.find().getAllResults()
请注意,这可能在结果集非常大时很危险。只有当您确定查询的整个结果集可以舒适地放入内存时,才使用 getAllResults()
。
遍历结果
为了更有效地处理结果,您可以懒加载地遍历一个光标
let doneIterating: EventLoopFuture<Void> = users.find().forEach { user: Document in
// ...
}
光标是泛型的
查找操作返回一个 FindCursor
。如您所见,FindCursor
是一个泛型类型。您可以使用 map
函数懒加式地将光标转换成不同的结果类型,这与数组或文档上的 map
操作类似
users.find()
.map { document in
return document["username"] as? String
}
.forEach { username: String? in
print("user: \(username)")
}
更新
users.update(where: "username" == "kitty", setting: ["age": 3]).whenSuccess { _ in
print("🐈")
}
删除
users.deleteOne(where: "username" == "kitty").whenSuccess { amountDeleted in
print("Deleted \(amountDeleted) kitties 😿")
}
📦 关于 BSON 和文档
MongoDB 是一个文档数据库,底层使用 BSON 存储类似 JSON 的数据。MongoKitten 通过其配套项目 BSON 规范 实现 OpenKitten/BSON。您可以在单独的 BSON 仓库中找到更多关于我们的 BSON 实现信息,但这里有一些基本内容
文本字面量
您通常会像这样创建 BSON 文档:
let documentA: Document = ["_id": ObjectId(), "username": "kitty", "password": "meow"]
let documentB: Document = ["kitty", 4]
从上面的例子中,我们可以了解以下几点:
- BSON 文档可以表示数组或字典
- 您可以使用字面量初始化文档,就像初始化常规字典和数组一样
- 文档中的值(无论是数组元素还是字典对的值)可以是任何 BSON 原始类型
- BSON 原始类型包括 Swift 的核心类型,如
Int
、String
、Double
和Bool
,以及来自 Foundation 的Date
- BSON 还包含一些独特类型,如
ObjectId
又一个集合
与普通数组和字典一样,`Document`遵循`Collection`协议。因此,您可以直接使用`Document`,并使用您已经从`Array`和`Dictionary`知道的API进行操作。例如,您可以使用for循环遍历文档。
for (key, value) in documentA {
// ...
}
for value in documentB.values {
// ...
}
文档还提供了下标来访问单个元素。下标返回`Primitive?`类型的值,因此在使用之前您可能需要使用`as?`进行类型转换。
let username = documentA["username"] as? String
在将`Document`和`Dictionary`之间转换之前请三思
我们的`Document`类型实现了优化、高效的方式,并提供了许多读取和操作数据的实用功能,包括Swift `Dictionary
`类型上没有的功能。除此之外,`Document`还实现了大多数`Dictionary`上的API,因此学习曲线非常平缓。
💾 可编码
MongoKitten通过提供`BSONEncoder`和`BSONDecoder`类型支持`Encodable`和`Decodable`(可编码)协议。使用我们的编码和解码器与使用Foundation `JSONEncoder
`和`JSONDecoder
`类的工作非常相似,区别在于`BSONEncoder`生产`Document`实例,而`BSONDecoder`接受`Document`实例,而不是`Data`。
例如,假设我们想编码以下结构体
struct User: Codable {
var profile: Profile?
var username: String
var password: String
var age: Int?
struct Profile: Codable {
var profilePicture: Data?
var firstName: String
var lastName: String
}
}
我们可以这样编码和解码实例
let user: User = ...
let encoder = BSONEncoder()
let encoded: Document = try encoder.encode(user)
let decoder = BSONDecoder()
let decoded: User = try decoder.decode(User.self, from: encoded)
一些说明
- `BSONEncoder`和`BSONDecoder`与其它编码和解码器工作方式非常相似
- 嵌套类型也可以编码,也是推荐的
- 嵌套结构体和类通常编码为嵌入文档
- 您可以使用编码/解码策略自定义表示形式
可编码和游标
在对`find`查询进行操作时,可以懒加载`Cursor`的结果。懒加载映射比将整个结果集保持在内存中更为高效,因为它允许有效地使用`forEach-`循环,从而降低应用程序的内存压力。您还可以使用可编码功能利用游标。
// Find all and decode each Document lazily as a `User` type
users.find().decode(User.self).forEach { user in
print(user.username)
}
🐱 社区
✌️ 如何帮助
支持 MongoKitten 发展
在这里接受项目捐赠。我们希望能够建立一个良好的测试环境以及大量的文档、教程和示例。
为 MongoKitten 作出贡献
- 有关贡献的信息,请参阅 CONTRIBUTING.md
- 您可以通过解决 TODOs 和回复问题来帮助我们
- 当然,任何正面或负面的反馈都有助于改进项目
☠️ 许可协议
MongoKitten 采用 MIT 许可协议。