Adicionando stored properties em sua extension

Todo desenvolvedor iOS já se deparou com essa mensagem não muito amigável:

#praCegoVer: a imagem mostra uma mensagem de erro dizendo que extensões não devem conter stored properties.

Pois é. Extensions não podem conter stored properties. Se você precisa adicionar uma propriedade em um componente nativo, você deverá criar seu próprio botão herdando de UIButton.

Particularmente falando, criar seu próprio botão utilizando uma herança de UIButton é a maneira mais correta de resolver essa situação. Mas como sempre existirão aqueles casos exepcionais, poder colocar uma propriedade em uma extension seria uma mão na roda, se isso fosse possível.

Mas será que realmente não é?

Existe uma forma de contornar essa situação: o Objective-C Runtime.

Associated Objects

Associated Objects (objetos associados) usam aquele velho conceito que nós aprendemos nas aulas de Programação Orientada a Objetos. Um objeto que tem um outro objeto dentro dele.

Com o Objective-C Runtime, você pode criar objetos associados em Runtime.

Para isso vamos usar duas funções disponíveis pelo Objective-C Runtime: objc_getAssociatedObject e objc_setAssociatedObject.

Suas definições, segundo a documentação da Apple:

objc_getAssociatedObject: Retorna o valor associado com um dado objeto para uma chave informada.

objc_setAssociatedObject: Seta um valor associado para um dado objeto usando uma chave informada e uma política de associação.

Epa, espera um pouco: política de associação? O que diabos é isso?

Política de associação

Política de associação é a forma como a associação do seu objeto vai ser feita. Lembra aquela coisa de strong, weak, copy, atomic, nonatomic? Pois é, é isso.

Existem 5 tipos de associação possíveis:

OBJC_ASSOCIATION_ASSIGN: Referência fraca (weak)
OBJC_ASSOCIATION_COPY: Cópia do objeto (deve implementar o protocolo NSCopying) com associação atômica.
OBJC_ASSOCIATION_COPY_NONATOMIC: Cópia do objeto (deve implementar o protocolo NSCopying) com associação não atômica.
OBJC_ASSOCIATION_RETAIN: Referência forte (strong) com associação atômica.
OBJC_ASSOCIATION_RETAIN_NONATOMIC: Referência forte (strong) com associação não atômica.

Vamos ver abaixo um exemplo de tudo isso que falamos até agora.

Implementando um action handler em um UIButton

Primeiro, vamos criar uma extension de UIButton e colocar nosso action handler.

#praCegoVer: a imagem mostra um trecho de código, onde há uma extension de UIButton e uma tentativa de criar um action handler, como uma mensagem de erro nessa linha, informando que extensões não devem conter stored properties.

É, não deu muito certo…

Vamos então usar o objc_getAssociatedObject e o objc_setAssociatedObject para fazer nossa stored property se transformar em uma computed property, permitida em extensions.

Então é só colocar os código no get e set dessa nossa propriedade, conforme o exemplo abaixo:

fileprivate struct UIButtonAssociatedKeys {
    static var action: UInt8 = 0
}

extension UIButton {

    var actionHandler: ((UIControl) -> Void)? {
        get {
            return objc_getAssociatedObject(self, &UIButtonAssociatedKeys.action) as? ((UIControl) -> Void)
        }
        set {
            objc_setAssociatedObject(self, &UIButtonAssociatedKeys.action, newValue, .OBJC_ASSOCIATION_COPY)
        }
    }

}

No nosso get, iremos buscar dentro de self (nosso UIButton), por uma propriedade associada definida pela referência da chave UIButtonAssociatedKeys.action, e retorná-la como nosso bloco.

No nosso set, iremos guardar em self (nosso UIButton) um objeto associado (newValue) para a referência da chave UIButtonAssociatedKeys.action, guardando uma cópia desse objeto.

Assim, iremos guardar e resgatar nosso action handler dentro de qualquer UIButton.

Fazendo o action hander responder ao tap do botão

Da mesma forma que usaríamos um addTarget para implementar o tap do botão via código, vamos criar uma função tap, para quardar nosso action handler e já setá-lo para o TouchUpInside.

Dessa forma, nosso código final ficaria assim:

import Foundation
import UIKit

fileprivate struct UIButtonAssociatedKeys {
    static var action: UInt8 = 0
}

extension UIButton {

    private var actionHandler: ((UIControl) -> Void)? {
        get {
            return objc_getAssociatedObject(self, &UIButtonAssociatedKeys.action) as? ((UIControl) -> Void)
        }
        set {
            objc_setAssociatedObject(self, &UIButtonAssociatedKeys.action, newValue, .OBJC_ASSOCIATION_COPY)
        }
    }

    @objc private func executeAction(_ sender: UIControl) {
        self.actionHandler?(sender)
    }

    func tap(_ action: @escaping (UIControl) -> Void) {
        self.actionHandler = action
        self.addTarget(self, action: #selector(executeAction(_:)), for: .touchUpInside)
    }
}

That’s all folks

Galera, espero que vocês tenham curtido essa dica, e em breve teremos mais dicas, mais tutoriais e muito mais conteúdo pra galera iOS.