目录
特性
🙌 支持 Codable(在内部使用FirebaseFirestoreSwift
)。🙌 提供简单易用的 CRUD、批量、事务方法。🙌 支持array-union/array-remove
。🙌 支持FieldValue.increment
。🙌 支持FieldValue.delete()
。🙌 支持基于 KeyPath 的查询。
使用 Swift 功能(版本:5.1)
使用说明
👉 示例应用
基本使用
Document
类型必须符合 SnapshotData
协议。
SnapshotData
协议继承了 Codable
。例如
struct Product: SnapshotData {
var name: String = ""
var desc: String?
var price: Int = 0
var attributes: [String: String] = [:]
}
可以方便地定义 DocumentPath<T>
和 CollectionPath<T>
。
为 DocumentPaths
或 CollectionPaths
的扩展定义路径。
extension CollectionPaths {
static let products = CollectionPath<Product>("products")
}
extension DocumentPaths {
static func product(_ productID: String) -> DocumentPath<Product> {
CollectionPaths.products.document(productID)
}
}
使用符合 SnapshotData
协议的模型和路径创建 Snapshot
。
let product = Snapshot<Product>(data: Product(), path: CollectionPath.products)
简而言之
let product = Snapshot(data: .init(), path: .products)
你可以通过调用 create(completion:)
来保存它
product.create { error in
if let error = error {
print("error", error)
return
}
print("created!")
}
FireSnapshot
也提供了读取(获取文档/监听文档
)、写入(更新/删除
)、批量写入和事务写入
// Update document
product.update { error in
if let error = error {
print("error", error)
return
}
print("updated!")
}
// Delete document
product.delete { error in
if let error = error {
print("error", error)
return
}
print("deleted!")
}
// Get document
Snapshot.get(.product("some_product_id")) { result in
switch result {
case let .success(product):
print(product.name)
case let .failure(error):
print(error)
}
}
// Listen document
let listener = Snapshot.listen(.product("some_product_id")) { result in
switch result {
case let .success(product):
print("listened new product", product.name)
case let .failure(error):
print(error)
}
}
// Get documents
Snapshot.get(.products) { result in
switch result {
case let .success(products):
print(products.count)
case let .failure(error):
print(error)
}
}
// Listen documents
let listener = Snapshot.listen(.products) { result in
switch result {
case let .success(products):
print("listened new products", products.count)
case let .failure(error):
print(error)
}
}
如果你可以读取/写入时间戳,如 createTime
和 updateTime
,则模型必须符合 HasTimestamps
协议。
struct Product: SnapshotData, HasTimestamps {
var name: String = ""
var desc: String?
var price: Int = 0
var attributes: [String: String] = [:]
}
let product = Snapshot(data: .init(), path: .products)
// `createTime` and `updateTime` will be written to field with other properties.
product.create()
Snapshot.get(product.path) { result in
guard let p = try? result.get() else {
return
}
// optional timestamp value.
print(p.createTime)
print(p.updateTime)
// `updateTime` will be updated with other properties.
p.update()
}
高级使用
@IncrementableInt
/ @IncrementableDouble
如果你想在模型中使用 FieldValue.increment
,使用 @IncrementableInt(Double)
。
@IncrementableInt
属性的类型为Int64
。@IncrementableDouble
属性的类型为Double
。
extension CollectionPaths {
static let products = CollectionPath<Model>("models")
}
struct Model: SnapshotData {
@IncrementableInt var count = 10
@IncrementableDouble var distance = 10.0
}
Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}
// Refer a number
print(model.count) // print `10`.
print(model.distance) // print `10.0`.
// Increment (use `$` prefix)
model.$count.increment(1)
print(model.count) // print `11`.
model.update()
model.$distance.increment(1.0)
print(model.distance) // print `11.0`.
model.update()
// Decrement
model.$count.increment(-1)
print(model.count) // print `9`.
model.update()
model.$distance.increment(-1.0)
print(model.distance) // print `9.0`.
model.update()
// if you want to reset property, use `reset` method.
model.$count.reset()
}
@AtomicArray
如果你想要使用 FieldValue.arrayUnion
或 FieldValue.arrayRemove
,请使用 @AtomicArray
。
@AtomicArray
的元素类型必须符合 Codable
协议。
extension CollectionPaths {
static let products = CollectionPath<Model>("models")
}
struct Model: SnapshotData {
@AtomicArray var languages: [String] = ["en", "ja"]
}
Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}
// Refer an array
print(model.languages) // print `["en", "ja"]`.
// Union element(s)
model.$languages.union("zh")
print(model.count) // print `["en", "ja", "zh"]`.
model.update()
// Remove element(s)
model.$languages.remove("en")
print(model.count) // print `["ja"]`.
model.update()
// if you want to reset property, use `reset` method.
model.$languages.reset()
}
@可删除字段
如果您想使用 FieldValue.delete
,请使用 @可删除字段
。
extension CollectionPaths {
static let products = CollectionPath<Model>("models")
}
struct Model: SnapshotData {
var bio: DeletableField<String>? = .init(value: "I'm a software engineer.")
}
Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}
print(model.bio?.value) // print `Optional("I'm a software engineer.")`
// Delete property
model.bio.delete()
model.update()
}
// After updated
Snapshot.get(.model(modelID)) { result in
guard let model = try? result.get() else {
return
}
print(model.bio) // nil
print(model.bio?.value) // nil
}
注意:通常,当属性设置为nil时,将写入文档的{key: null}
,
但使用 FieldValue.delete
时,将删除文档中 key
的字段。
基于KeyPath的查询
如果模型符合FieldNameReferable
协议,可以使用名为QueryBuilder
的基于KeyPath的查询生成器。
extension CollectionPaths {
static let products = CollectionPath<Product>("products")
}
struct Product: SnapshotData, HasTimestamps {
var name: String = ""
var desc: String?
var price: Int = 0
var deleted: Bool = false
var attributes: [String: String] = [:]
}
extension Product: FieldNameReferable {
static var fieldNames: [PartialKeyPath<Mock> : String] {
return [
\Self.self.name: "name",
\Self.self.desc: "desc",
\Self.self.price: "price",
\Self.self.deleted: "deleted",
]
}
}
Snapshot.get(.products, queryBuilder: { builder in
builder
.where(\.price, isGreaterThan: 5000)
.where(\.deleted, isEqualTo: false)
.order(by: \.updateTime, descending: true)
}) { result in
...
}
安装
- CocoaPods
pod 'FireSnapshot', '~> 0.11.1'
依赖
- Firebase:
6.12.0
或更高版本。 - FirebaseFirestoreSwift: 从主分支签出。
- Swift:
5.1
或更高版本。
通往1.0的道路
- 直到达到1.0版本,次要版本将会是破坏性的 🙇。
开发
设置
$ git clone ...
$ cd path/to/FireSnapshot
$ make
$ open FireSnapshot.xcworkspace
单元测试
在运行单元测试之前,请先启动 Firestore Emulator
。
$ npm install -g firebase-tools
$ firebase setup:emulators:firestore
$ cd ./firebase/
$ firebase emulators:start --only firestore
# Open Xcode and run Unit Test after running emulator.
或者,运行 ./scripts/test.sh
。
通信
- 如果你发现了一个错误,请创建一个问题报告。
- 如果你有一个功能请求,请创建一个问题报告。
- 如果你想做出贡献,请提交一个合并请求。
💪
致谢
FireSnapshot 受以下项目启发:
许可
FireSnapshot 采用 MIT 许可证。有关更多信息,请参阅 LICENSE 文件。