Comment avoir des propriétés stockées dans Swift, de la même manière que j'avais sur Objective-C?

132

Je passe d'une application d'Objective-C à Swift, dont j'ai quelques catégories avec des propriétés stockées, par exemple:

@interface UIView (MyCategory)

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

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

@end

Comme les extensions Swift n'acceptent pas les propriétés stockées comme celles-ci, je ne sais pas comment maintenir la même structure que le code Objc. Les propriétés stockées sont vraiment importantes pour mon application et je pense qu'Apple a dû créer une solution pour le faire dans Swift.

Comme l'a dit jou, ce que je cherchais était en fait d'utiliser des objets associés, alors j'ai fait (dans un autre contexte):

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

Mais j'obtiens un EXC_BAD_ACCESS en faisant:

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

Des idées?

Marcos Duarte
la source
11
Les catégories de classe Objective-C ne peuvent pas non plus définir de variables d'instance, alors comment avez-vous réalisé ces propriétés?
Martin R
Je ne sais pas pourquoi vous obtenez un mauvais accès, mais votre code ne devrait pas fonctionner. Vous transmettez des valeurs différentes à setAssociateObject et getAssociatedObject. Utiliser la chaîne "shapeLayer" comme clé est une erreur, c'est le pointeur (c'est l'adresse en fait) qui est la clé, pas vers quoi il pointe. Deux chaînes identiques résidant à des adresses différentes sont deux clés différentes. Passez en revue la réponse de Jou et remarquez comment il a défini xoAssociationKey comme une variable globale, donc c'est la même clé / pointeur lors de la définition / de l'obtention.
SafeFastExpressive

Réponses:

50

L'API des objets associés est un peu lourde à utiliser. Vous pouvez supprimer la plupart des passe-partout avec une classe d'assistance.

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

À condition que vous puissiez "ajouter" une propriété à la classe objective-c d'une manière plus lisible:

extension SomeType {

    private static let association = ObjectAssociation<NSObject>()

    var simulatedProperty: NSObject? {

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

Quant à la solution:

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
la source
Ok que faire si je veux stocker Int, Bool et etc?
Vyachaslav Gerchicov
1
Il n'est pas possible de stocker des types Swift directement via une association d'objets. Vous pouvez stocker par exemple NSNumberou NSValueet écrire des paires d'accesseurs supplémentaires qui seraient des types souhaités (Int, Bool, etc.).
Wojciech Nagrodzki
1
AVERTISSEMENT Cela ne fonctionne pas pour les structures, car la bibliothèque d'exécution objective-c ne prend en charge que les classes conformes àNSObjectProtocol
Charlton Provatas
2
@CharltonProvatas Il n'est pas possible d'utiliser des structures avec cette API, elles ne sont pas conformes au protocole AnyObject.
Wojciech Nagrodzki
@VyachaslavGerchicov [String]?est une structure, c'est le même cas que pour Int, Bool. Vous pouvez utiliser NSArraypour conserver une collection d' NSStringinstances.
Wojciech Nagrodzki
184

Comme dans Objective-C, vous ne pouvez pas ajouter de propriété stockée aux classes existantes. Si vous étendez une classe Objective-C (en UIViewest certainement une), vous pouvez toujours utiliser les objets associés pour émuler les propriétés stockées:

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

La clé d'association est un pointeur qui doit être l'unique pour chaque association. Pour cela, nous créons une variable globale privée et utilisons son adresse mémoire comme clé avec l' &opérateur. Voir Utilisation de Swift avec Cocoa et Objective-C pour plus de détails sur la manière dont les pointeurs sont gérés dans Swift.

MISE À JOUR pour Swift 2 et 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)
        }
    }
}

MISE À JOUR pour Swift 4

Dans Swift 4, c'est beaucoup plus simple. La structure Holder contiendra la valeur privée que notre propriété calculée exposera au monde, donnant l'illusion d'un comportement de propriété stockée à la place.

La source

extension UIViewController {
    struct Holder {
        static var _myComputedProperty:Bool = false
    }
    var myComputedProperty:Bool {
        get {
            return Holder._myComputedProperty
        }
        set(newValue) {
            Holder._myComputedProperty = newValue
        }
    }
}
jou
la source
2
@Yar Je ne savais pas objc_AssociationPolicy, merci! J'ai mis à jour la réponse
joué le
2
Dans Swift2, vous devez utiliser objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN
Tom
1
@SimplGy veuillez ajouter votre modification comme réponse supplémentaire plutôt que de modifier votre code dans la réponse de quelqu'un d'autre.
JAL
11
La solution Swift4 ne fonctionne pas si vous souhaitez avoir plus d'une instance de UIViewController qui utilise la propriété de l'extension. Veuillez vérifier les mises à jour dans la source.
iur
9
L'exemple Swift 4 ne fonctionne pas avec plusieurs instances et ne devrait probablement pas être ici.
Colin Cornaby
36

Je pense donc avoir trouvé une méthode plus propre que celles ci-dessus car elle ne nécessite aucune variable globale. Je l'ai obtenu d'ici: http://nshipster.com/swift-objc-runtime/

L'essentiel est que vous utilisez une structure comme ceci:

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

MISE À JOUR pour 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
la source
@AKWeblS J'ai implémenté un code comme le vôtre, mais lors de la mise à jour vers swift 2.0, je reçois l'erreur ne peut pas invoquer l'initialiseur pour le type «UInt» avec une liste d'arguments de type «objc_AssociationPolicy)». Code dans le prochain commentaire
user2363025
Comment mettre à jour pour Swift 2.0? var postDescription: Chaîne? {get {return objc_getAssociatedObject (self, & AssociatedKeys.postDescription) comme? String} set {si laisser nouvelleValeur = {nouvelleValeur objc_setAssociatedObject (auto, et AssociatedKeys.postDescription, newValue comme NSString ?, UInt (objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC))}}}
user2363025
1
ne fonctionne pas: var statique signifie que la valeur de la propriété est la même pour toutes les instances
Fry
@fry - cela continue de fonctionner pour moi. Oui, la clé est statique, mais associée à un objet spécifique, l'objet lui-même n'est pas global.
AlexK
15

La solution indiquée par jou ne prend pas en charge les types valeur , cela fonctionne bien avec eux aussi

Emballages

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

Une extension de classe possible (exemple d'utilisation):

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

C'est vraiment une excellente solution, je voulais ajouter un autre exemple d'utilisation qui incluait des structures et des valeurs qui ne sont pas optionnelles. En outre, les valeurs AssociatedKey peuvent être simplifiées.

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
la source
Et comment utiliser la classe Lifted?
3lvis
Pourquoi avez-vous besoin de cela? Pourquoi?
HepaKKes
Je voulais dire comment utilisez-vous cela en général, merci pour votre aide :) Pouvez-vous ajouter un exemple "Comment utiliser", s'il vous plaît?
3lvis
1
L'exemple "Comment utiliser" commencerait juste en dessous de l'en-tête "Une extension de classe possible". Je ne pense pas que les utilisateurs aient besoin de savoir comment utiliser la liftméthode et la Liftedclasse, ils devraient cependant utiliser getAssociatedObject& setAssociatedObjectfunctions. J'ajouterai "Exemple d'utilisation" entre parenthèses à côté de l'en-tête par souci de clarté.
HepaKKes
1
C'est certainement la meilleure solution, dommage qu'elle ne soit pas très bien expliquée. Il fonctionne avec des classes, des structures et d'autres types de valeur. Les propriétés ne doivent pas non plus être optionnelles. Bon travail.
picciano
13

Vous ne pouvez pas définir de catégories (extensions Swift) avec un nouveau stockage; toutes les propriétés supplémentaires doivent être calculées plutôt que stockées. La syntaxe fonctionne pour Objective C car @propertydans une catégorie signifie essentiellement "Je fournirai le getter et le setter". Dans Swift, vous devrez les définir vous-même pour obtenir une propriété calculée; quelque chose comme:

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

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

Cela devrait fonctionner correctement. N'oubliez pas que vous ne pouvez pas stocker de nouvelles valeurs dans le setter, ne travaillez qu'avec l'état de classe disponible existant.

Adam Wright
la source
7

Mon 0,02 $. Ce code est écrit en 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)
            }
        }
    }
}

J'ai essayé de nombreuses solutions et j'ai trouvé que c'était le seul moyen d'étendre réellement une classe avec des paramètres variables supplémentaires.

Amro Shafie
la source
type 'AssociatedKeys' cannot be defined within a protocol extension
Ça a l'air
6

Pourquoi s'appuyer sur le runtime objc? Je ne comprends pas. En utilisant quelque chose comme ce qui suit, vous obtiendrez presque le même comportement d'une propriété stockée, en utilisant uniquement une approche Swift pure :

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
la source
1
Intelligent! Un inconvénient possible avec cette approche est la gestion de la mémoire. Une fois l'objet hôte désalloué, sa propriété existera toujours dans le dictionnaire, ce qui pourrait potentiellement coûter cher en mémoire si elle est utilisée avec un grand nombre d'objets.
Charlton Provatas
Nous pouvons probablement contenir une variable de liste statique de wrappers contenant une référence faible aux objets (self). Et supprimez l'entrée du dictionnaire _myComputedProperty ainsi que la variable de liste statique des wrappers dans la méthode didSet de Wrapper (lorsque l'objet est réalloué, didSet dans notre classe Wrapper est appelé car notre référence faible est définie sur une nouvelle valeur avec nil).
Arjuna
5

Je préfère faire du code en Swift pur et ne pas me fier à l'héritage Objective-C. Pour cette raison, j'ai écrit une solution Swift pure avec deux avantages et deux inconvénients.

Avantages:

  1. Code Swift pur

  2. Fonctionne sur les classes et les complétions ou plus spécifiquement sur l' Anyobjet

Désavantages:

  1. Le code doit appeler une méthode willDeinit()pour libérer des objets liés à une instance de classe spécifique pour éviter les fuites de mémoire

  2. Vous ne pouvez pas faire d'extension directement à UIView pour cet exemple exact car cette var frameextension à UIView ne fait pas partie d'une classe.

ÉDITER:

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
la source
4
Cela ne fonctionne pas car extensionPropertyStorage est partagé entre toutes les instances. Si vous définissez une valeur pour une instance, vous définissez la valeur pour toutes les instances.
picciano le
Ce n'est pas une bonne guerre car gâcher les fonctions. Maintenant, c'est mieux et fonctionne comme prévu.
vedrano
@picciano extensionPropertyStorage est partagé avec toutes les instances par conception. C'est la variable globale (Dictionary) qui dé-référence d'abord l'instance UILabel ( NSObject) puis sa propriété ( [String: Any]).
vedrano
@picciano Je suppose que cela fonctionne pour les classpropriétés;)
Nicolas Miari
frameest une propriété d'instance. D'où vient la propriété de classe?
vedrano
3

Avec les catégories Obj-c, vous ne pouvez ajouter que des méthodes, pas des variables d'instance.

Dans votre exemple, vous avez utilisé @property comme raccourci pour ajouter des déclarations de méthode getter et setter. Vous devez toujours implémenter ces méthodes.

De même, dans Swift, vous pouvez ajouter des extensions d'utilisation pour ajouter des méthodes d'instance, des propriétés calculées, etc. mais pas des propriétés stockées.

Mike Pollard
la source
2

J'obtiens également un problème EXC_BAD_ACCESS. La valeur dans objc_getAssociatedObject()et objc_setAssociatedObject()devrait être un objet. Et le objc_AssociationPolicydevrait correspondre à l'objet.

RuiKQ
la source
3
Cela ne répond pas vraiment à la question. Si vous avez une autre question, vous pouvez la poser en cliquant sur Poser une question . Vous pouvez également ajouter une prime pour attirer davantage l'attention sur cette question une fois que vous avez suffisamment de réputation . - De l'avis
AtheistP3ace
@ AtheistP3ace Je suis vraiment désolé pour ça. J'essaye d'ajouter un commentaire à la question, mais je n'ai pas assez de réputation. J'essaye donc de répondre à la question.
RuiKQ
2

J'ai essayé d'utiliser objc_setAssociatedObject comme mentionné dans quelques-unes des réponses ici, mais après avoir échoué plusieurs fois, j'ai pris du recul et j'ai réalisé qu'il n'y avait aucune raison pour laquelle j'en avais besoin. En empruntant à quelques-unes des idées ici, j'ai trouvé ce code qui stocke simplement un tableau de toutes mes données supplémentaires (MyClass dans cet exemple) indexées par l'objet auquel je veux l'associer:

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
la source
avez-vous une idée comment effacer automatiquement extraData afin qu'il ne provoque pas de fuites de mémoire?
DoubleK
Je n'utilisais pas réellement cela avec des vues, dans mon cas, je n'instancie qu'un nombre fini d'objets dans la classe que j'utilisais, donc je n'ai pas vraiment envisagé les fuites de mémoire. Je ne peux pas penser à un moyen automatique de le faire, mais je suppose que dans le setter ci-dessus, vous pouvez ajouter une logique pour supprimer l'élément si valeur == nil, puis définir la valeur sur nil lorsque vous n'avez plus besoin de l'objet, ou peut-être dans didReceiveMemoryWarning ou quelque chose comme ça.
Dan
Tnx @Dan, j'ai utilisé viewWillDisapper pour définir la valeur sur nil et cela fonctionne bien, maintenant deinit est appelé et tout fonctionne bien. Tnx pour avoir publié cette solution
DoubleK
2

Voici une solution simplifiée et plus expressive. Cela fonctionne pour les types valeur et référence. L'approche du levage est tirée de la réponse @HepaKKes.

Code d'association:

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

Exemple d'utilisation:

1) Créez une extension et associez-y des propriétés. Utilisons à la fois les propriétés de type valeur et référence.

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) Vous pouvez maintenant utiliser tout comme les propriétés normales:

    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

Plus de détails:

  1. Le levage est nécessaire pour emballer les types de valeur.
  2. Le comportement par défaut de l'objet associé est conserver. Si vous souhaitez en savoir plus sur les objets associés, je vous recommande de consulter cet article .
Vadim Bulavin
la source
1

Un autre exemple d'utilisation des objets associés Objective-C et des propriétés calculées pour Swift 3 et 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
la source
1

si vous cherchez à définir un attribut de chaîne personnalisé sur un UIView, voici comment je l'ai fait sur Swift 4

Créer une extension 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
    }
}

Pour utiliser cette extension

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
la source
1

Que diriez-vous de stocker une carte statique dans une classe qui s'étend comme ceci:

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
la source
1
pourquoi cette réponse n'a pas de votes positifs. Solution intelligente après tout.
parJeevan
Cela fonctionne mais je pense que cette approche est imparfaite comme indiqué ici medium.com/@valv0/…
Teffi
0

J'ai essayé de stocker des propriétés en utilisant objc_getAssociatedObject, objc_setAssociatedObject, sans aucune chance. Mon objectif était de créer une extension pour UITextField, pour valider la longueur des caractères d'entrée de texte. Le code suivant fonctionne très bien pour moi. J'espère que cela aidera quelqu'un.

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
la source
Variables privées globales? Vous en rendez-vous compte _minet _maxsont globaux et seront les mêmes dans toutes les instances de UITextField? Même si cela fonctionne pour vous, cette réponse n'est pas liée car Marcos pose des questions sur les variables d' instance .
kelin
Oui, vous avez raison, cela ne fonctionne pas pour toutes les instances. I Corrigé en stockant les valeurs min et max dans struct. Désolé pour hors sujet.
Vadims Krutovs
0

Voici une alternative qui fonctionne aussi

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
la source
-1

J'ai trouvé cette solution plus pratique

MISE À JOUR pour 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)
    }

}

... puis dans votre code

class gameController: UIViewController {

    @IBOutlet var game: gameClass!

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

    }
}
Giuseppe Mazzilli
la source
C'est la bonne façon de le faire - mais pas vraiment une propriété stockée dans l'extension comme le demande la question.
UKDataGeek