⚠️ Pring 已弃用
Ballcap 🧢
请使用 🧢 Ballcap 是新的 Cloud Firestore 框架
https://github.com/1amageek/pring.ts
Pring <β>
Firestore 模型框架。已添加Firestore中的文档和集合的概念。Pring定义了文档的方案并支持类型安全的编程。Schema中还可以定义子集合。
请在此处报告问题 here
❗️
要求- iOS 10 或更高版本
- Swift 4.0 或更高版本
- Firebase firestore
- Firebase storage
- Cocoapods 1.4
❗️ gem install cocoapods --pre
安装
CocoaPods
- 在您的Podfile中插入
pod 'Pring'
。 - 运行
pod install
。
🎊
功能
💻
设计
如果您打算使用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()
嵌套集合 & 引用集合
NestedCollection
和 ReferenceCollection
是定义子集合的类。
当在子集合中持有 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 实现时有相应的库。