【问题标题】:How automatically remove callback from callbacks Array, when it has not caller any more?当回调数组不再有调用者时,如何自动从回调数组中删除回调?
【发布时间】:2021-05-09 17:58:32
【问题描述】:

我有这些课程:

class Callback {
    let callback: () -> Void
    init(callback: @escaping () -> Void) {
        self.callback = callback
    }
}

class CallbackContainer {
    private var callBacks = [Callback]()
    
    func add(callback: @escaping () -> Void) -> Callback {
        let cl = Callback(callback: callback)
        callBacks.append(cl)
        return cl
    }
    
    func callAll() {
        for callback in callBacks {
            callback.callback()
        }
    }
}

class Container {
    let callbackContainer = CallbackContainer()
    
    func executeSomeLongTasks() {
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {[weak self] in
            self?.callbackContainer.callAll()
        }
    }
}

class AViewController: UIViewController {
    var callback: Callback?
    let container: CallbackContainer
    init(container: CallbackContainer) {
        self.container = container
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        callback = container.add {
            debugPrint("Called callback on A")
        }
    }
    
    deinit {
        debugPrint("Deinited AViewController")
    }
}

我希望CallbackContainer 在没有人再调用它们时自动删除回调。例如:

let container = Container()

var aVC: AViewController? = AViewController(container: container.callbackContainer)

//on somewhere executed long task
container.executeSomeLongTasks()
aVC = nil
// should not call callback here

在这里,当aVC 被释放时——比如aVC = nil 或从导航堆栈中弹出它——那么存储在callbackContainer 中的回调(在AViewController viewDidload() 上创建)应该自动从内部数组中删除,而不需要需要手动在AViewController deinit() 上删除它。因此,不会再从CallbackContainer.callAll()调用回调

它应该像RxSwift 上的DisposeBag 一样工作。看了DisposeBag的代码,没看懂。

有人知道吗?

【问题讨论】:

    标签: swift xcode memory-management callback release


    【解决方案1】:

    你需要的是一种弱数组。据我所知,在 Swift 基础类中没有这样的类型:我的第一个想法是使用 NSMapTable 或与 weakToStrongObjects 配置类似的东西,它被记录为将键存储为弱引用,但不幸的是那个类不会自动重组,请参阅discussion part

    不建议使用从弱到强的映射表。被清零的弱键的强值将继续保持,直到映射表自行调整大小。

    你可以做的是:

    • 创建一个存储弱引用和闭包的装箱对象
    • 将该框添加到callBacks 数组中。
    • 调用callAll时,先清理callBacks数组,删除所有引用nil的框。

    手动清理是一个缺点,因为当弱引用是由 ARC 领导的 nil 时,您不会收到通知。但它是由 Container 集中完成的,所以客户端不需要关心。

    例如:

    typealias CallbackType = () -> Void
    
    class WeakCallback : Equatable {
        let id = UUID()
        
        weak var refItem: AnyObject?
        let callback:CallbackType
        
        init(refItem: AnyObject, callback:@escaping CallbackType) {
            self.refItem = refItem
            self.callback = callback
        }
        
        static func == (lhs: WeakCallback, rhs: WeakCallback) -> Bool {
            return lhs.id == rhs.id
        }
    }
    
    
    class CallbackContainer {
        var callBacks = [WeakCallback]()
        
        func add(ref:AnyObject, callback: @escaping () -> Void) -> WeakCallback {
            cleanupCallbackArray()
            let cl = WeakCallback(refItem:ref, callback: callback)
            callBacks.append(cl)
            return cl
        }
        
        func callAll() {
            cleanupCallbackArray()
            for cb in callBacks {
                if (cb.refItem != nil) {
                    cb.callback()
                } else {
                    print ("oops, found nil for \(cb.id)")
                }
            }
        }
        func cleanupCallbackArray() {
            callBacks = callBacks.filter { cb -> Bool in
                if (cb.refItem == nil) {
                    print ("\(cb.id) has nil reference, will be removed")
                    return false
                }
                return true
            }
        }
    }
    

    然后,在viewDidLoad 中,添加视图控制器作为对容器的引用:

        override func viewDidLoad() {
            super.viewDidLoad()
            callback = container.add (ref:self) {
                debugPrint("Called callback on A")
            }
        }
    

    测试:

    let cbContainer = CallbackContainer()
    
    var aVC: AViewController? = AViewController(container: cbContainer)
    aVC?.viewDidLoad()
    print ("callAll 1")
    cbContainer.callAll()
    print ("set to nil")
    aVC = nil
    print ("callAll 2")
    cbContainer.callAll()
    

    给予:

    调用全部 1

    “在 A 上调用回调”

    设置为零

    "Deinited AViewController"

    调用全部 2

    81C3D375-4B0E-4806-8088-AFE81C3125F3 的引用为零,将被删除

    如您所见,“callAll 2”不会调用闭包。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-12
      • 2011-07-26
      • 1970-01-01
      • 2014-06-03
      • 2017-06-10
      • 2014-01-14
      • 1970-01-01
      相关资源
      最近更新 更多