Объекты, связанные со средой выполнения Objc

Иногда возникает ситуация, когда вам нужно расширить существующий класс новыми свойствами, но, как вы уже знаете, расширения в swift не могут содержать сохраненные свойства. Вы спросите: как найти выход из этой ситуации? Ответ — объекты, связанные со средой выполнения objc.

Что это?

Это механика выполнения objc, которая позволяет нам связать какой-либо объект в памяти с другим объектом, используя специальный ключ и политику ассоциации.

Как это использовать?

Вот пример того, как связать объекты:

objc_setAssociatedObject(
    someObject,
    "associated_object_key",
    anotherObject,
    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
)

Вы можете получить ассоциированный объект для дальнейшей реализации

objc_getAssociatedObject(
    someObject,
    "associated_object_key"
)

Как это использовать на практике?

Предположим, вам нужно расширить класс UITextFiled свойством hintLabel, чтобы иметь возможность использовать его для отображения сообщений проверки. Очевидным решением будет создание подкласса UITextField, содержащего требуемое свойство. Затем долго и мучительно обновляйте все свои раскадровки новым компонентом пользовательского интерфейса. И если вы хотите, чтобы это работало в другом проекте, вам нужно будет скопировать в него подкласс и сохранить все зависимости, если это было что-то более сложное, чем простой текстовый подкласс. Звучит знакомо?

На мой взгляд, лучшим решением будет создание расширения, которое будет содержать всю необходимую логику, вот когда появляется объект, связанный с objc.

extension UITextField {
    private struct AssociatedKey {
        static var hintLabel = "UITextField.HintLabel"
    }
    @IBOutlet public weak var hintLabel: UILabel? {
        get {
            return objc_getAssociatedObject(
                self,
                &AssociatedKey.hintLabel
            ) as? UILabel
        }
        set(hintLabel) {
            objc_setAssociatedObject(
                self,
                &AssociatedKey.hintLabel,
                hintLabel,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }
}

Как мы уже упоминали, расширения не могут содержать сохраненные свойства, но мы можем использовать сеттеры и геттеры со связанными объектами для имитации поведения сохраненных свойств.