0

I have created a "utility class" whose sole purpose is to be a UITextFieldDelegate.

Basically, it is supposed to enable a UIAlertAction only when there is text in a textfield, otherwise the action is disabled.

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
    }

}

As you can see, it takes an alert action and a textfield in its constructor and makes itself the delegate of the text field. Simple, right?

Well, this is how delegate of UITextField is defined:

weak open var delegate: UITextFieldDelegate?

Due to this, ActionDisabler is de-initialized after the closure (a configuration handler for a textfield being added to a UIAlertController it is defined in is no longer in scope.

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

Subclassing UIAlertController and having each alert controller be the delegate of its own textfield is not an option, as explained by this answer.

Is there any way to make ActionDisabler not be de-initialized until the textfield that it is a delegate of is no longer alive?

Community
  • 1
  • 1
ahyattdev
  • 529
  • 1
  • 6
  • 18

1 Answers1

2

The by far best solution is to keep a reference to your ActionDisabler where the reference to your UITextField is kept, so both will get deallocated at the same time.

If that is not possible or would make your code ugly, you could subclass UITextField to hold the delegate twice. Once with a strong reference:

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

Now you set textField.strongDelegate = self and ActionDisabler will get deallocated together with the text field.

If you can't hold a reference and you can't subclass UITextField. We have to get creative. The ActionDisabler can hold a reference to itself, and set that reference to nil, to release itself. (aka manual memory management)

class ActionDisabler: NSObject, UITextFieldDelegate {

    let action: UIAlertAction
    var deallocPreventionAnchor: ActionDisabler?

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

When you don't need the ActionDisabler anymore you set self.deallocPreventionAnchor = nil and it will get deallocated. If you forget to set it to nil, you will leak the memory, because this is indeed manual memory management by exploiting the reference counter. And I can already see people screaming in the comments at me because that is kinda hacky :) (This also only makes sense when the deallocation will be triggered by a delegate function, otherwise you would have to keep a reference somewhere anyway)

LimeRed
  • 1,278
  • 13
  • 18
  • Thanks for your useful answer with many options. For my situation, it makes the most sense to subclass `UITextField` in order to have a strong reference. – ahyattdev Mar 07 '17 at 02:40