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

如何优雅处理视图控制器中代理未使用的方法?

针对你遇到的这个代理方法冗余实现的问题,我有两个比较优雅的解决方案,既能保留编译器对必要方法的强制实现提示,又不用写那些永远不会被调用的空方法:

方案一:拆分代理协议(推荐,保留编译时安全)

核心思路是把原本的单一代理协议拆分成基础协议和扩展协议,让不同场景的视图控制器遵循对应的协议,只实现自己需要的方法。

步骤1:拆分协议

// 基础协议:所有使用该视图的控制器都必须实现的方法
protocol TheViewBaseDelegate: class {
    func button1Tapped()
}

// 扩展协议:仅当需要显示button2时才需要遵循的协议,继承基础协议
protocol TheViewWithButton2Delegate: TheViewBaseDelegate {
    func button2Tapped()
}

步骤2:修改通用视图的代理逻辑

在视图内部区分两种代理类型,根据按钮的显示状态决定使用哪个代理:

class TheView: UIView {
    // 基础代理:所有场景都需要设置
    weak var baseDelegate: TheViewBaseDelegate?
    
    // 扩展代理:仅当显示button2时生效
    private weak var extendedDelegate: TheViewWithButton2Delegate?
    
    // 配置按钮显示状态的方法
    func setup(showButton2: Bool) {
        button2.isHidden = !showButton2
        
        // 如果需要显示button2,将baseDelegate强转为扩展代理类型
        if showButton2 {
            guard let delegate = baseDelegate as? TheViewWithButton2Delegate else {
                fatalError("需要显示button2时,代理必须遵循TheViewWithButton2Delegate协议")
            }
            extendedDelegate = delegate
        } else {
            extendedDelegate = nil
        }
    }
    
    // Button1点击事件
    @IBAction func button1Pressed(_ sender: UIButton) {
        baseDelegate?.button1Tapped()
    }
    
    // Button2点击事件
    @IBAction func button2Pressed(_ sender: UIButton) {
        extendedDelegate?.button2Tapped()
    }
}

步骤3:视图控制器的使用

  • ViewControllerA(需要两个按钮):遵循TheViewWithButton2Delegate,编译器会强制实现两个方法:
class ViewControllerA: UIViewController, TheViewWithButton2Delegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        theView.baseDelegate = self
        theView.setup(showButton2: true)
    }
    
    func button1Tapped() {
        // 处理button1逻辑
    }
    
    func button2Tapped() {
        // 处理button2逻辑
    }
}
  • ViewControllerB(仅需要一个按钮):只需要遵循TheViewBaseDelegate,仅实现必要的方法:
class ViewControllerB: UIViewController, TheViewBaseDelegate {
    override func viewDidLoad() {
        super.viewDidLoad()
        theView.baseDelegate = self
        theView.setup(showButton2: false)
    }
    
    func button1Tapped() {
        // 处理button1逻辑
    }
}

这个方案的优势在于:

  • 完全保留了编译器的强制提示:需要实现的方法一个都漏不掉,不需要的方法完全不用写
  • 类型安全:如果在需要显示button2的场景下,代理没有遵循扩展协议,会直接触发运行时断言,避免逻辑漏洞

方案二:用闭包替代代理(更灵活)

如果你觉得代理拆分有点繁琐,也可以把按钮的点击事件改成闭包回调,这样视图控制器可以只设置自己需要的回调:

修改通用视图

class TheView: UIView {
    // Button1的回调(设为非可选,保证必须设置,保留编译时提示)
    var onButton1Tap: () -> Void
    // Button2的回调(可选,仅当显示按钮时需要设置)
    var onButton2Tap: (() -> Void)?
    
    init(onButton1Tap: @escaping () -> Void) {
        self.onButton1Tap = onButton1Tap
        super.init(frame: .zero)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    func setup(showButton2: Bool) {
        button2.isHidden = !showButton2
    }
    
    @IBAction func button1Pressed(_ sender: UIButton) {
        onButton1Tap()
    }
    
    @IBAction func button2Pressed(_ sender: UIButton) {
        onButton2Tap?()
    }
}

视图控制器的使用

  • ViewControllerA:
class ViewControllerA: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let theView = TheView(onButton1Tap: { [weak self] in
            // 处理button1逻辑
        })
        theView.setup(showButton2: true)
        theView.onButton2Tap = { [weak self] in
            // 处理button2逻辑
        }
        view.addSubview(theView)
    }
}
  • ViewControllerB:
class ViewControllerB: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        let theView = TheView(onButton1Tap: { [weak self] in
            // 处理button1逻辑
        })
        theView.setup(showButton2: false)
        // 完全不需要管onButton2Tap
        view.addSubview(theView)
    }
}

这个方案的优势是更轻量、灵活,不需要定义一堆协议;缺点是如果某些回调是必须的,只能通过非可选闭包来保证,对于多按钮的复杂场景,不如代理协议的结构清晰。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 06:43:08