如何优雅处理视图控制器中代理未使用的方法?
针对你遇到的这个代理方法冗余实现的问题,我有两个比较优雅的解决方案,既能保留编译器对必要方法的强制实现提示,又不用写那些永远不会被调用的空方法:
方案一:拆分代理协议(推荐,保留编译时安全)
核心思路是把原本的单一代理协议拆分成基础协议和扩展协议,让不同场景的视图控制器遵循对应的协议,只实现自己需要的方法。
步骤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
相关产品推荐
相关产品推荐

