Como ter propriedades armazenadas no Swift, da mesma maneira que eu tinha no Objective-C?

132

Estou mudando um aplicativo do Objective-C para o Swift, que tenho algumas categorias com propriedades armazenadas, por exemplo:

@interface UIView (MyCategory)

- (void)alignToView:(UIView *)view
          alignment:(UIViewRelativeAlignment)alignment;
- (UIView *)clone;

@property (strong) PFObject *xo;
@property (nonatomic) BOOL isAnimating;

@end

Como as extensões Swift não aceitam propriedades armazenadas como essas, não sei como manter a mesma estrutura que o código Objc. As propriedades armazenadas são realmente importantes para o meu aplicativo e acredito que a Apple deve ter criado alguma solução para fazê-lo no Swift.

Como dito por jou, o que eu estava procurando era realmente usar objetos associados, então usei (em outro contexto):

import Foundation
import QuartzCore
import ObjectiveC

extension CALayer {
    var shapeLayer: CAShapeLayer? {
        get {
            return objc_getAssociatedObject(self, "shapeLayer") as? CAShapeLayer
        }
        set(newValue) {
            objc_setAssociatedObject(self, "shapeLayer", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    var initialPath: CGPathRef! {
        get {
            return objc_getAssociatedObject(self, "initialPath") as CGPathRef
        }
        set {
            objc_setAssociatedObject(self, "initialPath", newValue, UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }
}

Mas eu recebo um EXC_BAD_ACCESS ao fazer:

class UIBubble : UIView {
    required init(coder aDecoder: NSCoder) {
        ...
        self.layer.shapeLayer = CAShapeLayer()
        ...
    }
}

Alguma ideia?

Marcos Duarte
fonte
11
As categorias de classe Objective-C também não podem definir variáveis ​​de instância, então como você percebeu essas propriedades?
Martin R
Não sei por que você obtém um acesso ruim, mas seu código não deve funcionar. Você está passando valores diferentes para setAssociateObject e getAssociatedObject. Usar a string "shapeLayer" como chave está errada, é o ponteiro (é realmente o endereço) que é a chave, não o que ela aponta. Duas seqüências idênticas residentes em endereços diferentes são duas chaves diferentes. Revise a resposta de Jou e observe como ele definiu xoAssociationKey como uma variável global, para que ela seja a mesma chave / ponteiro ao definir / obter.
SafeFastExpressive

Respostas:

50

A API de objetos associados é um pouco complicada de usar. Você pode remover a maior parte do clichê com uma classe auxiliar.

public final class ObjectAssociation<T: AnyObject> {

    private let policy: objc_AssociationPolicy

    /// - Parameter policy: An association policy that will be used when linking objects.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {

        self.policy = policy
    }

    /// Accesses associated object.
    /// - Parameter index: An object whose associated object is to be accessed.
    public subscript(index: AnyObject) -> T? {

        get { return objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as! T? }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

Desde que você possa "adicionar" uma propriedade à classe objetivo-c de maneira mais legível:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

        get { return SomeType.association[self] }
        set { SomeType.association[self] = newValue }
    }
}

Quanto à solução:

extension CALayer {

    private static let initialPathAssociation = ObjectAssociation<CGPath>()
    private static let shapeLayerAssociation = ObjectAssociation<CAShapeLayer>()

    var initialPath: CGPath! {
        get { return CALayer.initialPathAssociation[self] }
        set { CALayer.initialPathAssociation[self] = newValue }
    }

    var shapeLayer: CAShapeLayer? {
        get { return CALayer.shapeLayerAssociation[self] }
        set { CALayer.shapeLayerAssociation[self] = newValue }
    }
}
Wojciech Nagrodzki
fonte
Ok, o que fazer se eu quiser armazenar Int, Bool e etc?
Vyachaslav Gerchicov
1
Não é possível armazenar tipos Swift via associação de objetos diretamente. Você pode armazenar, por exemplo, NSNumberou NSValuee escrever pares adicionais de acessadores que seriam do tipo desejado (Int, Bool, etc).
Wojciech Nagrodzki 02/07
1
AVISO Isso não trabalho para estruturas, porque a biblioteca de tempo de execução Objective-C suporta apenas classes que estejam em conformidade comNSObjectProtocol
Charlton Provatas
2
@CharltonProvatas Não é possível usar estruturas com esta API, elas não estão em conformidade com o protocolo AnyObject.
Wojciech Nagrodzki
@VyachaslavGerchicov [String]?é uma estrutura, é o mesmo caso do Int, Bool. Você pode usar NSArraypara manter uma coleção de NSStringinstâncias.
Wojciech Nagrodzki 14/02/19
184

Como no Objective-C, você não pode adicionar propriedades armazenadas a classes existentes. Se você estiver estendendo uma classe Objective-C ( UIViewé definitivamente uma), ainda poderá usar os Objetos Associados para emular as propriedades armazenadas:

para Swift 1

import ObjectiveC

private var xoAssociationKey: UInt8 = 0

extension UIView {
    var xo: PFObject! {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject
        }
        set(newValue) {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy(OBJC_ASSOCIATION_RETAIN))
        }
    }
}

A chave da associação é um ponteiro que deve ser exclusivo para cada associação. Para isso, criamos uma variável global privada e usamos seu endereço de memória como a chave do &operador. Consulte Usando o Swift com cacau e Objective-C para obter mais detalhes sobre como os ponteiros são manipulados no Swift.

ATUALIZADO para Swift 2 e 3

import ObjectiveC

private var xoAssociationKey: UInt8 = 0

extension UIView {
    var xo: PFObject! {
        get {
            return objc_getAssociatedObject(self, &xoAssociationKey) as? PFObject
        }
        set(newValue) {
            objc_setAssociatedObject(self, &xoAssociationKey, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN)
        }
    }
}

ATUALIZADO para Swift 4

No Swift 4, é muito mais simples. A estrutura Holder conterá o valor privado que nossa propriedade computada expõe ao mundo, dando a ilusão de um comportamento de propriedade armazenada.

Fonte

extension UIViewController {
    struct Holder {
        static var _myComputedProperty:Bool = false
    }
    var myComputedProperty:Bool {
        get {
            return Holder._myComputedProperty
        }
        set(newValue) {
            Holder._myComputedProperty = newValue
        }
    }
}
jou
fonte
2
@ Yar Não sabia objc_AssociationPolicy, obrigado! Atualizei a resposta
jun
2
No Swift2 você tem que usar objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom
1
@SimplGy, adicione sua edição como uma resposta adicional, em vez de editar seu código na resposta de outra pessoa.
JAL
11
A solução Swift4 não funciona se você deseja ter mais de uma instância do UIViewController que usa a propriedade da extensão. Por favor, verifique as atualizações na fonte.
IUR
9
O exemplo do Swift 4 não funciona com várias instâncias e provavelmente não deveria estar aqui.
Colin Cornaby 11/03/19
36

Então, acho que encontrei um método que funciona mais limpo que os acima, porque não requer nenhuma variável global. Eu peguei aqui: http://nshipster.com/swift-objc-runtime/

A essência é que você usa uma estrutura assim:

extension UIViewController {
    private struct AssociatedKeys {
        static var DescriptiveName = "nsh_DescriptiveName"
    }

    var descriptiveName: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.DescriptiveName) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.DescriptiveName,
                    newValue as NSString?,
                    UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                )
            }
        }
    }
}

ATUALIZAÇÃO para Swift 2

private struct AssociatedKeys {
    static var displayed = "displayed"
}

//this lets us check to see if the item is supposed to be displayed or not
var displayed : Bool {
    get {
        guard let number = objc_getAssociatedObject(self, &AssociatedKeys.displayed) as? NSNumber else {
            return true
        }
        return number.boolValue
    }

    set(value) {
        objc_setAssociatedObject(self,&AssociatedKeys.displayed,NSNumber(bool: value),objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
    }
}
AlexK
fonte
@AKWeblS Eu implementei um código como o seu, mas ao atualizar para o swift 2.0, estou recebendo o erro não é possível invocar o inicializador para o tipo 'UInt' com uma lista de argumentos do tipo 'objc_AssociationPolicy)'. Código no próximo comentário
user2363025
Como atualizar para o swift 2.0? var postDescription: String? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) como? String} set {if let newValue = newValue {objc_setAssociatedObject (self, & AssociatedKeys.postDescription, newValue como NSString ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)))}}
user2363025:
1
não funciona: meios var estáticos que o valor da propriedade é o mesmo para todas as instâncias
Fry
@ fritar - continua a trabalhar para mim. Sim, a chave é estática, mas está associada a um objeto específico, o objeto em si não é global.
AlexKr #
15

A solução apontada por jou não suporta tipos de valor , isso também funciona bem com eles

Wrappers

import ObjectiveC

final class Lifted<T> {
    let value: T
    init(_ x: T) {
        value = x
    }
}

private func lift<T>(x: T) -> Lifted<T>  {
    return Lifted(x)
}

func setAssociatedObject<T>(object: AnyObject, value: T, associativeKey: UnsafePointer<Void>, policy: objc_AssociationPolicy) {
    if let v: AnyObject = value as? AnyObject {
        objc_setAssociatedObject(object, associativeKey, v,  policy)
    }
    else {
        objc_setAssociatedObject(object, associativeKey, lift(value),  policy)
    }
}

func getAssociatedObject<T>(object: AnyObject, associativeKey: UnsafePointer<Void>) -> T? {
    if let v = objc_getAssociatedObject(object, associativeKey) as? T {
        return v
    }
    else if let v = objc_getAssociatedObject(object, associativeKey) as? Lifted<T> {
        return v.value
    }
    else {
        return nil
    }
}

Uma possível extensão de classe (exemplo de uso):

extension UIView {

    private struct AssociatedKey {
        static var viewExtension = "viewExtension"
    }

    var referenceTransform: CGAffineTransform? {
        get {
            return getAssociatedObject(self, associativeKey: &AssociatedKey.viewExtension)
        }

        set {
            if let value = newValue {
                setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.viewExtension, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

Esta é realmente uma ótima solução, eu queria adicionar outro exemplo de uso que incluísse estruturas e valores que não são opcionais. Além disso, os valores AssociatedKey podem ser simplificados.

struct Crate {
    var name: String
}

class Box {
    var name: String

    init(name: String) {
        self.name = name
    }
}

extension UIViewController {

    private struct AssociatedKey {
        static var displayed:   UInt8 = 0
        static var box:         UInt8 = 0
        static var crate:       UInt8 = 0
    }

    var displayed: Bool? {
        get {
            return getAssociatedObject(self, associativeKey: &AssociatedKey.displayed)
        }

        set {
            if let value = newValue {
                setAssociatedObject(self, value: value, associativeKey: &AssociatedKey.displayed, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }

    var box: Box {
        get {
            if let result:Box = getAssociatedObject(self, associativeKey: &AssociatedKey.box) {
                return result
            } else {
                let result = Box(name: "")
                self.box = result
                return result
            }
        }

        set {
            setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.box, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }

    var crate: Crate {
        get {
            if let result:Crate = getAssociatedObject(self, associativeKey: &AssociatedKey.crate) {
                return result
            } else {
                let result = Crate(name: "")
                self.crate = result
                return result
            }
        }

        set {
            setAssociatedObject(self, value: newValue, associativeKey: &AssociatedKey.crate, policy: objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}
HepaKKes
fonte
E como uso a classe Lifted?
3lvis 23/09/15
Por que você precisa daquilo? Pelo que?
HepaKKes
Eu quis dizer como você usa isso em geral, obrigado por sua ajuda :) Você pode adicionar um exemplo "Como usar", por favor?
3lvis 29/09/15
1
O exemplo "Como usar" começaria logo abaixo do cabeçalho "Uma possível extensão de classe". Não acho que os usuários precisem saber como usar o liftmétodo e a Liftedclasse, mas devem usar as funções getAssociatedObject& setAssociatedObject. Vou adicionar "Exemplo de uso" entre parênteses ao lado do cabeçalho por uma questão de clareza.
HepaKKes
1
Esta é certamente a melhor solução, pena que não é explicada muito bem. Funciona com classes, estruturas e outros tipos de valor. As propriedades também não precisam ser opcionais. Bom trabalho.
picciano 6/02/16
13

Você não pode definir categorias (extensões Swift) com novo armazenamento; quaisquer propriedades adicionais devem ser calculadas em vez de armazenadas. A sintaxe funciona para o objetivo C, porque @propertyem uma categoria significa essencialmente "fornecerei o getter e o setter". No Swift, você precisará defini-las para obter uma propriedade computada; algo como:

extension String {
    public var Foo : String {
        get
        {
            return "Foo"
        }

        set
        {
            // What do you want to do here?
        }
    }
}

Deve funcionar bem. Lembre-se, você não pode armazenar novos valores no setter, apenas trabalhar com o estado de classe disponível existente.

Adam Wright
fonte
7

Meus US $ 0,02. Este código está escrito no Swift 2.0

extension CALayer {
    private struct AssociatedKeys {
        static var shapeLayer:CAShapeLayer?
    }

    var shapeLayer: CAShapeLayer? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.shapeLayer) as? CAShapeLayer
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(self, &AssociatedKeys.shapeLayer, newValue as CAShapeLayer?, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
}

Eu tentei muitas soluções e descobri que essa é a única maneira de realmente estender uma classe com parâmetros variáveis ​​adicionais.

Amro Shafie
fonte
Parece interessante, mas se recusa não funciona com protocolos, type 'AssociatedKeys' cannot be defined within a protocol extension...
Ian Bytchek
6

Por que confiar no objc runtime? Eu não entendi o ponto. Usando algo como o seguinte, você obterá quase o comportamento idêntico de uma propriedade armazenada, usando apenas uma abordagem Swift pura :

extension UIViewController {
    private static var _myComputedProperty = [String:Bool]()

    var myComputedProperty:Bool {
        get {
            let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
            return UIViewController._myComputedProperty[tmpAddress] ?? false
        }
        set(newValue) {
            let tmpAddress = String(format: "%p", unsafeBitCast(self, to: Int.self))
            UIViewController._myComputedProperty[tmpAddress] = newValue
        }
    }
}
valvoline
fonte
1
Inteligente! Uma possível desvantagem dessa abordagem é o gerenciamento de memória. Depois que o objeto host é desalocado, sua propriedade ainda existirá no dicionário, o que pode ser potencialmente caro no uso da memória, se for utilizado com muitos objetos.
Charlton Provatas
Provavelmente, podemos manter uma variável estática da lista de wrappers que contêm uma referência fraca aos objetos (self). E remova a entrada do dicionário _myComputedProperty, bem como a variável estática da lista de wrappers no método didSet do Wrapper (quando o objeto é realocado, o didSet dentro da nossa classe Wrapper é chamado, pois nossa referência fraca define um novo valor com zero).
Arjuna
5

Prefiro fazer código no Swift puro e não confiar na herança do Objective-C. Por isso, escrevi a solução Swift pura com duas vantagens e duas desvantagens.

Vantagens:

  1. Código Swift puro

  2. Funciona em classes e conclusões ou mais especificamente em Anyobjetos

Desvantagens:

  1. O código deve chamar o método willDeinit()para liberar objetos vinculados a instância de classe específica para evitar vazamentos de memória

  2. Você não pode fazer a extensão diretamente no UIView para este exemplo exato, porque var frameé uma extensão do UIView, não faz parte da classe.

EDITAR:

import UIKit

var extensionPropertyStorage: [NSObject: [String: Any]] = [:]

var didSetFrame_ = "didSetFrame"

extension UILabel {

    override public var frame: CGRect {

        get {
            return didSetFrame ?? CGRectNull
        }

        set {
            didSetFrame = newValue
        }
    }

    var didSetFrame: CGRect? {

        get {
            return extensionPropertyStorage[self]?[didSetFrame_] as? CGRect
        }

        set {
            var selfDictionary = extensionPropertyStorage[self] ?? [String: Any]()

            selfDictionary[didSetFrame_] = newValue

            extensionPropertyStorage[self] = selfDictionary
        }
    }

    func willDeinit() {
        extensionPropertyStorage[self] = nil
    }
}
vedrano
fonte
4
Isso não funciona, pois o extensionPropertyStorage é compartilhado entre todas as instâncias. Se você definir um valor para uma instância, estará configurando o valor para todas as instâncias.
picciano 6/02/16
Guerra não é boa porque mexer com funções. Agora está melhor e funciona como pretendido.
vedrano
O @picciano extensionPropertyStorage é compartilhado com todas as instâncias por design. É a variável global (Dictionary) que primeiro desassocia a instância UILabel ( NSObject) e depois sua propriedade ( [String: Any]).
vedrano
@picciano Eu acho que isso funciona para classpropriedades;)
Nicolas Miari
frameé uma propriedade de instância. De onde vem a propriedade da classe?
vedrano
3

Com as categorias Obj-c, você pode adicionar apenas métodos, não variáveis ​​de instância.

No exemplo, você usou @property como um atalho para adicionar declarações de método getter e setter. Você ainda precisa implementar esses métodos.

Da mesma forma, no Swift, você pode adicionar extensões de uso para adicionar métodos de instância, propriedades calculadas etc., mas não propriedades armazenadas.

Mike Pollard
fonte
2

Também recebo um problema EXC_BAD_ACCESS.O valor em objc_getAssociatedObject()e objc_setAssociatedObject()deve ser um objeto. E o objc_AssociationPolicydeve corresponder ao objeto.

RuiKQ
fonte
3
Isso realmente não responde à pergunta. Se você tiver uma pergunta diferente, faça-a clicando em Fazer pergunta . Você também pode adicionar uma recompensa para chamar mais atenção para essa pergunta quando tiver reputação suficiente . - Do comentário
AtheistP3ace 15/02
@ AtheistP3ace Sinto muito por isso. Tento adicionar um comentário à pergunta, mas não tenho reputação suficiente. Então, eu tento responder à pergunta.
RuiKQ
2

Tentei usar objc_setAssociatedObject conforme mencionado em algumas das respostas aqui, mas depois de falhar algumas vezes, dei um passo atrás e percebi que não havia razão para isso. Tomando emprestado algumas das idéias aqui, criei esse código que simplesmente armazena uma matriz com quaisquer dados extras (MyClass neste exemplo) indexados pelo objeto ao qual quero associá-lo:

class MyClass {
    var a = 1
    init(a: Int)
    {
        self.a = a
    }
}

extension UIView
{
    static var extraData = [UIView: MyClass]()

    var myClassData: MyClass? {
        get {
            return UIView.extraData[self]
        }
        set(value) {
            UIView.extraData[self] = value
        }
    }
}

// Test Code: (Ran in a Swift Playground)
var view1 = UIView()
var view2 = UIView()

view1.myClassData = MyClass(a: 1)
view2.myClassData = MyClass(a: 2)
print(view1.myClassData?.a)
print(view2.myClassData?.a)
Dan
fonte
você tem alguma idéia de como limpar automaticamente extraData para que não cause vazamentos de memória?
DoubleK 30/09/16
Na verdade, eu não estava usando isso com visualizações, no meu caso, apenas instanciamos um número finito de objetos na classe que estava usando, por isso realmente não considerei vazamentos de memória. Não consigo pensar em uma maneira automática de fazer isso, mas suponho que no setter acima você possa adicionar lógica para remover o elemento se value == nil e, em seguida, defina o valor como nil quando não precisar mais do objeto, ou talvez em didReceiveMemoryWarning ou algo assim.
Dan
Tnx @ Dan, usei o viewWillDisapper para definir o valor como nulo e funciona bem, agora o deinit é chamado e tudo funciona bem. Tnx por postar esta solução
DoubleK 02/10
2

Aqui está a solução simplificada e mais expressiva. Funciona para os tipos de valor e referência. A abordagem do levantamento é retirada da resposta @HepaKKes.

Código da associação:

import ObjectiveC

final class Lifted<T> {
    let value: T
    init(_ x: T) {
        value = x
    }
}

private func lift<T>(_ x: T) -> Lifted<T>  {
    return Lifted(x)
}

func associated<T>(to base: AnyObject,
                key: UnsafePointer<UInt8>,
                policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN,
                initialiser: () -> T) -> T {
    if let v = objc_getAssociatedObject(base, key) as? T {
        return v
    }

    if let v = objc_getAssociatedObject(base, key) as? Lifted<T> {
        return v.value
    }

    let lifted = Lifted(initialiser())
    objc_setAssociatedObject(base, key, lifted, policy)
    return lifted.value
}

func associate<T>(to base: AnyObject, key: UnsafePointer<UInt8>, value: T, policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN) {
    if let v: AnyObject = value as AnyObject? {
        objc_setAssociatedObject(base, key, v, policy)
    }
    else {
        objc_setAssociatedObject(base, key, lift(value), policy)
    }
}

Exemplo de uso:

1) Crie uma extensão e associe propriedades a ela. Vamos usar as propriedades do tipo valor e referência.

extension UIButton {

    struct Keys {
        static fileprivate var color: UInt8 = 0
        static fileprivate var index: UInt8 = 0
    }

    var color: UIColor {
        get {
            return associated(to: self, key: &Keys.color) { .green }
        }
        set {
            associate(to: self, key: &Keys.color, value: newValue)
        }
    }

    var index: Int {
        get {
            return associated(to: self, key: &Keys.index) { -1 }
        }
        set {
            associate(to: self, key: &Keys.index, value: newValue)
        }
    }

}

2) Agora você pode usar apenas as propriedades regulares:

    let button = UIButton()
    print(button.color) // UIExtendedSRGBColorSpace 0 1 0 1 == green
    button.color = .black
    print(button.color) // UIExtendedGrayColorSpace 0 1 == black

    print(button.index) // -1
    button.index = 3
    print(button.index) // 3

Mais detalhes:

  1. A elevação é necessária para agrupar os tipos de valor.
  2. O comportamento padrão do objeto associado é retido. Se você quiser saber mais sobre objetos associados, recomendo verificar este artigo .
Vadim Bulavin
fonte
1

Outro exemplo com o uso de objetos associados ao Objective-C e propriedades calculadas para Swift 3 e Swift 4

import CoreLocation

extension CLLocation {

    private struct AssociatedKeys {
        static var originAddress = "originAddress"
        static var destinationAddress = "destinationAddress"
    }

    var originAddress: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.originAddress) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.originAddress,
                    newValue as NSString?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }

    var destinationAddress: String? {
        get {
            return objc_getAssociatedObject(self, &AssociatedKeys.destinationAddress) as? String
        }
        set {
            if let newValue = newValue {
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.destinationAddress,
                    newValue as NSString?,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )
            }
        }
    }

}
abdullahselek
fonte
1

se você deseja definir um atributo de string personalizado para um UIView, foi assim que eu o fiz no Swift 4

Crie uma extensão UIView

extension UIView {

    func setStringValue(value: String, key: String) {
        layer.setValue(value, forKey: key)
    }

    func stringValueFor(key: String) -> String? {
        return layer.value(forKey: key) as? String
    }
}

Para usar esta extensão

let key = "COLOR"

let redView = UIView() 

// To set
redView.setStringAttribute(value: "Red", key: key)

// To read
print(redView.stringValueFor(key: key)) // Optional("Red")
Kelvin Fok
fonte
1

Que tal armazenar o mapa estático na classe que está se estendendo assim:

extension UIView {

    struct Holder {
        static var _padding:[UIView:UIEdgeInsets] = [:]
    }

    var padding : UIEdgeInsets {
        get{ return UIView.Holder._padding[self] ?? .zero}
        set { UIView.Holder._padding[self] = newValue }
    }

}
Ashkan Ghodrat
fonte
1
por que essa resposta não tem votos positivos. Afinal, solução inteligente.
byJeevan
Isso funciona, mas acho que essa abordagem é falha conforme indicado aqui medium.com/@valv0/…
Teffi 01/07
0

Tentei armazenar propriedades usando objc_getAssociatedObject, objc_setAssociatedObject, sem sorte. Meu objetivo era criar uma extensão para o UITextField, para validar o comprimento dos caracteres de entrada de texto. O código a seguir funciona bem para mim. Espero que isso ajude alguém.

private var _min: Int?
private var _max: Int?

extension UITextField {    
    @IBInspectable var minLength: Int {
        get {
            return _min ?? 0
        }
        set {
            _min = newValue
        }
    }

    @IBInspectable var maxLength: Int {
        get {
            return _max ?? 1000
        }
        set {
            _max = newValue
        }
    }

    func validation() -> (valid: Bool, error: String) {
        var valid: Bool = true
        var error: String = ""
        guard let text = self.text else { return (true, "") }

        if text.characters.count < minLength {
            valid = false
            error = "Textfield should contain at least \(minLength) characters"
        }

        if text.characters.count > maxLength {
            valid = false
            error = "Textfield should not contain more then \(maxLength) characters"
        }

        if (text.characters.count < minLength) && (text.characters.count > maxLength) {
            valid = false
            error = "Textfield should contain at least \(minLength) characters\n"
            error = "Textfield should not contain more then \(maxLength) characters"
        }

        return (valid, error)
    }
}
Vadims Krutovs
fonte
Variáveis ​​privadas globais? Você percebe isso _mine _maxé global e será o mesmo em todas as instâncias do UITextField? Mesmo que funcione para você, essa resposta não tem relação porque Marcos pergunta sobre variáveis ​​de instância .
Kelin
Sim, você está certo, isso não funciona para todas as instâncias. Corrigido armazenando o valor mínimo e máximo em struct. Desculpe pelo tópico fora.
Vadims Krutovs /
0

Aqui está uma alternativa que também funciona

public final class Storage : AnyObject {

    var object:Any?

    public init(_ object:Any) {
        self.object = object
    }
}

extension Date {

    private static let associationMap = NSMapTable<NSString, AnyObject>()
    private struct Keys {
        static var Locale:NSString = "locale"
    }

    public var locale:Locale? {
        get {

            if let storage = Date.associationMap.object(forKey: Keys.Locale) {
                return (storage as! Storage).object as? Locale
            }
            return nil
        }
        set {
            if newValue != nil {
                Date.associationMap.setObject(Storage(newValue), forKey: Keys.Locale)
            }
        }
    }
}



var date = Date()
date.locale = Locale(identifier: "pt_BR")
print( date.locale )
Rondinelli Morais
fonte
-1

Achei esta solução mais prática

ATUALIZADO para Swift 3

extension UIColor {

    static let graySpace = UIColor.init(red: 50/255, green: 50/255, blue: 50/255, alpha: 1.0)
    static let redBlood = UIColor.init(red: 102/255, green: 0/255, blue: 0/255, alpha: 1.0)
    static let redOrange = UIColor.init(red: 204/255, green: 17/255, blue: 0/255, alpha: 1.0)

    func alpha(value : CGFloat) -> UIColor {
        var r = CGFloat(0), g = CGFloat(0), b = CGFloat(0), a = CGFloat(0)
        self.getRed(&r, green: &g, blue: &b, alpha: &a)
        return UIColor(red: r, green: g, blue: b, alpha: value)
    }

}

... então no seu código

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

    override func viewDidLoad() {
        self.view.backgroundColor = UIColor.graySpace

    }
}
Giuseppe Mazzilli
fonte
Esta é a maneira correta de fazer isso - mas não é realmente uma propriedade armazenada na extensão, como a pergunta.
UKDataGeek