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

Swift从Firebase构建用户数组遇越界问题求助

Fixing Array Out-of-Bounds & Replacing Delay with Completion Handler in Your Tinder-like Swift App

Hey there! Let's work through your two key issues step by step—resolving that array out-of-bounds error and replacing the unreliable DispatchQueue delay with a proper completion handler.

1. Fixing the Array Out-of-Bounds Error

Your crash happens for a few key reasons:

  • Race condition: setUpCard() runs before fetchPotentialMatches() finishes populating membersInSchool (the 1-second delay is a flimsy workaround, since network speeds vary).
  • No boundary check: You access membersInSchool[cardCount] without verifying if cardCount is within the array's valid range.
  • Duplicate entries: Using observe(.value) instead of observeSingleEvent(.value) can re-trigger data fetches multiple times, leading to duplicate users in your array (a hidden bug that could cause unexpected behavior later).

Quick Boundary Check Fix

First, add a guard clause in setUpCard() to prevent accessing invalid indices:

func setUpCard(){
    // Check if we have remaining matches to display
    guard cardCount < membersInSchool.count else {
        potentialMatchName.text = "No more matches!"
        potentialMatchGrade.text = ""
        return
    }
    
    let potMatch = membersInSchool[cardCount]
    let potMatchName = "\(potMatch.firstName!) \(potMatch.lastName!)"
    potentialMatchName.text = potMatchName
    potentialMatchGrade.text = potMatch.grade!
    cardCount += 1
}

Fix Data Fetch Reliability & Duplicates

Replace all instances of observe(.value) with observeSingleEvent(.value) in fetchPotentialMatches(). This ensures you only fetch data once, not every time the Firebase database updates:

// Replace userRef.observe(.value, ...) with:
userRef.observeSingleEvent(.value, with: { (snapshot) in
    // ... existing code
    
    // Then replace the school members observe call:
    ref.child("schools").child(currentUserSchool).child("members").observeSingleEvent(.value , with: { (snapshot) in
        // ... existing loop
        
        // And replace the other user observe call:
        ref.child("users").child(otherUsers).observeSingleEvent(.value, with: { (snap) in
            // ... existing code
        })
    })
})

2. Replacing Delay with a Completion Handler

The 1-second delay is a hack—we'll use a completion closure combined with DispatchGroup to wait for all asynchronous Firebase calls to finish before calling setUpCard().

Step 1: Update fetchPotentialMatches() with Completion & DispatchGroup

func fetchPotentialMatches(completion: @escaping () -> Void) {
    // Clear existing matches to avoid duplicates on re-fetch
    membersInSchool.removeAll()
    cardCount = 0
    
    let userRef = ref.child("users").child(uid!)
    userRef.observeSingleEvent(.value, with: { (snapshot) in
        guard let user = User(snapshot: snapshot) else {
            completion()
            return
        }
        
        let currentUserSchool = user.highSchool!
        let currentUserMatch = user.matchPreference!
        
        ref.child("schools").child(currentUserSchool).child("members").observeSingleEvent(.value , with: { (snapshot) in
            let group = DispatchGroup()
            
            for child in snapshot.children {
                guard let childSnapshot = child as? DataSnapshot,
                      let otherUserUID = childSnapshot.key as String?,
                      otherUserUID != uid! else {
                    continue
                }
                
                group.enter()
                ref.child("users").child(otherUserUID).observeSingleEvent(.value, with: { (snap) in
                    defer { group.leave() } // Ensure we leave the group even if parsing fails
                    
                    guard let otherU = User(snapshot: snap),
                          otherU.gender == currentUserMatch else {
                        return
                    }
                    
                    self.membersInSchool.append(otherU)
                    print("\(otherU.firstName!) \(otherU.lastName!)")
                })
            }
            
            // Trigger completion once all user requests are done
            group.notify(queue: .main) {
                completion()
            }
        })
    }){(error) in
        print(error.localizedDescription)
        completion() // Call completion even on error to avoid hanging
    }
}

Step 2: Update viewDidLoad() to Use the Completion Handler

override func viewDidLoad() {
    super.viewDidLoad()
    
    fetchPotentialMatches { [weak self] in
        // This runs ONLY after all matches are fetched successfully
        self?.setUpCard()
    }
}

Bonus: Improve User Struct Safety

Your current User init uses forced unwraps which can crash if Firebase data is missing. Update it to a failable initializer with optional binding:

struct User {
    var firstName: String
    var lastName: String
    var gender: String
    var matchPreference: String
    var photoURL: String?
    var highSchool: String
    var grade: String
    var uid: String
    var ref: DatabaseReference?
    var key: String
    
    init?(snapshot: DataSnapshot) {
        guard let value = snapshot.value as? [String: Any],
              let firstName = value["first name"] as? String,
              let lastName = value["last name"] as? String,
              let gender = value["gender"] as? String,
              let matchPreference = value["match preference"] as? String,
              let highSchool = value["School"] as? String,
              let grade = value["grade"] as? String,
              let uid = value["uid"] as? String else {
            return nil
        }
        
        self.key = snapshot.key
        self.ref = snapshot.ref
        self.firstName = firstName
        self.lastName = lastName
        self.gender = gender
        self.matchPreference = matchPreference
        self.photoURL = value["pic"] as? String
        self.highSchool = highSchool
        self.grade = grade
        self.uid = uid
    }
}

This way, if a user's data is incomplete, you won't crash—you'll just skip adding that user to your matches array.

内容的提问来源于stack exchange,提问作者A. Albert

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 03:45:06