riders online net lion jetbrain data ios swift associated-object

ios - online - jetbrain data



¿Cómo tener propiedades almacenadas en Swift, de la misma manera que tuve en Objective-C? (16)

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 por 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 al hacer:

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

¿Algunas ideas?


¿Por qué confiar en el tiempo de ejecución objc? No entiendo el punto. Al usar algo como lo siguiente, obtendrá casi el mismo comportamiento de una propiedad almacenada, utilizando solo un enfoque Swift puro :

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


Aquí hay una alternativa que también 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 )


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) Cree extensiones y asóciele propiedades. Usemos propiedades de valor y 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 solo como propiedades 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

Más detalles:

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

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/

Lo esencial es que uses una estructura así:

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


Como en Objective-C, no puede agregar propiedades almacenadas a las clases existentes. Si está ampliando una clase Objective-C ( UIView es definitivamente uno), aún puede usar Objetos Asociados para emular las 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 usamos su dirección de memoria como la clave con el operador & . Consulte el uso de Swift con Cocoa y Objective-C en 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) } } }


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

En su ejemplo, ha utilizado @property como un acceso directo para agregar declaraciones de método 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.


Encontré esta solución 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 } }


Intenté usar objc_setAssociatedObject como se menciona en algunas de las respuestas aquí, pero después de fallar varias veces, di un paso atrás y me di cuenta de que no había ninguna razón para necesitarlo. Tomando prestado de algunas de las ideas aquí, se me ocurrió este código que simplemente almacena una matriz de mis datos adicionales (MyClass en este ejemplo) indexados por el objeto al que quiero asociarlo:

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)


La API de objetos asociados es un poco engorrosa de usar. Puede eliminar la mayor parte del texto estándar con una clase de ayuda.

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


La solución señalada por jou no admite tipos de valores , 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) } } }


Mi $ 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 encontré que esta es la única manera de extender una clase con parámetros variables adicionales.


No puede definir categorías (extensiones Swift) con almacenamiento nuevo; cualquier propiedad adicional debe ser calculada en lugar de almacenada. La sintaxis funciona para Objective C porque @property en una categoría significa esencialmente "Proporcionaré getter y setter". 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 valores nuevos en el setter, solo trabaje con el estado de clase disponible existente.


Otro ejemplo con el uso de objetos asociados a 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 ) } } } }


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

Ventajas:

  1. Código Swift puro

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

Desventajas:

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

  2. No puede hacer la extensión directamente a UIView para este ejemplo exacto porque var frame es la extensión de 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 } }


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


Traté de almacenar propiedades usando 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) } }