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

如何为EIUIBarButtonItem的闭包添加弱引用避免控制器被持有?

解决EIUIBarButtonItem的循环引用问题

首先得搞清楚为什么控制器无法释放:你的控制器持有了EIUIBarButtonItem实例,而按钮的handle闭包又强引用了控制器(因为你传入的controller.buttonAction是控制器的实例方法,闭包会默认捕获控制器的强引用),这样就形成了控制器 → 按钮 → 闭包 → 控制器的循环引用链,导致两者都无法被ARC回收。

下面给你两种解决方案,你可以根据需求选择:

方案一:修改闭包捕获方式(无需修改类代码)

这是最简单的处理方式,只需要在创建按钮时,显式在闭包捕获列表中用weak self弱化对控制器的引用:

let barButton = EIUIBarButtonItem(image: myUIImage, style: .plain) { [weak self] sender in
    self?.buttonAction(sender)
}

这样闭包只会持有控制器的弱引用,当控制器被销毁时self会自动变为nil,循环引用链直接被打破。

方案二:修改EIUIBarButtonItem类代码,自动支持弱引用

如果你希望组件本身就能处理弱引用,不需要使用者手动添加捕获列表,可以调整类的设计,通过包装闭包实现自动弱引用:

class EIUIBarButtonItem: UIBarButtonItem {
    // 定义弱引用闭包包装类
    private class WeakActionWrapper<T: AnyObject> {
        weak var target: T?
        let action: (T, UIBarButtonItem) -> Void
        
        init(target: T, action: @escaping (T, UIBarButtonItem) -> Void) {
            self.target = target
            self.action = action
        }
    }
    
    private var actionWrapper: AnyObject?
    
    // 新增支持弱引用的初始化方法
    convenience init<T: AnyObject>(image: UIImage?, style: UIBarButtonItemStyle, target: T, action: @escaping (T, UIBarButtonItem) -> Void) {
        self.init(image: image, style: style)
        self.actionWrapper = WeakActionWrapper(target: target, action: action)
        self.target = self
        self.action = #selector(buttonAction(_:))
    }
    
    // 保留原有闭包初始化方法(使用者仍需手动处理弱引用)
    typealias HandleClosure = (UIBarButtonItem) -> Void
    private var handle: HandleClosure?
    
    init(image: UIImage?, style: UIBarButtonItemStyle, handle: HandleClosure? = nil) {
        self.handle = handle
        super.init()
        self.image = image
        self.style = style
        self.target = self
        self.action = #selector(buttonAction(_:))
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
    }
    
    @objc private func buttonAction(_ sender: UIBarButtonItem) {
        // 调用原有闭包
        handle?(sender)
        
        // 调用弱引用包装的闭包
        if let wrapper = actionWrapper as? WeakActionWrapper<AnyObject>, let target = wrapper.target {
            wrapper.action(target, sender)
        }
    }
}

使用修改后的类时,你可以这样创建按钮:

let barButton = EIUIBarButtonItem(image: myUIImage, style: .plain, target: self) { (controller, sender) in
    controller.buttonAction(sender)
}

这里的target会被WeakActionWrapper弱引用,自动避开循环引用,使用者不需要手动处理捕获列表。

注意点

  • 方案一更轻量,适合大多数日常场景,只需要使用者注意闭包的捕获方式;
  • 方案二封装性更好,适合通用组件的封装,能降低使用者的出错概率。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 06:23:34