¿Cómo tener propiedades almacenadas en Swift, de la misma manera que tenía en Objective-C?

132

Estoy cambiando una aplicación de Objective-C a Swift, que tengo un par de categorías con propiedades almacenadas, por ejemplo:

@interface UIView (MyCategory)

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

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

@end

Como las extensiones Swift no aceptan propiedades almacenadas como estas, no sé cómo mantener la misma estructura que el código Objc. Las propiedades almacenadas son realmente importantes para mi aplicación y creo que Apple debe haber creado alguna solución para hacerlo en Swift.

Como dijo jou, lo que estaba buscando era usar objetos asociados, así que lo hice (en otro 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))
        }
    }
}

Pero obtengo un EXC_BAD_ACCESS cuando hago:

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

¿Algunas ideas?

Marcos Duarte
fuente
11
Las categorías de clase Objective-C tampoco pueden definir variables de instancia, entonces, ¿cómo se dieron cuenta de esas propiedades?
Martin R
No estoy seguro de por qué tiene un mal acceso, pero su código no debería funcionar. Estás pasando diferentes valores a setAssociateObject y getAssociatedObject. Usar la cadena "shapeLayer" como clave está mal, es el puntero (en realidad, la dirección) la clave, no lo que apunta. Dos cadenas idénticas que residen en diferentes direcciones son dos claves diferentes. Revise la respuesta de Jou y observe cómo definió xoAssociationKey como una variable global, por lo que es la misma tecla / puntero al configurar / obtener.
SafeFastExpressive

Respuestas:

50

La API de objetos asociados es un poco engorrosa de usar. Puede eliminar la mayor parte del repetitivo con una clase 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) }
    }
}

Siempre que pueda "agregar" una propiedad a la clase objetiva-c de una manera más legible:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

En cuanto a la solución:

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
fuente
Ok, ¿qué hacer si quiero almacenar Int, Bool y etc.?
Vyachaslav Gerchicov
1
No es posible almacenar tipos Swift a través de la asociación de objetos directamente. Podría almacenar, por ejemplo, NSNumberoy NSValueescribir pares adicionales de accesos que serían del tipo que desea (Int, Bool, etc.).
Wojciech Nagrodzki
1
ADVERTENCIA Esto no funciona para estructuras, porque la biblioteca de tiempo de ejecución de Objective-C solo admite clases que se ajustan aNSObjectProtocol
Charlton Provatas
2
@CharltonProvatas No es posible utilizar estructuras con esta API, no se ajustan al protocolo AnyObject.
Wojciech Nagrodzki
@VyachaslavGerchicov [String]?es una estructura, este es el mismo caso que para Int, Bool. Puede usar NSArraypara mantener una colección de NSStringinstancias.
Wojciech Nagrodzki
184

Como en Objective-C, no puede agregar propiedades almacenadas a clases existentes. Si está extendiendo una clase Objective-C ( UIViewdefinitivamente es una), aún puede usar objetos asociados para emular propiedades almacenadas:

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))
        }
    }
}

La clave de asociación es un puntero que debe ser único para cada asociación. Para eso, creamos una variable global privada y utilizamos su dirección de memoria como clave con el &operador. Consulte Uso de Swift con Cocoa y Objective-C para obtener más detalles sobre cómo se manejan los punteros en Swift.

ACTUALIZADO para Swift 2 y 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)
        }
    }
}

ACTUALIZADO para Swift 4

En Swift 4, es mucho más simple. La estructura Holder contendrá el valor privado que nuestra propiedad computada expondrá al mundo, dando la ilusión de un comportamiento de propiedad almacenada.

Fuente

extension UIViewController {
    struct Holder {
        static var _myComputedProperty:Bool = false
    }
    var myComputedProperty:Bool {
        get {
            return Holder._myComputedProperty
        }
        set(newValue) {
            Holder._myComputedProperty = newValue
        }
    }
}
jou
fuente
2
@Yar No sabía objc_AssociationPolicy, gracias! He actualizado la respuesta
Jou
2
En Swift2 tienes que usar objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom
1
@SimplGy agregue su edición como una respuesta adicional en lugar de editar su código en la respuesta de otra persona.
JAL
11
La solución Swift4 no funciona si desea tener más de 1 instancia de UIViewController que use la propiedad de la extensión. Por favor, consulte las actualizaciones en la fuente.
IUR
9
El ejemplo de Swift 4 no funciona con varias instancias, y probablemente no debería estar aquí.
Colin Cornaby
36

Así que creo que encontré un método que funciona más limpio que los anteriores porque no requiere ninguna variable global. Lo obtuve desde aquí: http://nshipster.com/swift-objc-runtime/

La esencia es que usa una estructura como esta:

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)
                )
            }
        }
    }
}

ACTUALIZACIÓN 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
fuente
@AKWeblS He implementado un código como el tuyo, pero cuando actualizo a swift 2.0, obtengo el error no puedo invocar el inicializador para el tipo 'UInt' con una lista de argumentos del tipo 'objc_AssociationPolicy)'. Código en el siguiente comentario
user2363025
¿Cómo actualizar para swift 2.0? var postDescription: ¿Cadena? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) como? String} {if conjunto deje nuevoValor = {nuevoValor objc_setAssociatedObject (auto, y AssociatedKeys.postDescription, nuevoValor como NSString ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025
1
no funciona: var estática significa que el valor de la propiedad es el mismo para todas las instancias
Fry
@fry: sigue funcionando para mí. Sí, la clave es estática, pero está asociada a un objeto específico, el objeto en sí no es global.
AlexK
15

La solución señalada por jou no admite tipos de valor , esto también funciona bien con ellos

Envolturas

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
    }
}

Una posible extensión de clase (ejemplo 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 es realmente una gran solución, quería agregar otro ejemplo de uso que incluye estructuras y valores que no son opcionales. Además, los valores de AssociatedKey se pueden simplificar.

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
fuente
¿Y cómo uso la clase Lifted?
3lvis
¿Por qué necesitas eso? ¿Para qué?
HepaKKes
Me refería a cómo usas esto en general, gracias por tu ayuda :) ¿Puedes agregar un ejemplo de "Cómo usar", por favor?
3lvis
1
El ejemplo "Cómo usar" comenzaría justo debajo del encabezado "Una posible extensión de clase". No creo que los usuarios necesiten saber cómo usar el liftmétodo y la Liftedclase, deberían usar sin embargo getAssociatedObject& setAssociatedObjectfunciones. Agregaré "Ejemplo de uso" entre paréntesis junto al encabezado en aras de la claridad.
HepaKKes
1
Esta es ciertamente la mejor solución, lástima que no se explique muy bien. Funciona con clases, estructuras y otros tipos de valores. Las propiedades tampoco tienen que ser opcionales. Buen trabajo.
picciano
13

No puede definir categorías (extensiones Swift) con nuevo almacenamiento; cualquier propiedad adicional debe calcularse en lugar de almacenarse. La sintaxis funciona para el objetivo C porque @propertyen una categoría significa esencialmente "proporcionaré el captador y el definidor". En Swift, deberá definirlos usted mismo para obtener una propiedad calculada; algo como:

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

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

Debería funcionar bien. Recuerde, no puede almacenar nuevos valores en el setter, solo funciona con el estado de clase disponible existente.

Adam Wright
fuente
7

Mis $ 0.02. Este código está escrito en 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)
            }
        }
    }
}

He intentado muchas soluciones, y descubrí que esta es la única forma de extender realmente una clase con parámetros variables adicionales.

Amro Shafie
fuente
Parece interesante, pero se niega no funciona con protocolos, type 'AssociatedKeys' cannot be defined within a protocol extension...
Ian Bytchek
6

¿Por qué confiar en objc runtime? No entiendo el punto. Al usar algo como lo siguiente, logrará el comportamiento casi idéntico de una propiedad almacenada, al usar solo un enfoque puro de Swift :

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
        }
    }
}
valvolina
fuente
1
¡Inteligente! Un posible inconveniente con este enfoque es la gestión de la memoria. Después de que el objeto host se desasigna, su propiedad seguirá existiendo en el diccionario, lo que podría ser costoso en el uso de la memoria si se utiliza con muchos objetos.
Charlton Provatas
Probablemente podamos mantener una variable de lista estática de envoltorios que contienen referencias débiles a los objetos (self). Y elimine la entrada del diccionario _myComputedProperty, así como la variable de lista estática de envoltorios en el método didSet de Wrapper (cuando el objeto se reasigna didSet dentro de nuestra clase Wrapper se llama ya que nuestra referencia débil establece un nuevo valor con nil).
Arjuna
5

Prefiero hacer código en Swift puro y no confiar en la herencia de Objective-C. Debido a esto, escribí una solución Swift pura con dos ventajas y dos desventajas.

Ventajas:

  1. Código Swift puro

  2. Funciona en clases y terminaciones o más específicamente en Anyobjetos.

Desventajas

  1. El código debe llamar al método willDeinit()para liberar objetos vinculados a una instancia de clase específica para evitar pérdidas de memoria

  2. No puede hacer una extensión directamente a UIView para este ejemplo exacto porque var framees una extensión a UIView, no parte de la clase.

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
fuente
44
Esto no funciona ya que extensionPropertyStorage se comparte entre todas las instancias. Si establece un valor para una instancia, está estableciendo el valor para todas las instancias.
picciano
La guerra no es buena porque estropea las funciones. Ahora es mejor y funciona según lo previsto.
vedrano
@picciano extensionPropertyStorage se comparte con todas las instancias por diseño. Es la variable global (Diccionario) la que primero desreferencia la instancia de UILabel ( NSObject) y luego su propiedad ( [String: Any]).
vedrano
@picciano Supongo que esto funciona para classpropiedades;)
Nicolas Miari
framees una propiedad de instancia ¿De dónde viene la propiedad de clase?
vedrano
3

Con las categorías Obj-c solo puede agregar métodos, no variables de instancia.

En su ejemplo, ha utilizado @property como acceso directo para agregar declaraciones de métodos getter y setter. Aún necesita implementar esos métodos.

De manera similar, en Swift puede agregar extensiones de uso para agregar métodos de instancia, propiedades calculadas, etc. pero no propiedades almacenadas.

Mike Pollard
fuente
2

También recibo un problema EXC_BAD_ACCESS. El valor objc_getAssociatedObject()y objc_setAssociatedObject()debe ser un objeto. Y el objc_AssociationPolicydebe coincidir con el objeto.

RuiKQ
fuente
3
Esto realmente no responde la pregunta. Si tiene una pregunta diferente, puede hacerla haciendo clic en Hacer pregunta . También puede agregar una recompensa para llamar más la atención sobre esta pregunta una vez que tenga suficiente reputación . - De la opinión
AtheistP3ace
@ AtheistP3ace Lo siento mucho por eso. Intento agregar un comentario a la pregunta, pero no tengo suficiente reputación. Entonces trato de responder la pregunta.
RuiKQ
2

Intenté usar objc_setAssociatedObject como se menciona en algunas de las respuestas aquí, pero después de fallar algunas veces, di un paso atrás y me di cuenta de que no hay razón para eso. Partiendo de algunas de las ideas aquí, se me ocurrió este código que simplemente almacena una matriz de lo que mis datos adicionales están (MyClass en este ejemplo) indexados por el objeto con el que quiero asociarlos:

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
fuente
¿Tienes alguna idea de cómo borrar automáticamente extraData para que no cause pérdidas de memoria?
DoubleK
En realidad, no estaba usando esto con vistas, en mi caso solo instanciaba un número finito de objetos en la clase que estaba usando, por lo que realmente no he considerado pérdidas de memoria. No puedo pensar en una forma automática de hacer esto, pero supongo que en el setter anterior podría agregar lógica para eliminar el elemento si value == nil, y luego establecer el valor en nil cuando ya no necesite el objeto, o tal vez en didReceiveMemoryWarning o algo así.
Dan
Tnx @Dan, usé viewWillDisapper para establecer el valor en nil y funciona bien, ahora se llama a deinit y todo funciona bien. Tnx por publicar esta solución
DoubleK
2

Aquí hay una solución simplificada y más expresiva. Funciona tanto para el valor como para los tipos de referencia. El enfoque del levantamiento se toma de la respuesta de @HepaKKes.

Código de asociación:

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)
    }
}

Ejemplo de uso:

1) Crear extensión y asociarle propiedades. Usemos tanto el valor como las propiedades de tipo de referencia.

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) Ahora puedes usar las propiedades de forma regular:

    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

Más detalles:

  1. La elevación es necesaria para envolver los tipos de valor.
  2. El comportamiento del objeto asociado predeterminado es retener. Si desea obtener más información sobre los objetos asociados, le recomiendo consultar este artículo .
Vadim Bulavin
fuente
1

Otro ejemplo con el uso de objetos asociados Objective-C y propiedades calculadas para Swift 3 y 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
fuente
1

si está buscando establecer un atributo de cadena personalizado en una UIView, así es como lo hice en Swift 4

Crear una extensión 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 extensión

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
fuente
1

¿Qué hay de almacenar el mapa estático en la clase que se extiende así:

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
fuente
1
por qué esta respuesta no tiene votos a favor. Solución inteligente después de todo.
porJeevan
Esto funciona, pero creo que este enfoque es erróneo como se indica aquí medium.com/@valv0/…
Teffi
0

Traté de almacenar propiedades utilizando objc_getAssociatedObject, objc_setAssociatedObject, sin suerte. Mi objetivo era crear una extensión para UITextField, para validar la longitud de los caracteres de entrada de texto. El siguiente código funciona bien para mí. Espero que esto ayude a alguien.

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
fuente
Variables privadas globales? ¿Te das cuenta de eso _miny _maxson globales y serán lo mismo en todas las instancias del UITextField? Incluso si funciona para usted, esta respuesta no está relacionada porque Marcos pregunta por las variables de instancia .
Kelin
Sí, tienes razón, esto no funciona para todas las instancias. Lo arreglé almacenando el valor mínimo y máximo en la estructura. Perdón por el tema.
Vadims Krutovs
0

Aquí hay una alternativa que funciona también

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
fuente
-1

Esta solución me pareció más práctica

ACTUALIZADO 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)
    }

}

... luego en tu código

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}
Giuseppe Mazzilli
fuente
Esta es la forma correcta de hacerlo, pero en realidad no es una propiedad almacenada en la extensión como la pregunta.
UKDataGeek