Pring 0.17.3

Pring 0.17.3

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布最新发布2019年4月
SPM支持 SPM

1amageek 维护。



 
依赖项
Firebase>= 0
Firebase/Firestore>= 0
Firebase/Storage>= 0
 

Pring 0.17.3

⚠️Pring 已弃用

请使用 🧢 Ballcap 🧢

Ballcap 是新的 Cloud Firestore 框架


Version Platform Downloads

请捐赠以继续开发。

https://github.com/1amageek/pring.ts

Pring <β>

Firestore 模型框架。已添加Firestore中的文档和集合的概念。Pring定义了文档的方案并支持类型安全的编程。Schema中还可以定义子集合。

深入 Firebase

请在此处报告问题 here

要求❗️

安装

CocoaPods

  • 在您的Podfile中插入 pod 'Pring'
  • 运行 pod install

功能🎊

☑️您可以定义Firestore的文档模式。
☑️当然,有类型安全。
☑️它与Firestore和Storage无缝工作。
☑️您可以轻松关联子集合。
☑️支持GeoPoint。

设计💻

Firestore数据库设计

如果您打算使用Firestore来制作产品,我建议您阅读它。

待办事项

实现

  • 实现Firestore可以处理的DataType
  • 实现数据管理
  • 实现自定义DataType(正在考虑的规范)
  • 实现与Firestorage的链接
  • 实现nestedcollection功能
  • 实现referencecollection功能
  • 实现数据源
  • 实现具有查询功能的数据源(正在考虑的规范)

运行单元测试验证

  • 验证Firestore能处理的数据类型实现
  • 验证数据管理实现
  • 验证自定义数据类型实现
  • 验证与Firestorage的协作
  • 验证嵌套集合功能的实现
  • 验证引用集合功能的实现
  • 验证查询启用数据源的实现

如果您有功能请求,请提交问题

用法

例如...

@objcMembers
class User: Object {
    @objc enum UserType: Int {
        case normal
        case gold
        case premium        
    }
    dynamic var type: UserType = .normal
    dynamic var name: String?
    dynamic var thumbnail: File?
    dynamic var followers: ReferenceCollection<User> = []
    dynamic var items: NestedCollection<Item> = []
    
    // Custom property
    override func encode(_ key: String, value: Any?) -> Any? {
        if key == "type" {
            return self.type.rawValue
        }
        return nil
    }

    override func decode(_ key: String, value: Any?) -> Bool {
        if key == "type" {
            self.type = UserType(rawValue: value as! Int)
            return true
        }
        return false
    }
}
@objcMembers
class Item: Object {
    dynamic var thumbnail: File?
    dynamic var name: String? = "OWABIISHI"
}
// Set an arbitrary ID
let user: User = User(id: "ID")
user.save()
let userA: User = User()
userA.name = "userA"
userA.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)

let userB: User = User()
userB.name = "userB"
userB.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)

let item: Item = Item()
item.thumbnail = File(data: UIImageJPEGRepresentation(IMAGE, 0.3)!, mimeType: .jpeg)

userA.followers.insert(userB)
userA.items.insert(item)
userA.save()

重要❗️

Pring明确区分保存和更新,以防止意外覆盖。Pring提供三种初始化对象的模式。

将AutoID赋给对象初始化

let user: User = User()

给任意ID初始化

let user: User = User(id: "YOUR_ID") // isSaved false

处理已保存对象时的初始化

如果您正在处理一个已经保存的对象,请执行以下初始化。如果此初始化无法保存,请更新。

let user: User = User(id: "YOUR_ID", value: [:]) // isSaved true

开发者的责任是管理对象的保存状态。

架构

Pring 继承自 Object 类并定义了 Model。Pring 支持许多数据类型。

@objcMembers
class User: Object {
    dynamic var array: [String]                     = ["array"]
    dynamic var set: Set<String>                    = ["set"]
    dynamic var bool: Bool                          = true
    dynamic var binary: Data                        = "data".data(using: .utf8)!
    dynamic var file: File                          = File(data: UIImageJPEGRepresentation(UIImage(named: "")!, 1))
    dynamic var url: URL                            = URL(string: "https://firebase.google.com/")!
    dynamic var int: Int                            = Int.max
    dynamic var float: Double                       = Double.infinity
    dynamic var date: Date                          = Date(timeIntervalSince1970: 100)
    dynamic var geoPoint: GeoPoint                  = GeoPoint(latitude: 0, longitude: 0)
    dynamic var list: List<Group>                   = []    
    dynamic var dictionary: [String: Any]           = ["key": "value"]    
    dynamic var string: String                      = "string"
    
    let group: Reference<Group>                         = .init()
    let nestedCollection: NestedCollection<Item>             = []
    let referenceCollection: ReferenceCollection<User>  = []
}
数据类型 说明
Array 它是数组类型。
Set 它是集合类型。在 Firestore 中表示为 {"value": true}
Bool 它是一个布尔值。
File 它是文件类型。您可以将大型数据文件保存于此。
URL 它是 URL 类型。它在 Firestore 中以字符串形式保存。
Int 它是整型。
Float 它是浮点型。在 iOS 中,它将是 64 位 Double 类型。
Date 它是日期类型。
GeoPoint 它是地理点类型。
List 它是对象数组类型。
Dictionary 它是字典类型。保存结构化数据。
nestedCollection 或 referenceCollection 它是子集合类型。
String 它是字符串类型。
Reference 它是引用类型。它持有 DocumentReference
Null 它是空类型。
Any 它是一个自定义类型。如果它是一个继承自 NSObject 的类,您可以将其指定为自定义类型。

⚠️ Bool Int Float Double 不支持可选类型。

⚙️管理数据

保存

文档只能保存一次。

let object: MyObject = MyObject()
object.save { (ref, error) in
   // completion
}

检索

按照 ID 检索单据。

MyObject.get(document!.id, block: { (document, error) in
    // do something
})

更新

文档有一个更新方法。请注意,它与 Salada 不同。

MyObject.get(document!.id, block: { (document, error) in
    document.string = "newString"
    document.update { error in
       // update
    }
})

删除

按照 ID 删除单据。

MyObject.get(document!.id, block: { (document, error) in
    document.delete()
})

批量写入

let batch: WriteBatch = Firestore.firestore().batch()
batch.add(.save, object: userA)    //  ** File is not saved.
batch.add(.update, object: userB)
batch.add(.delete, object: userC)
batch.commit(completion: { (error) in
  // error handling
})

列表

列表比嵌套集合访问对象更快。列表在文档中存储数据,而不是在子集合中。

// save
let order: Order = Order()
do {
    let orderItem: OrderItem = OrderItem()
    orderItem.name = "aaaa"
    orderItem.price = 39
    order.items.append(orderItem)
}
do {
    let orderItem: OrderItem = OrderItem()
    orderItem.name = "bbb"
    orderItem.price = 21
    order.items.append(orderItem)
}
order.save()

更新数据时,务必更新父对象。

// update
Order.get("ORDER_ID") { (order, error) in
    order.items.first.name = "hoge"
    order.update()
}

📄File

Pring 有一个文件类,因为它可以无缝地与 Firebase 存储(Firebase Storage)协同工作。

保存

文件与文档保存同时保存。

let object: MyObject = MyObject()
object.thumbnailImage = File(data: PNG_DATA, mimeType: .png)
let tasks: [String: StorageUploadTask] = object.save { (ref, error) in

}

save 方法返回具有键设置的 StorageUploadTask。有关如何使用 StorageUploadTask 的详细信息,请参阅 Firebase 文档

let task: StorageUploadTask = tasks["thumbnailImage"]

获取数据

按大小获取数据。

let task: StorageDownloadTask = object.thumbnail.getData(100000, block: { (data, error) in
    // do something
})

更新

如果文档已经保存,请使用更新方法。 update 方法也返回 StorageUploadTask。运行更新方法会自动删除旧文件。

let newFile: File = File(data: PNG_DATA, mimeType: .png)
object.thumbnailImage = newFile
object.update()

删除

使用 delete 方法删除。

object.thumbnailImage = File.delete()
object.update()

如果它在数组中,则通过从数组中删除并更新它来自动删除文件。

object.files.remove(at: 0)
object.update()

嵌套集合 & 引用集合

NestedCollectionReferenceCollection 是定义子集合的类。

当在子集合中持有 File 时,将首先执行 File 的保存。当一次在子集合中存储多个 File 时,性能将降低。

嵌套集合

  • NestedCollection 在文档下嵌套数据并保存。
  • 文件的目的是嵌套路径。

引用集合

  • ReferenceCollection 在文档下保存文档ID。
  • 数据单独保存。
@objcMembers
class User: Object {
    dynamic var name: String?
    dynamic var followers: ReferenceCollection<User> = []
    dynamic var items: NestedCollection<Item> = []
}

@objcMembers
class Item: Object {
    dynamic var thumbnail: File?
}

let userA: User = User()
userA.name = "userA"

let userB: User = User()
userB.name = "userB"

let item: Item = Item()
item.thumbnail = File(data: JPEG_DATA, mimeType: .jpeg)

userA.followers.insert(userB)
userA.items.insert(item)
userA.save()
let item: Item = Item()
userA.items.insert(item)
userA.update() { error in
  if let error = error {
    // error handling
    return
  }
  // do something
}

数据源

DataSource 是一个易于从集合中检索数据的类。

class DataSourceViewController: UITableViewController {

    var dataSource: DataSource<User>?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.dataSource = User.order(by: \User.createdAt).limit(to: 30).dataSource()
            .on({ [weak self] (snapshot, changes) in
                guard let tableView: UITableView = self?.tableView else { return }
                switch changes {
                case .initial:
                    tableView.reloadData()
                case .update(let deletions, let insertions, let modifications):
                    tableView.beginUpdates()
                    tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
                    tableView.endUpdates()
                case .error(let error):
                    print(error)
                }
            }).listen()
    }

    // MARK: - Table view data source

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return self.dataSource?.count ?? 0
    }

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell: DataSourceViewCell = tableView.dequeueReusableCell(withIdentifier: "DataSourceViewCell", for: indexPath) as! DataSourceViewCell
        configure(cell, atIndexPath: indexPath)
        return cell
    }

    func configure(_ cell: DataSourceViewCell, atIndexPath indexPath: IndexPath) {
        guard let user: User = self.dataSource?[indexPath.item] else { return }
        cell.textLabel?.text = user.name
        cell.disposer = user.listen { (user, error) in
            cell.textLabel?.text = user?.name
        }
    }

    func tableView(_ tableView: UITableView, didEndDisplaying cell: DataSourceViewCell, forRowAt indexPath: IndexPath) {
        cell.disposer?.dispose()
    }

    override func tableView(_ tableView: UITableView, canPerformAction action: Selector, forRowAt indexPath: IndexPath, withSender sender: Any?) -> Bool {
        return true
    }

    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            self.dataSource?.removeDocument(at: indexPath.item)
        }
    }
}

子集合数据源

User.get("USER_ID") { (user, error) in
    guard let user: User = user else { return }
    self.dataSource = user.followers.order(by: \User.createdAt).dataSource()
        .on { (snapshot, changes) in
            // something
        }.listen()
}

同步客户端合并

@objcMembers
class User: Object {

    let group: Reference<Group> = Reference()
}

请向 DataSource 添加 on(parse:)

self.dataSource = User.order(by: \User.updatedAt).dataSource()
    .on({ [weak self] (snapshot, changes) in
        guard let tableView: UITableView = self?.tableView else { return }
        debugPrint("On")
        switch changes {
        case .initial:
            tableView.reloadData()
        case .update(let deletions, let insertions, let modifications):
            tableView.beginUpdates()
            tableView.insertRows(at: insertions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.deleteRows(at: deletions.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.reloadRows(at: modifications.map { IndexPath(row: $0, section: 0) }, with: .automatic)
            tableView.endUpdates()
        case .error(let error):
            print(error)
        }
    })
    .on(parse: { (snapshot, user, done) in
        user.group.get({ (group, error) in
            done(user)
        })
    })
    .onCompleted({ (snapshot, users) in
        debugPrint("completed")
    })
    .listen()

查询

获取文档

User.where(\User.name, isEqualTo: "name").get { (snapshot, error) in
    print(snapshot?.documents)
}

获取子集合

WHERE

let user: User = User(id: "user_id")
user.items.where(\Item.name, isEqualTo: "item_name").get { (snapshot, error) in
    print(snapshot?.documents)
}

ORDER

let user: User = User(id: "user_id")
user.items.order(by: \Item.updatedAt).get { (snapshot, error) in
    print(snapshot?.documents)
}

从查询创建数据源

let user: User = User(id: "user_id")
user.items
    .where(\Item.name, isEqualTo: "item_name")
    .dataSource()
    .on({ (snapshot, change) in
        // do something
    })
    .onCompleted { (snapshot, items) in
        print(items)
}

全文搜索

在 Firebase 上执行全文搜索时,请使用 ElasticSearch 或 Algolia。Swift 实现时有相应的库。

https://github.com/miuP/Algent