Swift从Firebase构建用户数组遇越界问题求助
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 beforefetchPotentialMatches()finishes populatingmembersInSchool(the 1-second delay is a flimsy workaround, since network speeds vary). - No boundary check: You access
membersInSchool[cardCount]without verifying ifcardCountis within the array's valid range. - Duplicate entries: Using
observe(.value)instead ofobserveSingleEvent(.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

