Wie habe ich Eigenschaften in Swift gespeichert, so wie ich es in Objective-C getan habe?


132

Ich wechsle eine Anwendung von Objective-C zu Swift, für die ich einige Kategorien mit gespeicherten Eigenschaften habe, zum Beispiel:

@interface UIView (MyCategory)

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

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

@end

Da Swift-Erweiterungen solche gespeicherten Eigenschaften nicht akzeptieren, weiß ich nicht, wie ich die gleiche Struktur wie der Objc-Code beibehalten soll. Gespeicherte Eigenschaften sind für meine App sehr wichtig, und ich glaube, Apple muss eine Lösung dafür in Swift erstellt haben.

Wie von jou gesagt, suchte ich tatsächlich nach zugehörigen Objekten, also tat ich es (in einem anderen Kontext):

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

Aber ich bekomme eine EXC_BAD_ACCESS, wenn ich mache:

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

Irgendwelche Ideen?


11
Objective-C-Klassenkategorien können auch keine Instanzvariablen definieren. Wie haben Sie diese Eigenschaften realisiert?
Martin R

Ich bin mir nicht sicher, warum Sie einen schlechten Zugriff erhalten, aber Ihr Code sollte nicht funktionieren. Sie übergeben unterschiedliche Werte an setAssociateObject und getAssociatedObject. Die Verwendung der Zeichenfolge "shapeLayer" als Schlüssel ist falsch. Es ist der Zeiger (tatsächlich die Adresse), der den Schlüssel darstellt, nicht das, worauf er zeigt. Zwei identische Zeichenfolgen an unterschiedlichen Adressen sind zwei unterschiedliche Schlüssel. Überprüfen Sie Jous Antwort und stellen Sie fest, wie er xoAssociationKey als globale Variable definiert hat, sodass es sich beim Setzen / Abrufen um denselben Schlüssel / Zeiger handelt.
SafeFastExpressive

Antworten:


50

Die API für zugeordnete Objekte ist etwas umständlich zu verwenden. Sie können den größten Teil der Kesselplatte mit einer Hilfsklasse entfernen.

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

Vorausgesetzt, Sie können der Objective-C-Klasse eine Eigenschaft besser lesbar "hinzufügen":

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Wie für die Lösung:

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, was tun, wenn ich Int, Bool usw. speichern möchte?
Vyachaslav Gerchicov

1
Es ist nicht möglich, Swift-Typen direkt über die Objektzuordnung zu speichern. Sie können zB NSNumberoder speichern NSValueund zusätzliche Accessorenpaare schreiben, die von den gewünschten Typen sind (Int, Bool usw.).
Wojciech Nagrodzki

1
WARNUNG Dies funktioniert nicht für Strukturen, da die Objective-C-Laufzeitbibliothek nur Klassen unterstützt, die konform sindNSObjectProtocol
Charlton Provatas

2
@CharltonProvatas Es ist nicht möglich, Strukturen mit dieser API zu verwenden, sie entsprechen nicht dem AnyObject-Protokoll.
Wojciech Nagrodzki

@VyachaslavGerchicov [String]?ist eine Struktur, dies ist der gleiche Fall wie für Int, Bool. Sie können NSArrayeine Sammlung von NSStringInstanzen verwenden.
Wojciech Nagrodzki

184

Wie in Objective-C können Sie vorhandenen Klassen keine gespeicherten Eigenschaften hinzufügen. Wenn Sie eine Objective-C-Klasse erweitern ( UIViewdefinitiv eine), können Sie weiterhin zugeordnete Objekte verwenden , um gespeicherte Eigenschaften zu emulieren:

für 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))
        }
    }
}

Der Zuordnungsschlüssel ist ein Zeiger, der für jede Zuordnung eindeutig sein sollte. Dazu erstellen wir eine private globale Variable und verwenden deren Speicheradresse als Schlüssel für den &Operator. Sehen Sie sich die Verwendung von Swift mit Cocoa und Objective-C auf mehr Details , wie Zeiger in Swift abgewickelt.

AKTUALISIERT für Swift 2 und 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)
        }
    }
}

AKTUALISIERT für Swift 4

In Swift 4 ist es viel einfacher. Die Holder-Struktur enthält den privaten Wert, den unsere berechnete Eigenschaft der Welt zur Verfügung stellt, und gibt stattdessen die Illusion eines Verhaltens gespeicherter Eigenschaften.

Quelle

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

2
@Yar Wusste nichts davon objc_AssociationPolicy, danke! Ich habe die Antwort aktualisiert
jou

2
In Swift2 müssen Sie objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom

1
@SimplGy Bitte fügen Sie Ihre Bearbeitung als zusätzliche Antwort hinzu, anstatt Ihren Code in die Antwort einer anderen Person zu bearbeiten.
JAL

11
Die Swift4-Lösung funktioniert nicht, wenn Sie mehr als eine Instanz von UIViewController haben möchten, die die Eigenschaft aus der Erweiterung verwendet. Bitte überprüfen Sie die Updates in der Quelle.
iur

9
Das Swift 4-Beispiel funktioniert nicht mit mehreren Instanzen und sollte wahrscheinlich nicht hier sein.
Colin Cornaby

36

Ich denke, ich habe eine Methode gefunden, die sauberer funktioniert als die oben genannten, da keine globalen Variablen erforderlich sind. Ich habe es von hier bekommen: http://nshipster.com/swift-objc-runtime/

Das Wesentliche ist, dass Sie eine Struktur wie folgt verwenden:

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

UPDATE für 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 Ich habe Code wie Ihren implementiert, aber beim Aktualisieren auf Swift 2.0 wird der Fehler angezeigt, dass der Initialisierer für den Typ 'UInt' nicht mit einer Argumentliste vom Typ 'objc_AssociationPolicy)' aufgerufen werden kann. Code im nächsten Kommentar
user2363025

Wie aktualisiere ich für Swift 2.0? var postDescription: String? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) as? String} set {if lassen newValue = newValue {objc_setAssociatedObject (self, & AssociatedKeys.postDescription, newValue als NSString ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025

1
funktioniert nicht: static var bedeutet, dass der Wert der Eigenschaft für alle Instanzen gleich ist
Fry

@fry - es funktioniert weiterhin für mich. Ja, der Schlüssel ist statisch, aber er ist einem bestimmten Objekt zugeordnet. Das Objekt selbst ist nicht global.
AlexK

15

Die von Ihnen erwähnte Lösung unterstützt keine Werttypen , dies funktioniert auch gut mit ihnen

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

Eine mögliche Klassenerweiterung (Anwendungsbeispiel):

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

Dies ist wirklich eine großartige Lösung. Ich wollte ein weiteres Anwendungsbeispiel hinzufügen, das Strukturen und Werte enthält, die keine Optionen sind. Außerdem können die AssociatedKey-Werte vereinfacht werden.

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

Und wie benutze ich die Lifted-Klasse?
3lvis

Warum brauchst du das? Wozu?
HepaKKes

Ich meinte, wie Sie dies im Allgemeinen verwenden, danke für Ihre Hilfe :) Können Sie bitte ein Beispiel für die Verwendung hinzufügen?
3lvis

1
Das Beispiel "Verwendung" beginnt direkt unter dem Header "Eine mögliche Klassenerweiterung". Ich denke nicht, dass Benutzer wissen müssen, wie man die liftMethode und die LiftedKlasse verwendet, sie sollten jedoch getAssociatedObject& setAssociatedObjectFunktionen verwenden. Der Klarheit halber werde ich in Klammern neben der Kopfzeile "Anwendungsbeispiel" einfügen.
HepaKKes

1
Dies ist sicherlich die beste Lösung, schade, dass es nicht sehr gut erklärt wird. Es funktioniert mit Klassen, Strukturen und anderen Werttypen. Die Eigenschaften müssen auch keine Optionen sein. Gute Arbeit.
Picciano

13

Sie können keine Kategorien (Swift-Erweiterungen) mit neuem Speicher definieren. Alle zusätzlichen Eigenschaften müssen berechnet und nicht gespeichert werden. Die Syntax funktioniert für Ziel C, da @propertyin einer Kategorie im Wesentlichen "Ich werde den Getter und Setter bereitstellen" bedeutet. In Swift müssen Sie diese selbst definieren, um eine berechnete Eigenschaft zu erhalten. etwas wie:

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

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

Sollte gut funktionieren. Denken Sie daran, dass Sie keine neuen Werte im Setter speichern können, sondern nur mit dem vorhandenen verfügbaren Klassenstatus arbeiten.


7

Meine $ 0,02. Dieser Code ist in Swift 2.0 geschrieben

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

Ich habe viele Lösungen ausprobiert und festgestellt, dass dies die einzige Möglichkeit ist, eine Klasse tatsächlich um zusätzliche variable Parameter zu erweitern.


Sieht interessant aus, aber weigert sich nicht mit Protokollen, type 'AssociatedKeys' cannot be defined within a protocol extension...
Ian Bytchek

6

Warum sich auf die objc-Laufzeit verlassen? Ich verstehe den Punkt nicht. Wenn Sie Folgendes verwenden, erzielen Sie fast das gleiche Verhalten einer gespeicherten Eigenschaft, indem Sie nur einen reinen Swift-Ansatz verwenden :

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
Klug! Ein möglicher Nachteil dieses Ansatzes ist die Speicherverwaltung. Nachdem die Zuweisung des Hostobjekts aufgehoben wurde, ist seine Eigenschaft weiterhin im Wörterbuch vorhanden, was bei der Speichernutzung möglicherweise teuer werden kann, wenn dies mit vielen Objekten verwendet wird.
Charlton Provatas

Wahrscheinlich können wir eine statische Listenvariable von Wrappern enthalten, die einen schwachen Verweis auf die Objekte (self) enthalten. Entfernen Sie den Eintrag aus dem _myComputedProperty-Wörterbuch sowie die statische Listenvariable der Wrapper in der didSet-Methode von Wrapper (Wenn das Objekt neu zugewiesen wird, wird didSet in unserer Wrapper-Klasse aufgerufen, da für unsere schwache Referenz ein neuer Wert mit nil festgelegt wird).
Arjuna

5

Ich bevorzuge Code in reinem Swift und verlasse mich nicht auf das Objective-C-Erbe. Aus diesem Grund habe ich eine reine Swift-Lösung mit zwei Vor- und zwei Nachteilen geschrieben.

Vorteile:

  1. Reiner Swift-Code

  2. Funktioniert mit Klassen und Vervollständigungen oder genauer mit AnyObjekten

Nachteile:

  1. Code sollte eine Methode aufrufen willDeinit(), um Objekte freizugeben, die mit einer bestimmten Klasseninstanz verknüpft sind, um Speicherverluste zu vermeiden

  2. Sie können für genau dieses Beispiel keine Erweiterung direkt auf UIView var framevornehmen, da die Erweiterung auf UIView nicht Teil der Klasse ist.

BEARBEITEN:

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
Dies funktioniert nicht, da extensionPropertyStorage von allen Instanzen gemeinsam genutzt wird. Wenn Sie einen Wert für eine Instanz festlegen, legen Sie den Wert für alle Instanzen fest.
Picciano

Es war nicht gut, weil es mit Funktionen durcheinander gebracht wurde. Jetzt ist es besser und funktioniert wie vorgesehen.
Vedrano

@picciano extensionPropertyStorage wird standardmäßig mit allen Instanzen geteilt. Es ist eine globale Variable (Dictionary), die zuerst die UILabel-Instanz ( NSObject) und dann ihre Eigenschaft ( [String: Any]) de-referenziert .
Vedrano

@ Picciano Ich denke, das funktioniert für classImmobilien;)
Nicolas Miari

frameist eine Instanzeigenschaft. Woher kommt Klasseneigentum?
Vedrano

3

Mit Obj-c Categories können Sie nur Methoden hinzufügen, keine Instanzvariablen.

In Ihrem Beispiel haben Sie @property als Verknüpfung zum Hinzufügen von Getter- und Setter-Methodendeklarationen verwendet. Sie müssen diese Methoden noch implementieren.

In ähnlicher Weise können Sie in Swift Verwendungserweiterungen hinzufügen, um Instanzmethoden, berechnete Eigenschaften usw. hinzuzufügen, jedoch keine gespeicherten Eigenschaften.


2

Ich bekomme auch ein EXC_BAD_ACCESS-Problem. Der Wert in objc_getAssociatedObject()und objc_setAssociatedObject()sollte ein Objekt sein. Und das objc_AssociationPolicysollte zum Objekt passen.


3
Dies beantwortet die Frage nicht wirklich. Wenn Sie eine andere Frage haben, können Sie diese stellen, indem Sie auf Frage stellen klicken . Sie können auch ein Kopfgeld hinzufügen, um mehr Aufmerksamkeit auf diese Frage zu lenken, sobald Sie genügend Ruf haben . - Von der Überprüfung
AtheistP3ace

@ AtheistP3ace Das tut mir so leid. Ich versuche, der Frage einen Kommentar hinzuzufügen. Aber ich habe nicht genug Ruf. Also versuche ich die Frage zu beantworten.
RuiKQ

2

Ich habe versucht, objc_setAssociatedObject zu verwenden, wie in einigen der Antworten hier erwähnt, aber nachdem ich einige Male damit gescheitert war, trat ich zurück und stellte fest, dass es keinen Grund gibt, warum ich das brauche. Ausgehend von einigen der hier vorgestellten Ideen habe ich diesen Code entwickelt, in dem einfach ein Array meiner zusätzlichen Daten (in diesem Beispiel MyClass) gespeichert ist, die von dem Objekt indiziert werden, mit dem ich sie verknüpfen möchte:

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)

Haben Sie eine Idee, wie Sie extraData automatisch löschen können, damit keine Speicherlecks auftreten?
DoubleK

Ich habe dies nicht mit Ansichten verwendet. In meinem Fall habe ich immer nur eine begrenzte Anzahl von Objekten in der von mir verwendeten Klasse instanziiert, sodass ich Speicherlecks nicht wirklich berücksichtigt habe. Ich kann mir keinen automatischen Weg vorstellen, dies zu tun, aber ich nehme an, Sie könnten im obigen Setter Logik hinzufügen, um das Element zu entfernen, wenn value == nil, und dann den Wert auf nil setzen, wenn Sie das Objekt nicht mehr benötigen, oder Vielleicht in didReceiveMemoryWarning oder so.
Dan

Tnx @Dan, ich habe viewWillDisapper verwendet, um den Wert auf Null zu setzen, und es funktioniert einwandfrei. Jetzt wird deinit aufgerufen und alles funktioniert einwandfrei. Tnx für die Veröffentlichung dieser Lösung
DoubleK

2

Hier ist eine vereinfachte und aussagekräftigere Lösung. Es funktioniert sowohl für Wert- als auch für Referenztypen. Der Ansatz des Hebens ist der Antwort von @HepaKKes entnommen.

Assoziationscode:

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

Anwendungsbeispiel:

1) Erstellen Sie eine Erweiterung und ordnen Sie ihr Eigenschaften zu. Verwenden wir sowohl Wert- als auch Referenztyp-Eigenschaften.

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) Jetzt können Sie nur noch reguläre Eigenschaften verwenden:

    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

Mehr Details:

  1. Zum Umschließen von Werttypen ist ein Anheben erforderlich.
  2. Das standardmäßig zugeordnete Objektverhalten bleibt erhalten. Wenn Sie mehr über zugehörige Objekte erfahren möchten, empfehlen wir Ihnen, diesen Artikel zu lesen .

1

Ein weiteres Beispiel für die Verwendung von Objective-C-zugeordneten Objekten und berechneten Eigenschaften für Swift 3 und 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

Wenn Sie ein benutzerdefiniertes Zeichenfolgenattribut für eine UIView festlegen möchten, habe ich dies in Swift 4 so gemacht

Erstellen Sie eine UIView-Erweiterung

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

So verwenden Sie diese Erweiterung

let key = "COLOR"

let redView = UIView() 

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

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

1

Wie wäre es mit dem Speichern einer statischen Karte in einer Klasse, die sich wie folgt erweitert:

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
warum diese Antwort keine positiven Stimmen hat. Immerhin eine kluge Lösung.
Jeevan

Dies funktioniert, aber ich denke, dieser Ansatz ist fehlerhaft, wie hier angegeben. Medium.com/@valv0/…
Teffi

0

Ich habe versucht, Eigenschaften mithilfe von objc_getAssociatedObject, objc_setAssociatedObject, ohne Glück zu speichern. Mein Ziel war es, eine Erweiterung für UITextField zu erstellen, um die Länge der Texteingabezeichen zu überprüfen. Der folgende Code funktioniert gut für mich. Hoffe das wird jemandem helfen.

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

Globale private Variablen? Ist Ihnen das klar _minund Sie _maxsind global und werden in allen Instanzen des UITextField gleich sein? Auch wenn es für Sie arbeitet, ist diese Antwort nicht verwendet , weil Marcos zu fragen Instanz Variablen.
Kelin

Ja, Sie haben Recht, dies funktioniert nicht für alle Instanzen. I Behoben durch Speichern von Min- und Max-Werten in struct. Entschuldigung für das Off-Topic.
Vadims Krutovs

0

Hier ist eine Alternative, die auch funktioniert

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

Ich fand diese Lösung praktischer

AKTUALISIERT für 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)
    }

}

... dann in deinem Code

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}

Dies ist der richtige Weg, aber nicht wirklich eine gespeicherte Eigenschaft in der Erweiterung, wie die Frage stellt.
UKDataGeek
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.