Come avere proprietà memorizzate in Swift, come ho fatto su Objective-C?


132

Sto cambiando un'applicazione da Objective-C a Swift, che ho un paio di categorie con proprietà memorizzate, ad esempio:

@interface UIView (MyCategory)

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

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

@end

Poiché le estensioni Swift non accettano proprietà memorizzate come queste, non so come mantenere la stessa struttura del codice Objc. Le proprietà memorizzate sono davvero importanti per la mia app e credo che Apple abbia creato una soluzione per farlo in Swift.

Come ho detto jou, quello che stavo cercando era effettivamente usare oggetti associati, quindi ho fatto (in un altro contesto):

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

Ma ottengo un EXC_BAD_ACCESS quando faccio:

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

Qualche idea?


11
Neanche le categorie di classe Objective-C possono definire variabili di istanza, quindi come hai realizzato queste proprietà?
Martin R,

Non sono sicuro del motivo per cui hai un accesso errato, ma il tuo codice non dovrebbe funzionare. Stai passando valori diversi a setAssociateObject e getAssociatedObject. L'uso della stringa "shapeLayer" come chiave è errato, è il puntatore (in realtà è l'indirizzo) che è la chiave, non ciò a cui punta. Due stringhe identiche residenti a indirizzi diversi sono due chiavi diverse. Rivedi la risposta di Jou e nota come ha definito xoAssociationKey come una variabile globale, quindi è la stessa chiave / puntatore durante l'impostazione / acquisizione.
SafeFastExpressive

Risposte:


50

L'API degli oggetti associati è un po 'complicata da usare. È possibile rimuovere la maggior parte della piastra della caldaia con una classe di supporto.

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

A condizione che tu possa "aggiungere" una proprietà alla classe obiettiva-c in un modo più leggibile:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Per quanto riguarda la soluzione:

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

Ok cosa devo fare se voglio memorizzare Int, Bool ed ecc?
Vyachaslav Gerchicov,

1
Non è possibile memorizzare direttamente i tipi di Swift tramite l'associazione degli oggetti. È possibile memorizzare ad es. NSNumberOe NSValuescrivere ulteriori coppie di accessori che sarebbero dei tipi desiderati (Int, Bool, ecc.).
Wojciech Nagrodzki,

1
ATTENZIONE Questo non funziona per le strutture, perché la libreria di runtime di object-c supporta solo classi conformi aNSObjectProtocol
Charlton Provatas,

2
@CharltonProvatas Non è possibile utilizzare le strutture con questa API, non sono conformi al protocollo AnyObject.
Wojciech Nagrodzki,

@VyachaslavGerchicov [String]?è una struttura, questo è lo stesso caso di Int, Bool. È possibile utilizzare NSArrayper conservare una raccolta di NSStringistanze.
Wojciech Nagrodzki,

184

Come in Objective-C, non è possibile aggiungere proprietà memorizzate alle classi esistenti. Se stai estendendo una classe Objective-C ( UIViewè sicuramente una), puoi comunque usare gli oggetti associati per emulare le proprietà memorizzate:

per 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 chiave di associazione è un puntatore che dovrebbe essere unico per ogni associazione. Per questo, creiamo una variabile globale privata e usiamo il suo indirizzo di memoria come chiave con l' &operatore. Vedi Uso di Swift con Cocoa e Objective-C per maggiori dettagli su come vengono gestiti i puntatori in Swift.

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

AGGIORNATO per Swift 4

In Swift 4, è molto più semplice. La struttura del Titolare conterrà il valore privato che la nostra proprietà calcolata esporrà al mondo, dando invece l'illusione di un comportamento di proprietà memorizzata.

fonte

extension UIViewController {
    struct Holder {
        static var _myComputedProperty:Bool = false
    }
    var myComputedProperty:Bool {
        get {
            return Holder._myComputedProperty
        }
        set(newValue) {
            Holder._myComputedProperty = newValue
        }
    }
}

2
@Yar Non lo sapevo objc_AssociationPolicy, grazie! Ho aggiornato la risposta
jou

2
In Swift2 devi usare objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom

1
@SimplGy aggiungi la modifica come risposta aggiuntiva anziché modificare il codice nella risposta di qualcun altro.
JAL

11
La soluzione Swift4 non funziona se si desidera avere più di 1 istanza di UIViewController che utilizza la proprietà dall'estensione. Si prega di controllare gli aggiornamenti nella fonte.
iur

9
L'esempio di Swift 4 non funziona con più istanze e probabilmente non dovrebbe essere qui.
Colin Cornaby,

36

Quindi penso di aver trovato un metodo che funziona in modo più pulito rispetto a quelli sopra perché non richiede variabili globali. L'ho preso da qui: http://nshipster.com/swift-objc-runtime/

L'essenza è che usi una struttura in questo modo:

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

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

@AKWeblS Ho implementato un codice come il tuo, ma durante l'aggiornamento a Swift 2.0 non riesco a richiamare l'inizializzatore per il tipo "UInt" con un elenco di argomenti di tipo "objc_AssociationPolicy)". Codice nel prossimo commento
user2363025

Come aggiornare per Swift 2.0? var postDescrizione: String? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) as? String} set {se lasciare newValue = {newValue objc_setAssociatedObject (self, e AssociatedKeys.postDescription, newValue come NSString ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
non funziona: static var significa che il valore della proprietà è lo stesso per tutte le istanze
Fry

@fry - continua a funzionare per me. Sì, la chiave è statica, ma è associata a un oggetto specifico, l'oggetto stesso non è globale.
AlexK,

15

La soluzione indicata da jou non supporta i tipi di valore , ma funziona anche con loro

Wrapper

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 possibile estensione di classe (esempio di utilizzo):

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

Questa è davvero un'ottima soluzione, volevo aggiungere un altro esempio di utilizzo che includesse strutture e valori non opzionali. Inoltre, i valori AssociatedKey possono essere semplificati.

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

E come posso usare la classe Lifted?
3lvis,

Perché ne hai bisogno? Per che cosa?
HepaKKes

Intendevo come lo usi in generale, grazie per il tuo aiuto :) Puoi aggiungere un esempio di "Come usare", per favore?
3lvis,

1
L'esempio "Come usare" inizierebbe proprio sotto l'intestazione "Una possibile estensione di classe". Non penso che gli utenti debbano sapere come usare il liftmetodo e la Liftedclasse, dovrebbero usare comunque getAssociatedObjecte le setAssociatedObjectfunzioni. Aggiungerò "Esempio di utilizzo" tra parentesi accanto all'intestazione per motivi di chiarezza.
HepaKKes,

1
Questa è sicuramente la soluzione migliore, peccato che non sia spiegata molto bene. Funziona con classi, strutture e altri tipi di valore. Le proprietà non devono essere neanche opzionali. Bel lavoro.
picciano,

13

Non è possibile definire le categorie (estensioni Swift) con nuovo spazio di archiviazione; eventuali proprietà aggiuntive devono essere calcolate anziché archiviate. La sintassi funziona per Objective C perché @propertyin una categoria essenzialmente significa "Fornirò il getter e il setter". In Swift, dovrai definirli tu stesso per ottenere una proprietà calcolata; qualcosa di simile a:

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

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

Dovrebbe funzionare bene. Ricorda, non puoi memorizzare nuovi valori nel setter, funziona solo con lo stato di classe disponibile esistente.


7

I miei $ 0,02. Questo codice è scritto in 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)
            }
        }
    }
}

Ho provato molte soluzioni e ho scoperto che questo è l'unico modo per estendere effettivamente una classe con parametri variabili extra.


Sembra interessante, ma i rifiuti non funzionano con i protocolli, type 'AssociatedKeys' cannot be defined within a protocol extension...
Ian Bytchek,

6

Perché affidarsi a runtime objc? Non capisco il punto. Usando qualcosa di simile al seguente otterrai quasi lo stesso comportamento di una proprietà memorizzata, usando solo un approccio 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
        }
    }
}

1
Intelligente! Un possibile aspetto negativo di questo approccio è la gestione della memoria. Dopo che l'oggetto host è stato deallocato, la sua proprietà sarà ancora presente nel dizionario, il che potrebbe potenzialmente diventare costoso in termini di utilizzo della memoria se utilizzato con molti oggetti.
Charlton Provatas,

Probabilmente possiamo contenere una variabile di elenco statico di wrapper che contengono riferimenti deboli agli oggetti (self). E rimuovi la voce dal dizionario _myComputedProperty e anche la variabile elenco statico di wrapper nel metodo didSet di Wrapper (Quando l'oggetto viene riallocato didSet all'interno della nostra classe Wrapper viene chiamato poiché il nostro riferimento debole viene impostato un nuovo valore con zero).
Arjuna,

5

Preferisco fare codice in puro Swift e non fare affidamento sull'eredità di Objective-C. Per questo motivo ho scritto la soluzione Swift pura con due vantaggi e due svantaggi.

vantaggi:

  1. Codice Pure Swift

  2. Funziona su classi e completamenti o più specificamente Anysull'oggetto

svantaggi:

  1. Il codice deve chiamare il metodo willDeinit()per rilasciare oggetti collegati a un'istanza di classe specifica per evitare perdite di memoria

  2. Non è possibile effettuare l'estensione direttamente in UIView per questo esempio esatto perché var frameè estensione di UIView, non parte della classe.

MODIFICARE:

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

4
Questo non funziona poiché extensionPropertyStorage è condiviso tra tutte le istanze. Se si imposta un valore per un'istanza, si sta impostando il valore per tutte le istanze.
picciano,

La guerra non va bene perché si scherza con le funzioni. Ora è meglio e funziona come previsto.
vedrano,

@picciano extensionPropertyStorage è condiviso con tutte le istanze dal design. È la variabile globale (Dizionario) che de-referenzia prima l'istanza di UILabel ( NSObject) e poi la sua proprietà ( [String: Any]).
vedrano,

@picciano Immagino che questo funzioni per le classproprietà;)
Nicolas Miari,

frameè una proprietà di istanza. Da dove viene la proprietà di classe?
vedrano,

3

Con Obj-c Categorie puoi solo aggiungere metodi, non variabili di istanza.

Nel tuo esempio hai usato @property come scorciatoia per aggiungere dichiarazioni sui metodi getter e setter. Devi ancora implementare questi metodi.

Allo stesso modo in Swift è possibile aggiungere estensioni d'uso per aggiungere metodi di istanza, proprietà calcolate ecc. Ma non proprietà memorizzate.


2

Ottengo anche un problema EXC_BAD_ACCESS. Il valore in objc_getAssociatedObject()e objc_setAssociatedObject()dovrebbe essere un oggetto. E objc_AssociationPolicydovrebbe corrispondere all'oggetto.


3
Questo non risponde davvero alla domanda. Se hai una domanda diversa, puoi farla facendo clic su Poni domanda . Puoi anche aggiungere una taglia per attirare più attenzione a questa domanda una volta che hai abbastanza reputazione . - Dalla recensione
AtheistP3ace,

@ AtheistP3ace Mi dispiace così tanto. Provo ad aggiungere un commento alla domanda, ma non ho abbastanza reputazione. Quindi provo a rispondere alla domanda.
RuiKQ,

2

Ho provato a usare objc_setAssociatedObject come menzionato in alcune delle risposte qui, ma dopo aver fallito alcune volte ho fatto un passo indietro e ho capito che non c'è motivo per cui ne ho bisogno. Prendendo in prestito da alcune delle idee qui, ho trovato questo codice che memorizza semplicemente una matrice di qualunque sia il mio dato extra (MyClass in questo esempio) indicizzato dall'oggetto con cui voglio associarlo:

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)

hai idea di come cancellare automaticamente extraData in modo che non causi perdite di memoria?
DoubleK,

In realtà non lo stavo usando con le viste, nel mio caso ho sempre creato un'istanza di un numero finito di oggetti nella classe che stavo usando, quindi non ho davvero preso in considerazione le perdite di memoria. Non riesco a pensare a un modo automatico per farlo, ma suppongo che nel setter sopra potresti aggiungere la logica per rimuovere l'elemento se value == zero, quindi impostare il valore su zero quando non hai più bisogno dell'oggetto, oppure forse in didReceiveMemoryWarning o qualcosa del genere.
Dan,

Tnx @Dan, ho usato viewWillDisapper per impostare il valore su zero e funziona bene, ora deinit viene chiamato e tutto funziona bene. Grazie per pubblicare questa soluzione
DoubleK,

2

Ecco una soluzione semplificata e più espressiva. Funziona con valori e tipi di riferimento. L'approccio del sollevamento è preso dalla risposta di @HepaKKes.

Codice associazione:

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

Esempio di utilizzo:

1) Creare l'estensione e associare le proprietà ad esso. Usiamo le proprietà sia del valore che del tipo di riferimento.

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) Ora puoi usare le proprietà normali:

    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

Più dettagli:

  1. Il sollevamento è necessario per avvolgere i tipi di valore.
  2. Viene mantenuto il comportamento predefinito dell'oggetto associato. Se vuoi saperne di più sugli oggetti associati, ti consiglio di controllare questo articolo .

1

Un altro esempio con l'utilizzo di oggetti associati a Objective-C e proprietà calcolate per 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
                )
            }
        }
    }

}

1

se stai cercando di impostare un attributo stringa personalizzato su un UIView, è così che l'ho fatto su Swift 4

Crea un'estensione 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
    }
}

Per usare questa estensione

let key = "COLOR"

let redView = UIView() 

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

// To read
print(redView.stringValueFor(key: key)) // Optional("Red")

1

Che ne dici di memorizzare una mappa statica in una classe che si sta estendendo in questo modo:

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

}

1
perché questa risposta non ha voti positivi. Soluzione intelligente dopo tutto.
Jeevan

Funziona ma penso che questo approccio sia imperfetto, come indicato qui medium.com/@valv0/…
Teffi

0

Ho provato a memorizzare le proprietà utilizzando objc_getAssociatedObject, objc_setAssociatedObject, senza alcuna fortuna. Il mio obiettivo era creare l'estensione per UITextField, per convalidare la lunghezza dei caratteri di input di testo. Il seguente codice funziona bene per me. Spero che questo possa aiutare qualcuno.

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

Variabili private globali? Ti rendi conto che _mine _maxsono globali e sarà lo stesso in tutte le istanze della UITextField? Anche se funziona per te, questa risposta non è correlata perché Marcos chiede delle variabili di istanza .
Kelin

Sì, hai ragione, questo non funziona per tutte le istanze. Ho risolto memorizzando il valore minimo e massimo in struct. Ci scusiamo per l'argomento fuori tema.
Vadims Krutovs,

0

Ecco un'alternativa che funziona anche

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 )

-1

Ho trovato questa soluzione più pratica

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

}

... quindi nel tuo codice

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}

Questo è il modo giusto per farlo - ma non proprio una proprietà memorizzata nell'estensione come fa la domanda.
UKDataGeek,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.