【问题标题】:How to correctly load information from Firebase?如何从 Firebase 正确加载信息?
【发布时间】:2018-01-07 16:40:20
【问题描述】:

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

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

The image

【问题讨论】:

    标签: ios swift firebase firebase-realtime-database collectionview


    【解决方案1】:

    您应该将加载委托给单元格本身,而不是您的 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 限制......这在 imo 中不太可能。

    更新 .. 进行一些更正并使用本地缓存

    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 的ONE 实例。即,可以:Firebasenode.node.refFirebasenode.node.getSnapshot..,这不是:Firebasenode.refFirebasenode().ref

    【讨论】:

    • 效果很好,谢谢。我还有一个问题,也许你可以回答。我认为这可能是collectionview内存周期或其他问题。滚动时,它似乎没有清除单元格并且正在重复使用相同的单元格。如果我将 1 个单元格设置为蓝色,其他所有内容为白色,我将在向下滚动时随机看到蓝色单元格。每次我向下滚动 800 年左右时都会发生。
    • 将单元格设置为蓝色的逻辑是什么?
    • 在handleSnapshot 中,如果快照不等于“”,那么在disapatchQueue 下我设置self.backgroundColor = UIColor.blue。如果我在创建原始蓝色单元格后在那里设置断点,它将不会被命中,但仍会随机生成额外的蓝色单元格
    • 我认为这是因为在我设置值之前我没有将所有单元格值设置为空,并且在我创建更多单元格时它正在复制这些值。当我有机会时,我会测试这个。看起来这家伙有同样的问题This other post
    • 技术上是的,你应该在 setupViews() 中放置一个默认的“empy”值,因为当你在 collectionView 的委托方法中实例化 cell 时会调用它,然后通过设置firebase 路径变量,它开始它的网络任务。所有这一切的下一步是将您获取的值缓存在强引用中,这样您就不必在每次重新排队单元时重新执行网络任务。这是处理此问题(尤其是图像)的一个很好的示例/教程是本教程:youtu.be/ilwEMj7mWCY?list=PL0dzCUj1L5JE1wErjzEyVqlvx92VN3DL5
    猜你喜欢
    • 1970-01-01
    • 2017-12-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多