Gibt es eine Möglichkeit, zugeordnete Objekte in Swift festzulegen?


82

Von Ziel C aus können Sie die Funktion objc_setAssociatedObjectzwischen zwei Objekten aufrufen , damit diese eine Referenz verwalten. Dies kann hilfreich sein, wenn Sie zur Laufzeit nicht möchten, dass ein Objekt zerstört wird, bis auch seine Referenz entfernt wird. Hat Swift etwas Ähnliches?


3
Sie können objc_setAssociatedObjectvon Swift verwenden: developer.apple.com/library/prerelease/ios/documentation/Swift/…
jtbandes

LANGES BEISPIEL MIT VOLLSTÄNDIGEM CODE zum Ausschneiden und Einfügen: stackoverflow.com/documentation/swift/1085/associated-objects/…
Fattie

Antworten:


130

Hier ist ein einfaches, aber vollständiges Beispiel, das aus der Antwort von jckarter abgeleitet wurde .

Es zeigt, wie Sie einer vorhandenen Klasse eine neue Eigenschaft hinzufügen. Dazu wird eine berechnete Eigenschaft in einem Erweiterungsblock definiert. Die berechnete Eigenschaft wird als zugehöriges Objekt gespeichert:

import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
private var AssociatedObjectHandle: UInt8 = 0

extension MyClass {
    var stringProperty:String {
        get {
            return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! String
        }
        set {
            objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

BEARBEITEN:

Wenn Sie das Abrufen des Werts einer nicht initialisierten Eigenschaft unterstützen und den Fehler vermeiden möchten unexpectedly found nil while unwrapping an Optional value, können Sie den Getter folgendermaßen ändern:

    get {
        return objc_getAssociatedObject(self, &AssociatedObjectHandle) as? String ?? ""
    }

Konnten Sie auf diese Weise eine Funktion einstellen, die einem Typealias entspricht?
Morkrom

1
@PhamHoan Int ist ein nativer Swift-Typ. Versuchen Sie stattdessen, NSNumber zu verwenden.
Klaas

1
@PhamHoan Ich habe eine Lösung hinzugefügt, um ein nicht initialisiertes Objekt zu erhalten.
Klaas

1
@ Jawwad Ich denke, es macht keinen Unterschied. Das UInt8kommt aus dem verlinkten Original-Thread.
Klaas

1
hey @Jacky - warum sagst du, dass es KOPIEREN sollte? Bitte lass es uns wissen!
Fattie

31

Die Lösung unterstützt alle Werttypen als auch, und nicht nur jene, die automatisch überbrücken, wie String, Int, Double usw.

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

Können Sie mir bitte erklären, was diese Assoziationsflaggen bedeuten? Ich versuche tatsächlich, die Navigationsleiste mit scrollView scrollen zu lassen, also speichere ich scrollView in der Erweiterung. Welche Flags soll ich verwenden?
Meteore

1
Außerdem versuche ich, mit dieser Methode ein Array von [AnyObject] zu speichern, aber der Getter gibt mir immer null zurück. Irgendwie gibt das else if von get den Wert nicht richtig zurück.
Meteore

Die Art der Assoziativitätsrichtlinie, für die Sie sich entscheiden, liegt bei Ihnen. Sie hängt von Ihrem Programm ab. Für weitere Informationen empfehle ich Ihnen, diesen Artikel unter nshipster.com/associated-objects zu lesen . Ich habe versucht, Arrays zu speichern, es scheint gut zu funktionieren. Welche Swift-Version verwenden Sie übrigens?
HepaKKes

Das ist brilliant. Für Swift 2.3 habe ich die Generika aus dem Lift verschoben und Anystattdessen verwendet. Ich werde einen Blog-Artikel darüber machen, denke ich
Dan Rosenstark

5
Dies wurde in Swift 3 viel einfacher. Aber hier ist mein Artikel, der auf Ihrer Antwort hier basiert: yar2050.com/2016/11/associated-object-support-for-swift-23.html
Dan Rosenstark

5

Dies funktioniert natürlich nur mit Objective-C-Objekten. Nachdem Sie ein wenig damit herumgespielt haben, gehen Sie wie folgt vor, um die Anrufe in Swift zu tätigen:

import ObjectiveC

// Define a variable whose address we'll use as key.
// "let" doesn't work here.
var kSomeKey = "s"func someFunc() {
    objc_setAssociatedObject(target, &kSomeKey, value, UInt(OBJC_ASSOCIATION_RETAIN))

    let value : AnyObject! = objc_getAssociatedObject(target, &kSomeKey)
}

Ich habe diese Methode ausprobiert, aber mein Wertobjekt scheint sofort freigegeben zu werden, wenn keine anderen Verweise von Swift-Code darauf vorhanden sind. Mein Zielobjekt ist ein SKNode und mein Wertobjekt ist eine schnelle Klasse, die NSObject erweitert. Sollte das funktionieren?
Nacross

Tatsächlich scheint deinit während objc_setAssociatedObject aufgerufen zu werden, obwohl das Objekt gerade erstellt wurde und weiter unten in der Methode verwendet wird.
Nacross

4

Update in Swift 3.0 Dies ist beispielsweise ein UITextField

import Foundation
import UIKit
import ObjectiveC

// Declare a global var to produce a unique address as the assoc object handle
var AssociatedObjectHandle: UInt8 = 0

extension UITextField
{
    var nextTextField:UITextField {
    get {
        return objc_getAssociatedObject(self, &AssociatedObjectHandle) as! UITextField
    }
    set {
        objc_setAssociatedObject(self, &AssociatedObjectHandle, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
        }
    }
}

2

Klaas antwortet nur für Swift 2.1:

import ObjectiveC

let value = NSUUID().UUIDString
var associationKey: UInt8 = 0

objc_setAssociatedObject(parentObject, &associationKey, value, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)

let fetchedValue = objc_getAssociatedObject(parentObject, &associationKey) as! String

2

Ich habe einen modernen Helfer geschrieben, der Swift 5.1 benötigt.

/*
 AssociatedObject.swift

 Copyright © 2020 RFUI.
 https://github.com/BB9z/iOS-Project-Template

 The MIT License
 https://opensource.org/licenses/MIT
 */

import Foundation

/**
 Objective-C associated value wrapper.

 Usage

 ```
 private let fooAssociation = AssociatedObject<String>()
 extension SomeObject {
     var foo: String? {
         get { fooAssociation[self] }
         set { fooAssociation[self] = newValue }
     }
 }
 ```
 */
public final class AssociatedObject<T> {
    private let policy: objc_AssociationPolicy

    /// Creates an associated value wrapper.
    /// - Parameter policy: The policy for the association.
    public init(policy: objc_AssociationPolicy = .OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
        self.policy = policy
    }

    /// Accesses the associated value.
    /// - Parameter index: The source object for the association.
    public subscript(index: AnyObject) -> T? {
        get { objc_getAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque()) as? T }
        set { objc_setAssociatedObject(index, Unmanaged.passUnretained(self).toOpaque(), newValue, policy) }
    }
}

Sie werden überrascht sein, dass es sogar Swift-Strukturen kostenlos unterstützt.

Schnelle Strukturassoziation


0

Fügen Sie einfach #import <objc/runtime.h>auf Ihre brindging Header - Datei für den Zugriff objc_setAssociatedObject unter SWIFT - Code


18
oder Sie können nur import ObjectiveCin Swift
Newacct

0

Der oben genannte Freund hat Ihre Frage beantwortet, aber wenn es sich um Verschlusseigenschaften handelt, beachten Sie bitte:

`` `

import UIKit
public extension UICollectionView {

typealias XYRearrangeNewDataBlock = (_ newData: [Any]) -> Void
typealias XYRearrangeOriginaDataBlock = () -> [Any]

// MARK:- associat key
private struct xy_associatedKeys {
    static var originalDataBlockKey = "xy_originalDataBlockKey"
    static var newDataBlockKey = "xy_newDataBlockKey"
}


private class BlockContainer {
    var rearrangeNewDataBlock: XYRearrangeNewDataBlock?
    var rearrangeOriginaDataBlock: XYRearrangeOriginaDataBlock?
}


private var newDataBlock: BlockContainer? {
    get {
        if let newDataBlock = objc_getAssociatedObject(self, &xy_associatedKeys.newDataBlockKey) as? BlockContainer {
            return newDataBlock
        }
        return nil
    }

    set(newValue) {
        objc_setAssociatedObject(self, xy_associatedKeys.newDataBlockKey, newValue, .OBJC_ASSOCIATION_COPY_NONATOMIC)
    }
}
convenience init(collectionVewFlowLayout : UICollectionViewFlowLayout, originalDataBlock: @escaping XYRearrangeOriginaDataBlock, newDataBlock:  @escaping XYRearrangeNewDataBlock) {
    self.init()


    let blockContainer: BlockContainer = BlockContainer()
    blockContainer.rearrangeNewDataBlock = newDataBlock
    blockContainer.rearrangeOriginaDataBlock = originalDataBlock
    self.newDataBlock = blockContainer
}

`` `

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.