Jak przechowywać właściwości w Swift, tak samo jak w Objective-C?

132

Przełączam aplikację z Objective-C na Swift, w której mam kilka kategorii z przechowywanymi właściwościami, na przykład:

@interface UIView (MyCategory)

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

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

@end

Ponieważ rozszerzenia Swift nie akceptują przechowywanych właściwości, takich jak te, nie wiem, jak zachować tę samą strukturę, co kod Objc. Przechowywane właściwości są naprawdę ważne dla mojej aplikacji i uważam, że firma Apple musiała opracować jakieś rozwiązanie do robienia tego w Swift.

Jak powiedział jou, w rzeczywistości szukałem powiązanych obiektów, więc zrobiłem (w innym kontekście):

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

Ale dostaję EXC_BAD_ACCESS, gdy robię:

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

Jakieś pomysły?

Marcos Duarte
źródło
11
Kategorie klas celu-C również nie mogą definiować zmiennych instancji, więc jak zrealizowałeś te właściwości?
Martin R
Nie jestem pewien, dlaczego masz zły dostęp, ale Twój kod nie powinien działać. Przekazujesz różne wartości do setAssociateObject i getAssociatedObject. Używanie łańcucha „shapeLayer” jako klucza jest błędne, to wskaźnik (w rzeczywistości jest to adres) jest kluczem, a nie tym, na co wskazuje. Dwa identyczne ciągi znajdujące się pod różnymi adresami to dwa różne klucze. Przejrzyj odpowiedź Jou i zwróć uwagę, jak zdefiniował xoAssociationKey jako zmienną globalną, więc jest to ten sam klucz / wskaźnik podczas ustawiania / pobierania.
SafeFastExpressive

Odpowiedzi:

50

Interfejs API obiektów powiązanych jest nieco kłopotliwy w użyciu. Możesz usunąć większość boilerplate'u za pomocą klasy pomocniczej.

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

Pod warunkiem, że możesz „dodać” właściwość do klasy goal-c w bardziej czytelny sposób:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Co do rozwiązania:

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 }
    }
}
Wojciech Nagrodzki
źródło
Ok, co zrobić, jeśli chcę przechowywać Int, Bool itd.?
Vyachaslav Gerchicov
1
Nie ma możliwości bezpośredniego przechowywania typów Swift za pośrednictwem powiązań obiektów. Możesz zapisać np. NSNumberLub NSValuei napisać dodatkową parę akcesorów, które byłyby pożądanego typu (Int, Bool, itp.).
Wojciech Nagrodzki
1
OSTRZEŻENIE To nie działa dla struktur, ponieważ biblioteka wykonawcza Objective-c obsługuje tylko klasy zgodne zNSObjectProtocol
Charlton Provatas
2
@CharltonProvatas Nie można używać struktur z tym API, nie są one zgodne z protokołem AnyObject.
Wojciech Nagrodzki
@VyachaslavGerchicov [String]?jest strukturą, to jest ten sam przypadek co dla Int, Bool. Możesz użyć NSArraydo przechowywania kolekcji NSStringinstancji.
Wojciech Nagrodzki
184

Podobnie jak w przypadku Objective-C, nie można dodawać właściwości przechowywanych do istniejących klas. Jeśli rozszerzasz klasę Objective-C ( UIViewzdecydowanie jedną), nadal możesz używać Skojarzonych obiektów do emulacji przechowywanych właściwości:

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

Klucz asocjacji to wskaźnik, który powinien być unikalny dla każdego powiązania. W tym celu tworzymy prywatną zmienną globalną i używamy jej adresu pamięci jako klucza z &operatorem. Zobacz Używanie języka Swift z kakao i Objective-C, aby uzyskać więcej informacji na temat obsługi wskaźników w języku Swift.

AKTUALIZACJA dla Swift 2 i 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)
        }
    }
}

AKTUALIZACJA dla Swift 4

W Swift 4 jest to znacznie prostsze. Struktura Holder będzie zawierała prywatną wartość, którą nasza obliczona własność ujawni światu, dając w zamian iluzję zachowania własności przechowywanej.

Źródło

extension UIViewController {
    struct Holder {
        static var _myComputedProperty:Bool = false
    }
    var myComputedProperty:Bool {
        get {
            return Holder._myComputedProperty
        }
        set(newValue) {
            Holder._myComputedProperty = newValue
        }
    }
}
jou
źródło
2
@Yar Nie wiedziałem o objc_AssociationPolicy, dzięki! Zaktualizowałem odpowiedź
jou
2
W Swift2 musisz użyć objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom
1
@SimplGy, dodaj swoją zmianę jako dodatkową odpowiedź, zamiast edytować kod w odpowiedzi innej osoby.
JAL
13
Rozwiązanie Swift4 nie działa, jeśli chcesz mieć więcej niż 1 wystąpienie UIViewController, które używa właściwości z rozszerzenia. Sprawdź aktualizacje w źródle.
Iur
10
Przykład Swift 4 nie działa z wieloma instancjami i prawdopodobnie nie powinien tu być.
Colin Cornaby
36

Więc myślę, że znalazłem metodę, która działa czyściej niż te powyżej, ponieważ nie wymaga żadnych zmiennych globalnych. Mam to stąd: http://nshipster.com/swift-objc-runtime/

Istota jest taka, że ​​używasz takiej struktury:

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 dla 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)
    }
}
AlexK
źródło
@AKWeblS Zaimplementowałem kod podobny do twojego, ale podczas aktualizacji do Swift 2.0 pojawia się błąd nie może wywołać inicjatora dla typu „UInt” z listą argumentów typu „objc_AssociationPolicy)”. Kod w następnym komentarzu
user2363025
Jak zaktualizować do Swift 2.0? var postDescription: Ciąg? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) as? Ciąg} zestaw {czy pozwolić nowaWartość = nowaWartość {objc_setAssociatedObject (samodzielne i AssociatedKeys.postDescription, nowaWartość jak NSString ?, Uint (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025
1
nie działa: zmienna statyczna oznacza, że ​​wartość właściwości jest taka sama dla wszystkich instancji
Fry
@fry - to nadal działa dla mnie. Tak, klucz jest statyczny, ale jest powiązany z określonym obiektem, sam obiekt nie jest globalny.
AlexK
16

Rozwiązanie wskazane przez jou nie obsługuje typów wartości , działa to również dobrze z nimi

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

To naprawdę świetne rozwiązanie, chciałem dodać kolejny przykład użycia, który zawierał struktury i wartości, które nie są opcjami. Można również uprościć wartości AssociatedKey.

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)
        }
    }
}
HepaKKes
źródło
A jak używać klasy Lifted?
3lvis
Dlaczego tego potrzebujesz? Po co?
HepaKKes
Chodziło mi o to, jak ogólnie z tego korzystasz, dzięki za pomoc :) Czy możesz dodać przykład „Jak używać”?
3lvis
1
Przykład „Jak używać” zaczynałby się tuż pod nagłówkiem „Możliwe rozszerzenie klasy”. Myślę, że użytkownicy nie muszą wiedzieć, jak używać liftmetody i Liftedklasy, powinni jednak używać getAssociatedObject& setAssociatedObjectfunkcji. Dla większej przejrzystości dodam „Przykład użycia” w nawiasach obok nagłówka.
HepaKKes
1
To z pewnością najlepsze rozwiązanie, szkoda, że ​​nie zostało to dobrze wyjaśnione. Działa z klasami, strukturami i innymi typami wartości. Właściwości też nie muszą być opcjonalne. Dobra robota.
picciano
13

Nie możesz definiować kategorii (rozszerzeń Swift) z nowym magazynem; wszelkie dodatkowe właściwości muszą być obliczane, a nie przechowywane. Składnia działa dla celu C, ponieważ @propertyw kategorii zasadniczo oznacza „Zapewnię metodę pobierającą i ustawiającą”. W języku Swift musisz zdefiniować je samodzielnie, aby uzyskać obliczoną właściwość; coś jak:

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

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

Powinno działać dobrze. Pamiętaj, że nie możesz przechowywać nowych wartości w seterze, pracować tylko z istniejącym stanem dostępnej klasy.

Adam Wright
źródło
7

Moje 0,02 $. Ten kod jest napisany w języku 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)
            }
        }
    }
}

Wypróbowałem wiele rozwiązań i stwierdziłem, że jest to jedyny sposób na rozszerzenie klasy o dodatkowe parametry zmiennych.

Amr Hossam
źródło
Wygląda ciekawie, ale type 'AssociatedKeys' cannot be defined within a protocol extension
odmowa
6

Dlaczego warto polegać na środowisku wykonawczym objc? Nie rozumiem. Używając czegoś podobnego do poniższego, uzyskasz prawie identyczne zachowanie właściwości przechowywanej, używając tylko czystego podejścia Swift :

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
        }
    }
}
valvoline
źródło
1
Sprytny! Jedną z możliwych wad tego podejścia jest zarządzanie pamięcią. Po cofnięciu alokacji obiektu hosta jego właściwość będzie nadal istnieć w słowniku, co może być potencjalnie kosztowne w przypadku użycia pamięci, jeśli jest używana z wieloma obiektami.
Charlton Provatas
Prawdopodobnie możemy przechowywać statyczną listę zmiennych opakowań, które zawierają słabe odniesienie do obiektów (self). I usuń wpis ze słownika _myComputedProperty, a także statyczną listę zmiennych opakowań w metodzie didSet programu Wrapper (gdy obiekt zostanie ponownie przydzielony, wywoływana jest funkcja didSet wewnątrz naszej klasy Wrapper, ponieważ nasze słabe odniesienie otrzymuje nową wartość z nil).
Arjuna
5

Wolę tworzyć kod w czystym języku Swift i nie polegać na dziedzictwie Objective-C. Z tego powodu napisałem czyste rozwiązanie Swifta, które ma dwie zalety i dwie wady.

Zalety:

  1. Czysty kod Swift

  2. Działa na klasach i uzupełnieniach, a dokładniej na Anyobiekcie

Niedogodności:

  1. Kod powinien wywołać metodę, willDeinit()aby zwolnić obiekty połączone z konkretną instancją klasy, aby uniknąć wycieków pamięci

  2. Nie można utworzyć rozszerzenia bezpośrednio do UIView dla tego dokładnego przykładu, ponieważ var framejest rozszerzeniem do UIView, a nie częścią klasy.

EDYTOWAĆ:

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
    }
}
vedrano
źródło
4
To nie działa, ponieważ rozszerzeniePropertyStorage jest wspólne dla wszystkich wystąpień. Jeśli ustawisz wartość dla jednego wystąpienia, ustawisz wartość dla wszystkich wystąpień.
picciano
Wojna nie jest dobra, ponieważ psuje funkcje. Teraz jest lepiej i działa zgodnie z przeznaczeniem.
vedrano
@picciano extensionPropertyStorage jest współdzielone ze wszystkimi instancjami zgodnie z projektem. Jest to zmienna globalna (słownik), która najpierw usuwa odwołania do instancji UILabel ( NSObject), a następnie do jej właściwości ( [String: Any]).
vedrano
@picciano Myślę, że to działa w przypadku classnieruchomości;)
Nicolas Miari
framejest właściwością instancji. Skąd się bierze własność klasy?
vedrano
3

Za pomocą kategorii Obj-c można dodawać tylko metody, a nie zmienne instancji.

W swoim przykładzie użyłeś @property jako skrótu do dodawania deklaracji metod pobierających i ustawiających. Nadal musisz wdrożyć te metody.

Podobnie w Swift możesz dodać rozszerzenia użycia, aby dodać metody instancji, właściwości obliczone itp., Ale nie właściwości przechowywane.

Mike Pollard
źródło
2

Otrzymuję również problem EXC_BAD_ACCESS. Wartość w objc_getAssociatedObject()i objc_setAssociatedObject()powinna być obiektem. I objc_AssociationPolicypowinna być zgodna z Object.

RuiKQ
źródło
3
To naprawdę nie odpowiada na pytanie. Jeśli masz inne pytanie, możesz je zadać, klikając Zadaj pytanie . Możesz także dodać nagrodę, aby zwrócić większą uwagę na to pytanie, gdy zdobędziesz wystarczającą reputację . - Z recenzji
AtheistP3ace
@ AtheistP3ace Tak mi przykro. Próbuję dodać komentarz do pytania, ale nie mam wystarczającej reputacji. Więc staram się odpowiedzieć na pytanie.
RuiKQ
2

Próbowałem użyć objc_setAssociatedObject, jak wspomniano w kilku odpowiedziach tutaj, ale po kilku niepowodzeniach cofnąłem się i zdałem sobie sprawę, że nie ma powodu, aby tego potrzebować. Korzystając z kilku pomysłów tutaj, wymyśliłem ten kod, który po prostu przechowuje tablicę moich dodatkowych danych (w tym przykładzie MyClass) indeksowanych przez obiekt, z którym chcę je powiązać:

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)
Dan
źródło
czy masz pomysł, jak automatycznie wyczyścić extraData, aby nie powodowało wycieków pamięci?
DoubleK
Właściwie nie używałem tego z widokami, w moim przypadku zawsze tworzę tylko skończoną liczbę obiektów w klasie, której używałem, więc tak naprawdę nie brałem pod uwagę wycieków pamięci. Nie przychodzi mi do głowy automatyczny sposób na zrobienie tego, ale przypuszczam, że w seterze powyżej możesz dodać logikę, aby usunąć element, jeśli value == nil, a następnie ustawić wartość na zero, gdy nie potrzebujesz już obiektu, lub może w didReceiveMemoryWarning czy coś.
Dan
Tnx @Dan, użyłem viewWillDisapper, aby ustawić wartość na zero i działa dobrze, teraz wywoływana jest deinit i wszystko działa dobrze. Tnx za opublikowanie tego rozwiązania
DoubleK
2

Oto uproszczone i bardziej wyraziste rozwiązanie. Działa zarówno w przypadku typów wartości, jak i typów referencyjnych. Podejście do podnoszenia jest zaczerpnięte z odpowiedzi @HepaKKes.

Kod stowarzyszenia:

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

Przykład użycia:

1) Utwórz rozszerzenie i skojarz z nim właściwości. Użyjmy zarówno właściwości value, jak i typu referencyjnego.

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) Teraz możesz używać zwykłych właściwości:

    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

Więcej szczegółów:

  1. Podnoszenie jest potrzebne do pakowania typów wartości.
  2. Zachowane zostaje domyślne zachowanie skojarzonego obiektu. Jeśli chcesz dowiedzieć się więcej o powiązanych obiektach, polecam przeczytanie tego artykułu .
Vadim Bulavin
źródło
1

Kolejny przykład użycia obiektów powiązanych z Objective-C i obliczonych właściwości dla Swift 3 i 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
                )
            }
        }
    }

}
abdullahselek
źródło
1

Jeśli chcesz ustawić niestandardowy atrybut ciągu na UIView, tak zrobiłem to w Swift 4

Utwórz rozszerzenie 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
    }
}

Aby użyć tego rozszerzenia

let key = "COLOR"

let redView = UIView() 

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

// To read
print(redView.stringValueFor(key: key)) // Optional("Red")
Kelvin Fok
źródło
1

Co powiesz na przechowywanie mapy statycznej do klasy, która rozszerza się w ten sposób:

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

}
Ashkan Ghodrat
źródło
1
dlaczego ta odpowiedź nie ma pozytywnych głosów. W końcu sprytne rozwiązanie.
doJeevan
To działa, ale myślę, że to podejście jest wadliwe, jak stwierdzono tutaj medium.com/@valv0/…
Teffi
0

Próbowałem przechowywać właściwości przy użyciu objc_getAssociatedObject, objc_setAssociatedObject, bez powodzenia. Moim celem było stworzenie rozszerzenia dla UITextField, aby sprawdzić długość znaków wprowadzania tekstu. Poniższy kod działa dobrze dla mnie. Mam nadzieję, że to komuś pomoże.

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)
    }
}
Vadims Krutovs
źródło
Globalne zmienne prywatne? Czy zdajesz sobie z tego sprawę _mini _maxsą one globalne i będą takie same we wszystkich wystąpieniach UITextField? Nawet jeśli to zadziała, ta odpowiedź nie ma związku, ponieważ Marcos pyta o zmienne instancji .
kelin
Tak, masz rację, to nie działa we wszystkich przypadkach. Naprawiono przez przechowywanie wartości minimalnej i maksymalnej w strukturze. Przepraszam za nie na temat.
Vadims Krutovs
0

Oto alternatywa, która również działa

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 )
Rondinelli Morais
źródło
-1

Uważam, że to rozwiązanie jest bardziej praktyczne

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

}

... potem w swoim kodzie

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}
Giuseppe Mazzilli
źródło
To jest właściwy sposób, aby to zrobić - ale tak naprawdę nie jest to przechowywana właściwość w rozszerzeniu, jak zadaje pytanie.
UKDataGeek