You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Swift异步闭包问题:UICollectionView数据源加载异常

Fixing Your UICollectionView Data Loading Issue with Closures & Firebase

Hey there, let's break down exactly what's going wrong with your code and get that collectionView loading data properly!

The Core Problems

You're running into three key issues that are blocking your data from showing up:

  1. Asynchronous Execution Timing
    In your viewDidLoad, you set self.datasource = data and call collectionView?.reloadData() before your getPrivatePosts closure finishes fetching data. Since Firebase calls are async, your collectionView refreshes when data.posts is still empty. Even after you assign data.posts = posts! in the closure, you never tell the collectionView to refresh again.

  2. Firebase Listener Misuse
    You're using observe(.value) which sets up a persistent listener that triggers every time the data changes. For initial data loading, you should use observeSingleEvent(of: .value) to fetch the data once (unless you want real-time updates, which isn't indicated here).

  3. Premature Completion Calls
    Your completion(ret) is called inside the loop over each contact's privatePosts. This means you're returning partial data multiple times instead of waiting until all contacts' posts are collected.

The Fixed Code

Let's rewrite your code step by step to fix these issues:

1. Update Your homeView Class

First, move the homeDataSource instance to a class property so it's accessible everywhere, and fix the async flow:

import LBTAComponents
import Firebase

class homeView: DatasourceController {
    // Make data a class property instead of local to viewDidLoad
    let data = homeDataSource()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        self.datasource = data
        
        getPrivatePosts { [weak self] posts in
            guard let self = self, let posts = posts else { return }
            
            // Assign posts to datasource on the MAIN THREAD (UI updates must be on main)
            DispatchQueue.main.async {
                self.data.posts = posts
                print("Final data.posts count: \(self.data.posts.count)")
                self.collectionView?.reloadData()
            }
        }
    }
    
    // Keep your existing collectionView layout methods here
    override func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: view.frame.width , height: 150)
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForHeaderInSection section: Int) -> CGSize {
        return CGSize(width: view.frame.width, height: 0 )
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize {
        return CGSize(width: view.frame.width, height: 0)
    }
    
    override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        performSegue(withIdentifier: "goToNewPost", sender: self)
    }

2. Fix the getPrivatePosts Function

We'll use a counter to wait for all contact posts to load, switch to single-event listeners, and only call completion once all data is ready:

func getPrivatePosts(completion : @escaping (_ privatePosts : [Post]?) ->()){
        var ret = [Post]()
        var pendingRequests = 0
        
        guard let currentUid = Auth.auth().currentUser?.uid else {
            completion(nil)
            return
        }
        
        staticValuesForData.instance.dataBaseUserref.child(currentUid).child("contacts").observeSingleEvent(of: .value) { snapshot in
            let contactSnapshots = snapshot.children.allObjects as! [DataSnapshot]
            pendingRequests = contactSnapshots.count
            
            // If no contacts, return empty array immediately
            if pendingRequests == 0 {
                completion(ret)
                return
            }
            
            for contactSnapshot in contactSnapshots {
                guard let contactUid = contactSnapshot.childSnapshot(forPath: "uid").value as? String else {
                    pendingRequests -= 1
                    if pendingRequests == 0 {
                        completion(ret)
                    }
                    continue
                }
                
                staticValuesForData.instance.dataBaseUserref.child(contactUid).child("privatePosts").observeSingleEvent(of: .value) { postSnapshot in
                    defer {
                        // Decrement counter after processing this contact's posts
                        pendingRequests -= 1
                        // If all requests are done, return the full array
                        if pendingRequests == 0 {
                            completion(ret)
                        }
                    }
                    
                    let posts = postSnapshot.children.allObjects as! [DataSnapshot]
                    for post in posts {
                        guard let dict = post.value as? [String : AnyObject] else { continue }
                        
                        // Parse post data
                        let fullname = dict["fullname"] as! String
                        let picUrl = dict["picUrl"] as! String
                        let postContent = dict["postContent"] as! String
                        let time = dict["time"] as! Int
                        let uid = dict["uid"] as! String
                        let username = dict["username"] as! String
                        let reposts = dict["reposts"] as! [String]
                        let downs = dict["downs"] as! [String]
                        
                        // Parse comments
                        var comments = [Comment]()
                        let commentArr = postSnapshot.childSnapshot(forPath: "comments").children.allObjects as! [DataSnapshot]
                        for c in commentArr {
                            guard let commentDict = c.value as? [String : AnyObject] else { continue }
                            let cuid = commentDict["uid"] as! String
                            let ccommentText = commentDict["commentText"] as! String
                            let cpicUrl = commentDict["picUrl"] as! String
                            let cusername = commentDict["username"] as! String
                            let ctime = commentDict["time"] as! Int
                            let com = Comment(uid: cuid, commentText: ccommentText, time: ctime, picUrl: cpicUrl, username: cusername)
                            comments.append(com)
                        }
                        
                        // Create post object and add to array
                        let postToAdd = Post(postContent: postContent, picUrl: picUrl, userName: username, fullName: fullname, postID: uid, postTime: time, downs: downs, reposts: reposts, comments: comments)
                        ret.append(postToAdd)
                    }
                }
            }
        }
    }
}

3. homeDataSource Class (No Changes Needed)

Your existing homeDataSource is fine, but just a reminder: make sure your userCell correctly uses the Post object from item(_ indexPath:) to populate its UI.

Key Improvements Explained

  • Class-level Datasource: Moving data to a class property ensures it's retained and accessible throughout the view controller's lifecycle.
  • Main Thread UI Updates: Wrapping reloadData() in DispatchQueue.main.async guarantees UI changes happen on the main thread (required for all UIKit operations).
  • Single-Event Listeners: observeSingleEvent fetches data once instead of setting up a permanent listener, which is better for initial loading.
  • Pending Request Counter: We track how many contact post requests are pending, and only call completion when all are done, ensuring we return the full set of posts.
  • Weak Self Capture: Using [weak self] in the closure prevents retain cycles (a common memory leak issue in iOS).

内容的提问来源于stack exchange,提问作者zackkkkkkk

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:19:49