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

时间:2022-10-29 13:01:50

I will try my best to explain what I'm doing. I have an infinite scrolling collection view that loads a value from my firebase database for every cell. Every time the collection view creates a cell it calls .observe on the database location and gets a snapchat. If it loads any value it sets the cells background to black. Black background = cell loaded from database. If you look at the image below you can tell not all cells are loading from the database. Can the database not handle this many calls? Is it a threading issue? Does what I'm doing work with firebase? As of right now I call firebase in my override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { method

我会尽力解释我在做什么。我有一个无限滚动的集合视图,它从我的firebase数据库中为每个单元加载一个值。每次收集视图创建一个单元格时,它都会调用。在数据库位置上观察并得到一个snapchat。如果它加载任何值,它将单元格背景设置为黑色。从数据库加载的单元格。如果您查看下面的图片,您可以知道并不是所有的单元格都在从数据库中加载。数据库不能处理这么多调用吗?是线程问题吗?我在做的东西对firebase有用吗?现在,我在我的覆盖func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: indexPath: indexPath)中调用firebase——> UICollectionViewCell{方法

After some testing it seems that it is loading everything from firebase fine, but it's not updating the UI. This leads me to believe it's creating the cell before it's loading the information for some reason? I will try to figure out how to make it 'block' somehow.

在进行了一些测试之后,它似乎正在从firebase fine加载所有内容,但它并没有更新UI。这让我相信它是在加载信息之前创建的单元格?我将设法弄清楚如何使它“阻塞”。

The image

图像

1 个解决方案

#1


1  

You should delegate the loading to the cell itself, not your collectionView: cellForItemAtIndexPath method. The reason for this is the delegate method will hang asynchronously and for the callback of the FireBase network task. While the latter is usually quick (by experience), you might have some issues here with UI loading.. Judging by the number of squares on your view..

应该将加载委托给单元格本身,而不是您的集合视图:cellForItemAtIndexPath方法。原因是委托方法将异步挂起并用于FireBase网络任务的回调。虽然后者通常是快速的(根据经验),但是您在这里可能会遇到一些UI加载的问题。从你视图上的方块数目来判断。

So ideally, you'd want something like this:

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

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
        }
    }

}

And you'd use it:

你会使用它:

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
    }

If this doesn't work, it's probable that you might be encountering some Firebase limitation.. which is rather unlikely imo.

如果这不起作用,很有可能您会遇到一些防火墙限制。这在我看来是不太可能的。

Update .. with some corrections and with local cache.

更新. .有一些修正和本地缓存。

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)
    }

}

One point however, using NSCache: this works really well for small to medium sized lists or ranges of content; but it has a memory management feature which can de-alocated content if memory becomes scarce.. So when dealing with larger sets like yours, you might as well go for using a classing Dictionnary, as it's memory will not be de-alocated automatically. Using this just becomes as simple as swaping things out:

但有一点,使用NSCache非常适合中小型列表或内容范围;但是它有一个内存管理功能,如果内存变得稀缺,它可以删除内容。因此,当处理像您这样的较大的集合时,您不妨使用classing Dictionnary,因为它的内存不会自动取消注册。使用它就像把东西包起来一样简单:

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
    }

}

Also, always make sure you got through the node strong reference to Firebasenode, this ensures that you're always using the ONE instance of Firebasenode. Ie, this is ok: Firebasenode.node.ref or Firebasenode.node.getSnapshot.., this is not: Firebasenode.ref or Firebasenode().ref

此外,始终确保您通过了对Firebasenode的节点强引用,这将确保您始终使用Firebasenode的一个实例。这个是ok的:Firebasenode.node。裁判或Firebasenode.node.getSnapshot . .,这不是:Firebasenode。.ref裁判或Firebasenode()

#1


1  

You should delegate the loading to the cell itself, not your collectionView: cellForItemAtIndexPath method. The reason for this is the delegate method will hang asynchronously and for the callback of the FireBase network task. While the latter is usually quick (by experience), you might have some issues here with UI loading.. Judging by the number of squares on your view..

应该将加载委托给单元格本身,而不是您的集合视图:cellForItemAtIndexPath方法。原因是委托方法将异步挂起并用于FireBase网络任务的回调。虽然后者通常是快速的(根据经验),但是您在这里可能会遇到一些UI加载的问题。从你视图上的方块数目来判断。

So ideally, you'd want something like this:

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

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
        }
    }

}

And you'd use it:

你会使用它:

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
    }

If this doesn't work, it's probable that you might be encountering some Firebase limitation.. which is rather unlikely imo.

如果这不起作用,很有可能您会遇到一些防火墙限制。这在我看来是不太可能的。

Update .. with some corrections and with local cache.

更新. .有一些修正和本地缓存。

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)
    }

}

One point however, using NSCache: this works really well for small to medium sized lists or ranges of content; but it has a memory management feature which can de-alocated content if memory becomes scarce.. So when dealing with larger sets like yours, you might as well go for using a classing Dictionnary, as it's memory will not be de-alocated automatically. Using this just becomes as simple as swaping things out:

但有一点,使用NSCache非常适合中小型列表或内容范围;但是它有一个内存管理功能,如果内存变得稀缺,它可以删除内容。因此,当处理像您这样的较大的集合时,您不妨使用classing Dictionnary,因为它的内存不会自动取消注册。使用它就像把东西包起来一样简单:

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
    }

}

Also, always make sure you got through the node strong reference to Firebasenode, this ensures that you're always using the ONE instance of Firebasenode. Ie, this is ok: Firebasenode.node.ref or Firebasenode.node.getSnapshot.., this is not: Firebasenode.ref or Firebasenode().ref

此外,始终确保您通过了对Firebasenode的节点强引用,这将确保您始终使用Firebasenode的一个实例。这个是ok的:Firebasenode.node。裁判或Firebasenode.node.getSnapshot . .,这不是:Firebasenode。.ref裁判或Firebasenode()