如何将Swift闭包获取的Firestore模型数据传递到Segue目标控制器变量
问题原因
你的问题本质是异步执行顺序和Segue生命周期不匹配:
prepare(for:sender:)是Segue触发后立刻同步执行的方法,执行完毕后Segue就会完成跳转,展示目标控制器- Firestore的
getDocument是异步网络请求,发起后不会阻塞当前线程,等请求拿到结果时prepare方法早就执行完毕了,自然无法拿到异步返回的model给目标控制器赋值 - 除此之外你现有代码还有两个编译问题:
- 你的
getUpdatedModel回调仅返回AccountModel?类型,没有返回error参数,闭包内的if let error = error会直接报错 - 变量
d是闭包内部的局部变量,外部作用域无法访问,vc.account = d会报变量未定义错误
- 你的
最优解决方案:先请求数据,成功后手动触发Segue
这个方案体验最流畅,逻辑也最清晰:
步骤1:修改Segue绑定
把Storyboard里原来绑定在点击元素(按钮、Cell等)上的自动Segue删除,改为从当前ViewController本体拖拽到目标AnotherViewController的通用Segue,给Segue设置唯一标识符,比如showAnotherViewController
步骤2:修正你的getUpdatedModel方法
补充错误回调,同时去掉强制解包减少崩溃风险:
func getUpdatedModel(callback: @escaping (AccountModel?, Error?)->Void) { guard let documentId = self.documentId else { callback(nil, NSError(domain: "AccountModel", code: -1, userInfo: [NSLocalizedDescriptionKey: "documentId为空"])) return } let db = Firestore.firestore() let ref = db.collection("collectionName").document(documentId) ref.getDocument { (document, error) in // 先处理错误 if let error = error { callback(nil, error) return } guard let document = document, document.exists, let data = document.data() else { callback(nil, NSError(domain: "AccountModel", code: -2, userInfo: [NSLocalizedDescriptionKey: "文档不存在"])) return } let model = AccountModel.init(data, userId: "test") callback(model, nil) } }
步骤3:在触发跳转的操作中先请求数据,再手动触发Segue
比如你原来是点击按钮触发跳转,就把按钮点击事件改为如下逻辑:
@IBAction func jumpButtonClicked(_ sender: UIButton) { // 先发起请求 self.accountModel?.getUpdatedModel() { [weak self] model, error in guard let self = self, let model = model, error == nil else { print("获取模型失败:\(error?.localizedDescription ?? "")") // 可在这里加错误提示 return } // 请求成功后手动触发Segue,把model作为sender参数传递 DispatchQueue.main.async { self.performSegue(withIdentifier: "showAnotherViewController", sender: model) } } }
步骤4:修改prepare方法,直接取值赋值
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showAnotherViewController", let vc = segue.destination as? AnotherViewController, let accountModel = sender as? AccountModel { vc.account = accountModel } }
备选方案:自动Segue触发后异步更新目标控制器
如果你坚持要用自动Segue,也可以先跳转,再异步更新目标控制器的属性和UI,缺点是跳转后会有加载等待的空窗期,体验不如第一种方案:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if let vc = segue.destination as? AnotherViewController { // 先给目标控制器传个加载状态,展示加载UI vc.showLoading() // 发起异步请求,成功后更新目标控制器的属性 self.accountModel?.getUpdatedModel() { [weak vc] model, error in guard let model = model, error == nil else { DispatchQueue.main.async { vc?.showError() } return } DispatchQueue.main.async { vc?.account = model vc?.refreshUI() // 目标控制器收到model后刷新页面 } } } }
内容的提问来源于stack exchange,提问作者Zach Smith
相关产品推荐
相关产品推荐

