Ios 如何从Firebase正确加载信息?

Ios 如何从Firebase正确加载信息?,ios,swift,firebase,firebase-realtime-database,collectionview,Ios,Swift,Firebase,Firebase Realtime Database,Collectionview,我会尽力解释我在做什么。我有一个无限滚动集合视图,它从firebase数据库中为每个单元格加载一个值。每次集合视图创建它调用的单元格时。观察数据库位置并获取快照。如果加载任何值,则将单元格背景设置为黑色。黑色背景=从数据库加载的单元格。如果查看下图,可以看出并非所有单元格都是从数据库加载的。数据库不能处理这么多的调用吗?这是线程问题吗?我现在做的和firebase一起工作吗?从现在起,我在我的办公室里给firebase打电话 覆盖func collectionView(collectionVie

我会尽力解释我在做什么。我有一个无限滚动集合视图,它从firebase数据库中为每个单元格加载一个值。每次集合视图创建它调用的单元格时。观察数据库位置并获取快照。如果加载任何值,则将单元格背景设置为黑色。黑色背景=从数据库加载的单元格。如果查看下图,可以看出并非所有单元格都是从数据库加载的。数据库不能处理这么多的调用吗?这是线程问题吗?我现在做的和firebase一起工作吗?从现在起,我在我的办公室里给firebase打电话 覆盖func collectionView(collectionView:UICollectionView,cellForItemAt indexPath:indexPath)->UICollectionViewCell{ 方法

经过一些测试,它似乎正在加载firebase fine中的所有内容,但它没有更新UI。这让我相信它在加载信息之前就已经创建了单元格。出于某种原因,我将尝试找出如何使其“阻塞”


您应该将加载委托给单元格本身,而不是您的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)
    }

}

另外,始终确保您通过
节点
Firebasenode
的强引用,这确保您始终使用
Firebasenode
的一个实例。也就是说,这是可以的:
Firebasenode.node.ref
Firebasenode.node.getSnapshot..
,这不是:
Firebasenode.ref
Firebasenode().ref

非常好,谢谢。我还有一个问题,也许你可以回答。我想这可能是因为collectionview内存周期或其他问题。滚动时,它似乎没有清除单元格,而是重复使用相同的单元格。如果我将1个单元格设置为蓝色,则每隔一个单元格设置为白色,我将随机看到蓝色单元格当我向下滚动时。每次向下滚动800 y左右时都会发生。将单元格设置为蓝色背后的逻辑是什么?在handleSnapshot内部,如果快照不等于“”然后在disapatchQueue下,我设置self.backgroundColor=UIColor.blue。如果我在创建原始蓝色单元格后在那里设置断点,它不会被命中,但仍然会生成随机的额外蓝色单元格。我认为这是因为我没有在设置这些值之前将所有单元格值设置为空,而是在创建更多单元格时复制这些值。我会在有机会的时候进行测试。看起来这家伙在技术上也有同样的问题是的,你应该在setupview()中设置一个默认的“empy”值,因为当您在
collectionView
的委托方法中实例化
单元格时会调用该函数,然后通过设置firebase path变量,它开始网络任务。所有这些的下一步是将您获取的值缓存在强引用中,这样您就不必每次创建单元格时都重新执行网络任务已重新排队。以下是处理此问题(尤其是图像)的一个很好的示例/教程:
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
    }

}