如何正确加载来自 Firebase 的信息?
How to correctly load information from Firebase?
我会尽力解释我在做什么。我有一个无限滚动的集合视图,它从我的 firebase 数据库中为每个单元格加载一个值。每次集合视图创建一个单元格时,它都会在数据库位置调用 .observe 并获取一个 snapchat。如果它加载任何值,它会将单元格背景设置为黑色。黑色背景 = 从数据库加载的单元格。如果您查看下图,您会发现并非所有单元格都从数据库中加载。数据库不能处理这么多调用吗?是线程问题吗?我正在做的事情是否适用于 Firebase?截至目前,我在我的
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
方法
经过一些测试,它似乎可以很好地从 firebase 加载所有内容,但不会更新 UI。这让我相信它会在出于某种原因加载信息之前创建单元格?我会想办法弄清楚如何'block'以某种方式做到这一点。
The image
您应该将加载委托给单元格本身,而不是您的 collectionView: cellForItemAtIndexPath 方法。这样做的原因是委托方法将异步挂起并用于 FireBase 网络任务的回调。虽然后者通常很快(根据经验),但您可能会在 UI 加载时遇到一些问题。根据您视图中的方块数量来判断。
理想情况下,您会想要这样的东西:
import FirebaseDatabase
class FirebaseNode {
//This allows you to set a single point of reference for Firebase Database accross your app
static let ref = Database.database().reference(fromURL: "Your Firebase URL")
}
class BasicCell : UICollectionViewCell {
var firPathObserver : String { //This will make sure that as soon as you set the value, it will fetch from firebase
didSet {
let path = firPathObserver
FirebaseNode.ref.thePathToYouDoc(path) ..... {
snapshot _
self.handleSnapshot(snapshot)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupSubViews()
}
func setupSubViews() {
//you add your views here..
}
func handleSnapshot(_ snapshot: FIRSnapshot) {
//use the content of the observed value
DispatchQueue.main.async {
//handle UI updates/animations here
}
}
}
你会用到它:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let path = someWhereThatStoresThePath(indexPath.item)//you get your observer ID according to indexPath.item.. in whichever way you do this
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cell ID", for: indexPath) as! BasicCell
cell.firPathObserver = path
return cell
}
如果这不起作用,则可能是您遇到了一些 Firebase 限制。我认为这不太可能。
更新 .. 进行一些更正并使用本地缓存。
class FirebaseNode {
//This allows you to set a single point of reference for Firebase Database accross your app
static let node = FirebaseNode()
let ref = Database.database().reference(fromURL: "Your Firebase URL")
//This is the cache, set to private, since type casting between String and NSString would add messiness to your code
private var cache2 = NSCache<NSString, DataSnapshot>()
func getSnapshotWith(_ id: String) -> DataSnapshot? {
let identifier = id as NSString
return cache2.object(forKey: identifier)
}
func addSnapToCache(_ id: String,_ snapshot: DataSnapshot) {
cache2.setObject(snapshot, forKey: id as NSString)
}
}
class BasicCell : UICollectionViewCell {
var firPathObserver : String? { //This will make sure that as soon as you set the value, it will fetch from firebase
didSet {
handleFirebaseContent(self.firPathObserver)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupSubViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupSubViews() {
//you add your views here..
}
func handleFirebaseContent(_ atPath: String?) {
guard let path = atPath else {
//there is no content
handleNoPath()
return
}
if let localSnap = FirebaseNode.node.getSnapshotWith(path) {
handleSnapshot(localSnap)
return
}
makeFirebaseNetworkTaskFor(path)
}
func handleSnapshot(_ snapshot: DataSnapshot) {
//use the content of the observed value, create and apply vars
DispatchQueue.main.async {
//handle UI updates/animations here
}
}
private func handleNoPath() {
//make the change.
}
private func makeFirebaseNetworkTaskFor(_ id: String) {
FirebaseNode.node.ref.child("go all the way to your object tree...").child(id).observeSingleEvent(of: .value, with: {
(snapshot) in
//Add the conditional logic here..
//If snapshot != "<null>"
FirebaseNode.node.addSnapToCache(id, snapshot)
self.handleSnapshot(snapshot)
//If snapshot == "<null>"
return
}, withCancel: nil)
}
}
但有一点,使用 NSCache:这对于中小型列表或内容范围非常有效;但它具有内存管理功能,可以在内存变得稀缺时取消分配内容。因此,在处理像您这样的大型集合时,您不妨使用分类字典,因为它的内存不会自动取消分配。使用它就像交换东西一样简单:
class FirebaseNode {
//This allows you to set a single point of reference for Firebase Database accross your app
static let node = FirebaseNode()
let ref = Database.database().reference(fromURL: "Your Firebase URL")
//This is the cache, set to private, since type casting between String and NSString would add messiness to your code
private var cache2 : [String:DataSnapshot] = [:]
func getSnapshotWith(_ id: String) -> DataSnapshot? {
return cache2[id]
}
func addSnapToCache(_ id: String,_ snapshot: DataSnapshot) {
cache2[id] = snapshot
}
}
此外,始终确保您通过 node
对 Firebasenode
的强引用,这确保您始终使用 Firebasenode
的一个实例。也就是说,这是可以的:Firebasenode.node.ref
或 Firebasenode.node.getSnapshot..
,这不是:Firebasenode.ref
或 Firebasenode().ref
我会尽力解释我在做什么。我有一个无限滚动的集合视图,它从我的 firebase 数据库中为每个单元格加载一个值。每次集合视图创建一个单元格时,它都会在数据库位置调用 .observe 并获取一个 snapchat。如果它加载任何值,它会将单元格背景设置为黑色。黑色背景 = 从数据库加载的单元格。如果您查看下图,您会发现并非所有单元格都从数据库中加载。数据库不能处理这么多调用吗?是线程问题吗?我正在做的事情是否适用于 Firebase?截至目前,我在我的 override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 方法
经过一些测试,它似乎可以很好地从 firebase 加载所有内容,但不会更新 UI。这让我相信它会在出于某种原因加载信息之前创建单元格?我会想办法弄清楚如何'block'以某种方式做到这一点。
The image
您应该将加载委托给单元格本身,而不是您的 collectionView: cellForItemAtIndexPath 方法。这样做的原因是委托方法将异步挂起并用于 FireBase 网络任务的回调。虽然后者通常很快(根据经验),但您可能会在 UI 加载时遇到一些问题。根据您视图中的方块数量来判断。
理想情况下,您会想要这样的东西:
import FirebaseDatabase
class FirebaseNode {
//This allows you to set a single point of reference for Firebase Database accross your app
static let ref = Database.database().reference(fromURL: "Your Firebase URL")
}
class BasicCell : UICollectionViewCell {
var firPathObserver : String { //This will make sure that as soon as you set the value, it will fetch from firebase
didSet {
let path = firPathObserver
FirebaseNode.ref.thePathToYouDoc(path) ..... {
snapshot _
self.handleSnapshot(snapshot)
}
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupSubViews()
}
func setupSubViews() {
//you add your views here..
}
func handleSnapshot(_ snapshot: FIRSnapshot) {
//use the content of the observed value
DispatchQueue.main.async {
//handle UI updates/animations here
}
}
}
你会用到它:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let path = someWhereThatStoresThePath(indexPath.item)//you get your observer ID according to indexPath.item.. in whichever way you do this
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Your Cell ID", for: indexPath) as! BasicCell
cell.firPathObserver = path
return cell
}
如果这不起作用,则可能是您遇到了一些 Firebase 限制。我认为这不太可能。
更新 .. 进行一些更正并使用本地缓存。
class FirebaseNode {
//This allows you to set a single point of reference for Firebase Database accross your app
static let node = FirebaseNode()
let ref = Database.database().reference(fromURL: "Your Firebase URL")
//This is the cache, set to private, since type casting between String and NSString would add messiness to your code
private var cache2 = NSCache<NSString, DataSnapshot>()
func getSnapshotWith(_ id: String) -> DataSnapshot? {
let identifier = id as NSString
return cache2.object(forKey: identifier)
}
func addSnapToCache(_ id: String,_ snapshot: DataSnapshot) {
cache2.setObject(snapshot, forKey: id as NSString)
}
}
class BasicCell : UICollectionViewCell {
var firPathObserver : String? { //This will make sure that as soon as you set the value, it will fetch from firebase
didSet {
handleFirebaseContent(self.firPathObserver)
}
}
override init(frame: CGRect) {
super.init(frame: frame)
setupSubViews()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupSubViews() {
//you add your views here..
}
func handleFirebaseContent(_ atPath: String?) {
guard let path = atPath else {
//there is no content
handleNoPath()
return
}
if let localSnap = FirebaseNode.node.getSnapshotWith(path) {
handleSnapshot(localSnap)
return
}
makeFirebaseNetworkTaskFor(path)
}
func handleSnapshot(_ snapshot: DataSnapshot) {
//use the content of the observed value, create and apply vars
DispatchQueue.main.async {
//handle UI updates/animations here
}
}
private func handleNoPath() {
//make the change.
}
private func makeFirebaseNetworkTaskFor(_ id: String) {
FirebaseNode.node.ref.child("go all the way to your object tree...").child(id).observeSingleEvent(of: .value, with: {
(snapshot) in
//Add the conditional logic here..
//If snapshot != "<null>"
FirebaseNode.node.addSnapToCache(id, snapshot)
self.handleSnapshot(snapshot)
//If snapshot == "<null>"
return
}, withCancel: nil)
}
}
但有一点,使用 NSCache:这对于中小型列表或内容范围非常有效;但它具有内存管理功能,可以在内存变得稀缺时取消分配内容。因此,在处理像您这样的大型集合时,您不妨使用分类字典,因为它的内存不会自动取消分配。使用它就像交换东西一样简单:
class FirebaseNode {
//This allows you to set a single point of reference for Firebase Database accross your app
static let node = FirebaseNode()
let ref = Database.database().reference(fromURL: "Your Firebase URL")
//This is the cache, set to private, since type casting between String and NSString would add messiness to your code
private var cache2 : [String:DataSnapshot] = [:]
func getSnapshotWith(_ id: String) -> DataSnapshot? {
return cache2[id]
}
func addSnapToCache(_ id: String,_ snapshot: DataSnapshot) {
cache2[id] = snapshot
}
}
此外,始终确保您通过 node
对 Firebasenode
的强引用,这确保您始终使用 Firebasenode
的一个实例。也就是说,这是可以的:Firebasenode.node.ref
或 Firebasenode.node.getSnapshot..
,这不是:Firebasenode.ref
或 Firebasenode().ref