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?


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

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

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

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

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.


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.


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

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

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.


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.


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)

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 .

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

}

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

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

}

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

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 )

-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

    }
}

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
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.