无Segue的Swift代理模式问题:点击按钮时代理对象为nil
解决Swift代理模式中代理对象为nil的问题(无Segue场景)
我来帮你搞定这个代理nil的问题!在不用Segue的情况下,核心是要抓准代理设置的时机,下面一步步给你讲清楚完整实现和避坑点:
第一步:定义代理协议
首先得明确Boss和Worker之间的通信规则,我们定义一个类类型的协议(必须加AnyObject,这样才能用weak修饰代理,避免循环引用):
import UIKit // 代理协议:规定Boss向Worker发送指令的方法 protocol BossInstructionDelegate: AnyObject { func receiveInstruction(_ instruction: String) }
第二步:实现Boss视图控制器(TheBoss VC)
在Boss里,我们需要持有代理对象、处理按钮点击事件,重点是在跳转Worker时手动设置代理:
class TheBossVC: UIViewController { // 关联Storyboard里的按钮(如果是代码创建的话,自己初始化即可) @IBOutlet weak var doThisButton: UIButton! @IBOutlet weak var doThatButton: UIButton! @IBOutlet weak var goToTheWorkerButton: UIButton! // 代理对象,必须用weak修饰!防止循环引用 weak var delegate: BossInstructionDelegate? // 保存最新的指令,解决「先点指令再跳转」的丢失问题 private var latestInstruction: String? override func viewDidLoad() { super.viewDidLoad() // 给按钮绑定点击事件(如果是Storyboard关联的话,这步也可以省略) setupButtonActions() } private func setupButtonActions() { doThisButton.addTarget(self, action: #selector(doThisTapped), for: .touchUpInside) doThatButton.addTarget(self, action: #selector(doThatTapped), for: .touchUpInside) goToTheWorkerButton.addTarget(self, action: #selector(navigateToWorker), for: .touchUpInside) } // 点击「做这件事」按钮 @objc private func doThisTapped() { latestInstruction = "去做这件事!" sendInstructionToWorker() } // 点击「做那件事」按钮 @objc private func doThatTapped() { latestInstruction = "去做那件事!" sendInstructionToWorker() } // 统一发送指令的方法,先判断代理是否存在 private func sendInstructionToWorker() { guard let instruction = latestInstruction else { return } delegate?.receiveInstruction(instruction) } // 跳转到Worker页面的核心逻辑 @objc private func navigateToWorker() { // 手动实例化Worker VC(如果是从Storyboard取的话,用下面的注释代码) // let workerVC = storyboard?.instantiateViewController(withIdentifier: "TheWorkerVC") as! TheWorkerVC let workerVC = TheWorkerVC() // 关键!这里必须把Worker设置为Boss的代理,不然代理就是nil self.delegate = workerVC // 如果跳转后需要同步之前的指令,直接调用发送方法 sendInstructionToWorker() // 跳转页面(根据你的导航结构选present或者push) present(workerVC, animated: true) // navigationController?.pushViewController(workerVC, animated: true) } }
第三步:实现Worker视图控制器(TheWorker VC)
Worker需要遵守我们定义的代理协议,实现接收指令的方法,把内容显示在文本框里:
class TheWorkerVC: UIViewController, BossInstructionDelegate { // 关联Storyboard里的文本框 @IBOutlet weak var instructionTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() // 这里不需要额外设置,代理是由Boss那边赋值的 } // 实现代理方法,更新UI的时候记得在主线程 func receiveInstruction(_ instruction: String) { DispatchQueue.main.async { self.instructionTextField.text = instruction } } }
常见坑点说明
- 代理为nil的核心原因:很多人会忘记在实例化Worker后给Boss的
delegate赋值,这时候点击指令按钮自然找不到代理对象。一定要在跳转前完成self.delegate = workerVC这一步! - 提前点击指令的问题:如果用户先点doThis/doThat再跳转,此时Worker还没创建,指令会丢失。我们通过
latestInstruction保存最新指令,跳转后自动发送,就能解决这个问题。 - 循环引用风险:代理属性必须用
weak修饰,因为Worker会被Boss的delegate持有,同时如果是present/push的话,Worker也会被导航栈持有,用weak可以避免内存泄漏。
内容的提问来源于stack exchange,提问作者notSwiftDave
相关产品推荐
相关产品推荐

