Adicionando stored properties em sua extension
Todo desenvolvedor iOS já se deparou com essa mensagem não muito amigável:
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.
É, 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.