Czy istnieje sposób na ustawienie powiązanych obiektów w Swift?

82

Wychodząc z celu C możesz wywołać funkcję objc_setAssociatedObjectmiędzy 2 obiektami, aby utrzymywały odniesienie, co może być przydatne, jeśli w czasie wykonywania nie chcesz, aby obiekt został zniszczony, dopóki jego odwołanie nie zostanie również usunięte. Czy Swift ma coś podobnego do tego?

amleszk
źródło
3
Możesz użyć objc_setAssociatedObjectze Swift: developer.apple.com/library/prerelease/ios/documentation/Swift/…
jtbandes.
DŁUGI PRZYKŁAD Z PEŁNYM
KODEM

Odpowiedzi:

131

Oto prosty, ale kompletny przykład zaczerpnięty z odpowiedzi jckartera .

Pokazuje, jak dodać nową właściwość do istniejącej klasy. Robi to poprzez zdefiniowanie obliczonej właściwości w bloku rozszerzenia. Obliczona właściwość jest przechowywana jako skojarzony obiekt:

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

EDYTOWAĆ:

Jeśli potrzebujesz obsługiwać pobieranie wartości niezainicjowanej właściwości i unikać otrzymywania błędu unexpectedly found nil while unwrapping an Optional value, możesz zmodyfikować getter w następujący sposób:

    get {
        return objc_getAssociatedObject(self, &AssociatedObjectHandle) as? String ?? ""
    }
Klaas
źródło
Czy udało Ci się ustawić funkcję zgodną z aliasem typu w ten sposób?
Morkrom
1
@PhamHoan Int to typ natywny Swift. Spróbuj zamiast tego użyć NSNumber.
Klaas,
1
@PhamHoan Dodałem rozwiązanie umożliwiające uzyskanie niezainicjowanego obiektu.
Klaas
1
@Jawwad Myślę, że to nie ma znaczenia. UInt8Pochodzi z połączonej pierwotnej nici.
Klaas,
1
hej @Jacky - co sprawia, że ​​mówisz, że to powinno być KOPIUJ ... daj nam znać!
Fattie
31

Rozwiązanie obsługuje również wszystkie typy wartości , a nie tylko te, które są mostkowane automagicznie , takie jak String, Int, Double itp.

Owijki

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

Możliwe rozszerzenie klasy (przykład użycia)

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)
            }
        }
    }
}
HepaKKes
źródło
Czy możesz mi wyjaśnić, co oznaczają te flagi stowarzyszenia? W rzeczywistości próbuję przewijać pasek nawigacyjny za pomocą scrollView, więc przechowuję scrollView w rozszerzeniu. Których flag powinienem używać?
meteors
1
Próbuję również przechowywać tablicę [AnyObject] przy użyciu tej metody, ale funkcja pobierająca zawsze zwraca mi nil. W jakiś inny sposób, jeśli z get nie zwraca poprawnie wartości.
meteors
Rodzaj polityki asocjacyjnej, którego zdecydujesz się użyć, zależy od Ciebie, zależy to od Twojego programu. Aby uzyskać więcej informacji, radzę zapoznać się z tym artykułem, nshipster.com/associated-objects . Próbowałem przechowywać tablice, wydaje się, że działa dobrze. Nawiasem mówiąc, której wersji Swift używasz?
HepaKKes
To jest genialne. W przypadku Swift 2.3 ostatecznie przeniosłem Generics z windy i użyłem Anyzamiast tego. Myślę, że opublikuję artykuł na ten temat
Dan Rosenstark,
5
W Swift 3 stało się to o wiele łatwiejsze. Ale tak czy inaczej, oto mój artykuł oparty na twojej odpowiedzi tutaj: yar2050.com/2016/11/associated-object-support-for-swift-23.html
Dan Rosenstark
5

Oczywiście działa to tylko z obiektami Objective-C. Po trochę zabawie przy tym, oto jak wykonywać połączenia w Swift:

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)
}
DarkDust
źródło
Próbowałem tej metody, ale wydaje się, że mój obiekt wartości został natychmiast zwolniony, gdy nie ma innych odniesień do niego z kodu szybkiego. Moim obiektem docelowym jest SKNode, a mój obiekt wartości to klasa Swift, która rozszerza NSObject. Czy to powinno działać?
nacross
W rzeczywistości wydaje się, że deinit jest wywoływana podczas objc_setAssociatedObject, mimo że obiekt został właśnie utworzony i jest używany w dalszej części metody.
nacross
4

Aktualizacja w Swift 3.0 Na przykład jest to 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)
        }
    }
}
płomień 3
źródło
3

Napisałem nowoczesnego pomocnika, który wymaga Swift 5.1.

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

Możesz być zaskoczony, że obsługuje on nawet struktury Swift za darmo.

Skojarzenie szybkiej struktury

BB9z
źródło
2

Klaas odpowiada tylko dla 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
RhodanV5500
źródło
0

Po prostu dodaj #import <objc/runtime.h>swój plik nagłówkowy brindging, aby uzyskać dostęp do objc_setAssociatedObject w szybkim kodzie

jaumard
źródło
18
lub możesz po prostu import ObjectiveCw Swift
newacct
0

Powyższy znajomy odpowiedział na Twoje pytanie, ale jeśli dotyczy ono właściwości zamknięcia, pamiętaj:

`` ''

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
}

`` ''

Alpface
źródło