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

无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
        }
    }
}

常见坑点说明

  1. 代理为nil的核心原因:很多人会忘记在实例化Worker后给Boss的delegate赋值,这时候点击指令按钮自然找不到代理对象。一定要在跳转前完成self.delegate = workerVC这一步!
  2. 提前点击指令的问题:如果用户先点doThis/doThat再跳转,此时Worker还没创建,指令会丢失。我们通过latestInstruction保存最新指令,跳转后自动发送,就能解决这个问题。
  3. 循环引用风险:代理属性必须用weak修饰,因为Worker会被Boss的delegate持有,同时如果是present/push的话,Worker也会被导航栈持有,用weak可以避免内存泄漏。

内容的提问来源于stack exchange,提问作者notSwiftDave

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 03:24:11