FireSnapshot 0.11.1

FireSnapshot 0.11.1

sgr-ksmt 维护。



  • 作者
  • Suguru Kishimoto

Release Swift Firebase Platform license

一个简洁的 Firebase-Cloud-Firestore 包装器,支持 Codable。

@sgr-ksmt 开发 Twitter Follow


目录


特性

  • 🙌支持 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>
DocumentPathsCollectionPaths 的扩展定义路径。

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)
    }
}

如果你可以读取/写入时间戳,如 createTimeupdateTime,则模型必须符合 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.arrayUnionFieldValue.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 文件。