Firestore加载数据时Optional解包崩溃及计数异常求助
问题概述
视频列表TableViewController可正常显示name和lessonName字段,但解包imageName对应的UIImage、fileURL对应的URL时触发「Found nil while unwrapping optional」崩溃;控制台多次打印videos count,先输出3次videos count = 0,后输出3次videos count = 2,存在数据加载时序或代码逻辑问题。
报错根源分析
- 资源路径不匹配:从日志可见
imageName和fileURL是Firebase Storage的gs://协议路径,而非本地资源名或标准HTTP/HTTPS URL,直接用UIImage(named:)和URL(string:)无法解析,返回nil后强制解包导致崩溃。 - ViewModel数组赋值逻辑错误:在
fetchData的documents.map闭包中错误调用videos.append,导致videos数组被重复赋值,触发多次@Published回调,进而引发TableView多次reload,出现重复的count打印。 - 无容错的强制解包:所有可能返回nil的操作(如图片加载、URL解析)未做可选绑定,直接使用
!强制解包,一旦值为nil就触发崩溃。
分步解决方案
1. 修复ViewModel数组赋值逻辑
移除map闭包内的videos.append调用,避免数组重复赋值,减少不必要的TableView reload触发。
修改后的VideosViewModel代码:
import Foundation import FirebaseFirestore class VideosViewModel: ObservableObject { @Published var videos = [Video]() private var db = Firestore.firestore() func fetchData() { db.collection("videos").addSnapshotListener { [self] (querySnapshot, error) in guard let documents = querySnapshot?.documents else { print("No Documents") return } self.videos = documents.map { queryDocumentSnapshot -> Video in let data = queryDocumentSnapshot.data() let name = data["name"] as? String ?? "" let imageName = data["imageName"] as? String ?? "" let lessonName = data["lessonName"] as? String ?? "" let fileURL = data["fileURL"] as? String ?? "" print(data) return Video(name: name, imageName: imageName, lessonName: lessonName, fileURL: fileURL) } } } }
2. 正确加载Firebase Storage资源
gs://路径是Firebase Storage的内部标识,需通过Firebase Storage SDK获取下载URL后再加载资源:
加载列表项图片
在cellForRowAt中替换原图片加载代码,使用Storage SDK异步获取图片并加载:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let video = viewModel.videos[indexPath.row] tableView.tableFooterView = UIView() // 基础配置保持不变 cell.textLabel?.text = video.name cell.detailTextLabel?.text = video.lessonName cell.accessoryType = .disclosureIndicator let backgroundView = UIView() backgroundView.backgroundColor = UIColor(named: "VideoLessonsCellHighlighted") cell.selectedBackgroundView = backgroundView cell.textLabel?.font = UIFont(name: "Helvetica-Bold", size: 14) cell.detailTextLabel?.font = UIFont(name: "Helvetica", size: 12) // 异步加载Firebase Storage图片 let storageRef = Storage.storage().reference(forURL: video.imageName) storageRef.downloadURL { url, error in if let error = error { print("获取图片下载URL失败:\(error.localizedDescription)") return } guard let imageURL = url else { print("图片URL无效") return } URLSession.shared.dataTask(with: imageURL) { data, _, error in if let error = error { print("下载图片失败:\(error.localizedDescription)") return } guard let imageData = data, let image = UIImage(data: imageData) else { print("无法解析图片数据") return } DispatchQueue.main.async { cell.imageView?.image = image.withRenderingMode(.alwaysTemplate) } }.resume() } return cell }
加载播放视频
修改playVideo方法,先获取视频的下载URL再初始化AVPlayer:
func playVideo(at indexPath: IndexPath){ let selectedVideo = viewModel.videos[indexPath.row] let storageRef = Storage.storage().reference(forURL: selectedVideo.fileURL) storageRef.downloadURL { [weak self] url, error in guard let self = self else { return } if let error = error { print("获取视频下载URL失败:\(error.localizedDescription)") return } guard let videoURL = url else { print("视频URL无效") return } let player = AVPlayer(url: videoURL) self.playerViewController.player = player self.present(self.playerViewController, animated: true) { player.play() } } }
3. 移除所有强制解包操作
确保所有可能返回nil的操作都通过if let或guard let做可选绑定,彻底避免解包崩溃。
控制台count打印问题说明
原代码中ViewModel的videos.append导致数组被重复赋值,加上Firestore snapshotListener首次可能返回空文档、TableView初始加载的多次调用,引发多次count打印。修复ViewModel的赋值逻辑后,videos只会在数据更新时被正确赋值一次,TableView reload次数会恢复正常,count打印次数也会减少。
内容的提问来源于stack exchange,提问作者Vugar

