ios - 如何正确加载来自 Firebase 的信息?

标签 ios swift firebase firebase-realtime-database collectionview

我会尽力解释我在做什么。我有一个无限滚动的 Collection View ,它从我的 firebase 数据库中为每个单元格加载一个值。每次 Collection View 创建一个单元格时,它都会在数据库位置调用 .observe 并获取一个 snapchat。如果它加载任何值,它会将单元格背景设置为黑色。黑色背景 = 从数据库加载的单元格。如果您查看下图,您会发现并非所有单元格都从数据库中加载。数据库不能处理这么多调用吗?是线程问题吗?我正在做的事情是否适用于 Firebase?截至目前,我在我的 覆盖 func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 方法

经过一些测试后,它似乎可以很好地从 firebase 加载所有内容,但不会更新 UI。这让我相信它会在出于某种原因加载信息之前创建单元格?我将尝试弄清楚如何以某种方式使其“阻塞”。

The image

最佳答案

您应该将加载委托(delegate)给单元格本身,而不是您的 collectionView: cellForItemAtIndexPath 方法。这样做的原因是委托(delegate)方法将异步挂起并用于 FireBase 网络任务的回调。虽然后者通常很快(根据经验),但您可能会在此处加载 UI 时遇到一些问题。根据 View 中的方 block 数量来判断。

理想情况下,你会想要这样的东西:

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
    }

}

此外,始终确保您通过 nodeFirebasenode 的强引用,这确保您始终使用 Firebasenode。也就是说,这是可以的:Firebasenode.node.refFirebasenode.node.getSnapshot..,这不是:Firebasenode.refFirebasenode().ref

关于ios - 如何正确加载来自 Firebase 的信息?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45427218/

相关文章:

android - 如何保持 FirebaseRecyclerAdapter 的滚动位置

objective-c - iOS:addObserver 和 superview 查询

ios - 如何在应用程序启动时启动 admob 插页式广告,以便它首先使用 swift 显示

ios - PFQueryTableViewController 分页不适用于 heightForRowAtIndexPath

ios - dataSource 不会在 cellForItemAt 函数中更新

ios - Firebase 快速作为 OS X 的后端服务

ios - UIPickerView 在 UIView 中不显示添加的 subview

swift - Swift 中的函数参数类型

swift - 如何为 vapor web 框架编写中间件?

Android - 如何以编程方式检查当前是否启用了 Firebase Analytics?