如何防止对象在当前作为委托时被取消初始化

How to prevent an object from being de-initialized when it is currently being a delegate

我创建了一个 "utility class",其唯一目的是成为一个 UITextFieldDelegate

基本上,只有当文本字段中有文本时才应该启用 UIAlertAction,否则该操作将被禁用。

class ActionDisabler: NSObject, UITextFieldDelegate {

    let action: UIAlertAction

    init(action: UIAlertAction, textField: UITextField) {
        self.action = action
        super.init()
        textField.delegate = self

        // Initialize it as enabled or disabled
        if let text = textField.text {
            action.isEnabled = !text.isEmpty
        } else {
            action.isEnabled = false
        }

    }

    deinit {
        print("Too early?")
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        if let text = textField.text {
            action.isEnabled = text.utf8.count - string.utf8.count > 0
        } else {
            action.isEnabled = false
        }

        return true
    }

}

如您所见,它在其构造函数中采用警报操作和文本字段,并使自己成为文本字段的委托。简单吧?

嗯,UITextFielddelegate 是这样定义的:

weak open var delegate: UITextFieldDelegate?

因此,ActionDisabler 在关闭后被取消初始化(添加到 UIAlertController 中定义的文本字段的配置处理程序不再在范围内。

alert.addTextField(configurationHandler: { textField in
    _ = ActionDisabler(action: join, textField: textField)
})

子类化 UIAlertController 并让每个警报控制器成为其自己的文本字段的委托不是一种选择,如 this answer 所解释的那样。

有什么方法可以使 ActionDisabler 在作为委托的文本字段不再存在之前不被取消初始化?

迄今为止最好的解决方案是在保留对 UITextField 的引用的地方保留对 ActionDisabler 的引用,这样两者将同时被释放。

如果这不可能或会使您的代码难看,您可以子类化 UITextField 以持有委托两次。一次强引用:

class UIStrongTextField: UITextField {
    // The delegate will get dealocated when the text field itself gets deallocated
    var strongDelegate: UITextFieldDelegate? {
        didSet {
            self.delegate = strongDelegate
        }
    }
}

现在您设置 textField.strongDelegate = selfActionDisabler 将与文本字段一起释放。

如果您不能持有引用并且不能子类化 UITextField。我们必须要有创意。 ActionDisabler 可以保存对自身的引用,并将该引用设置为 nil,以释放自身。 (又名手动内存管理)

class ActionDisabler: NSObject, UITextFieldDelegate {

    let action: UIAlertAction
    var deallocPreventionAnchor: ActionDisabler?

    init(action: UIAlertAction, textField: UITextField) {
        self.deallocPreventionAnchor = self
        self.action = action

当您不再需要 ActionDisabler 时,您设置 self.deallocPreventionAnchor = nil 它将被释放。如果忘记设置为 nil,就会泄漏内存,因为这确实是利用引用计数器进行手动内存管理。而且我已经可以看到人们在评论中对我尖叫,因为这有点老套:)(这也只有在释放将由委托函数触发时才有意义,否则你将不得不在某处保留引用)