Meilleur moyen d'implémenter Enums avec Core Data


109

Quelle est la meilleure façon de lier des entités Core Data à des valeurs d'énumération afin que je puisse attribuer une propriété de type à l'entité? En d'autres termes, j'ai une entité appelée Itemavec une itemTypepropriété que je veux être liée à une énumération, quelle est la meilleure façon de procéder.

Réponses:


130

Vous devrez créer des accesseurs personnalisés si vous souhaitez limiter les valeurs à une énumération. Donc, vous déclarez d'abord une énumération, comme ceci:

typedef enum {
    kPaymentFrequencyOneOff = 0,
    kPaymentFrequencyYearly = 1,
    kPaymentFrequencyMonthly = 2,
    kPaymentFrequencyWeekly = 3
} PaymentFrequency;

Ensuite, déclarez les getters et les setters pour votre propriété. C'est une mauvaise idée de remplacer ceux existants, car les accesseurs standard attendent un objet NSNumber plutôt qu'un type scalaire, et vous rencontrerez des problèmes si quelque chose dans les liaisons ou les systèmes KVO essaie d'accéder à votre valeur.

- (PaymentFrequency)itemTypeRaw {
    return (PaymentFrequency)[[self itemType] intValue];
}

- (void)setItemTypeRaw:(PaymentFrequency)type {
    [self setItemType:[NSNumber numberWithInt:type]];
}

Enfin, vous devez implémenter + keyPathsForValuesAffecting<Key>afin d'obtenir des notifications KVO pour itemTypeRaw lorsque itemType change.

+ (NSSet *)keyPathsForValuesAffectingItemTypeRaw {
    return [NSSet setWithObject:@"itemType"];
}

2
Merci - dommage que Core Data ne le supporte pas de manière native. Je veux dire: Xcode génère des fichiers de classe, pourquoi pas enums?
Constantino Tsarouhas

Le dernier code est si vous souhaitez observer l'élément itemTypeRaw. Cependant, vous pouvez simplement observer l'élément itemType au lieu de itemTypeRaw, n'est-ce pas?
Blanc anonyme

2
Avec Xcode 4.5, vous n'avez besoin de rien de tout cela. Jetez un œil à ma réponse. Il vous suffit de définir l'énumération comme un int16_tet vous êtes prêt.
Daniel Eggert

79

Vous pouvez faire de cette façon, de manière plus simple:

typedef enum Types_e : int16_t {
    TypeA = 0,
    TypeB = 1,
} Types_t;

@property (nonatomic) Types_t itemType;

Et dans votre modèle, défini itemTypecomme un nombre 16 bits. Terminé. Aucun code supplémentaire nécessaire. Mettez juste votre habitude

@dynamic itemType;

Si vous utilisez Xcode pour créer votre NSManagedObjectsous-classe, assurez-vous que le paramètre " Utiliser les propriétés scalaires pour les types de données primitifs " est coché.


4
Non, cela n'a rien à voir avec C ++ 11. Il fait partie de clang 3.3 prenant en charge les énumérations avec un type sous-jacent fixe pour ObjC. Cf clang.llvm.org/docs/…
Daniel Eggert

6
Comment éviter de perdre ce code à chaque fois que vous régénérez la classe de modèle? J'ai utilisé des catégories pour que les entités du domaine principal puissent être régénérées.
Rob

2
Le retainest lié à la gestion de la mémoire, pas à savoir si elle est stockée dans la base de données ou non.
Daniel Eggert

2
Je suis d'accord avec Rob. Je ne veux pas que cela doive être régénéré encore et encore. Je préfère la catégorie.
Kyle Redfearn

3
@Rob Categories est un moyen de le faire, mais à la place, vous pouvez également utiliser mogenerator: github.com/rentzsch/mogenerator . Mogenerator générera 2 classes par entité, où une classe sera toujours écrasée lors des changements de modèle de données et les autres sous-classes de cette classe pour des éléments personnalisés et ne sera jamais écrasée.
tapmonkey

22

Une approche alternative que j'envisage est de ne pas déclarer du tout une énumération, mais plutôt de déclarer les valeurs en tant que méthodes de catégorie sur NSNumber.


Intéressant. Cela semble définitivement faisable.
Michael Gaylord

idée brillante! tellement plus facile que de créer des tables dans la base de données, à moins que votre base de données ne soit remplie à partir d'un service Web, il est probablement préférable d'utiliser une table de base de données!
TheLearner


Je l'aime. Je vais utiliser cette approche dans mon projet. J'aime le fait que je puisse également contenir toutes mes autres méta-informations sur les métadonnées dans la catégorie NSNumber. (c'est-à-dire lier des chaînes aux valeurs d'énumération)
DonnaLea

Vraiment super idée! Très utile pour associer des identificateurs de chaîne, en utilisant directement dans JSON, Core Data, etc.
Gregarious

5

Si vous utilisez mogenerator, jetez un œil à ceci: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Vous pouvez avoir un attribut Integer 16 appelé itemType, avec une attributeValueScalarTypevaleur de Itemdans les informations utilisateur. Ensuite, dans les informations utilisateur de votre entité, définissez additionalHeaderFileNamele nom de l'en-tête dans lequel l' Iteménumération est définie. Lors de la génération de vos fichiers d'en-tête, mogenerator attribuera automatiquement le Itemtype à la propriété .


2

J'ai défini le type d'attribut comme un entier de 16 bits, puis j'utilise ceci:

#import <CoreData/CoreData.h>

enum {
    LDDirtyTypeRecord = 0,
    LDDirtyTypeAttachment
};
typedef int16_t LDDirtyType;

enum {
    LDDirtyActionInsert = 0,
    LDDirtyActionDelete
};
typedef int16_t LDDirtyAction;


@interface LDDirty : NSManagedObject

@property (nonatomic, strong) NSString* identifier;
@property (nonatomic) LDDirtyType type;
@property (nonatomic) LDDirtyAction action;

@end

...

#import "LDDirty.h"

@implementation LDDirty

@dynamic identifier;
@dynamic type;
@dynamic action;

@end

1

Étant donné que les énumérations sont soutenues par un court standard, vous ne pouvez pas non plus utiliser l'encapsuleur NSNumber et définir la propriété directement en tant que valeur scalaire. Assurez-vous de définir le type de données dans le modèle de données de base sur «Integer 32».

MyEntity.h

typedef enum {
kEnumThing, /* 0 is implied */
kEnumWidget, /* 1 is implied */
} MyThingAMaBobs;

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

Ailleurs dans le code

myEntityInstance.coreDataEnumStorage = kEnumThing;

Ou analyse à partir d'une chaîne JSON ou chargement à partir d'un fichier

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];

1

J'ai beaucoup fait cela et je trouve le formulaire suivant utile:

// accountType
public var account:AccountType {
    get {
        willAccessValueForKey(Field.Account.rawValue)
        defer { didAccessValueForKey(Field.Account.rawValue) }
        return primitiveAccountType.flatMap { AccountType(rawValue: $0) } ?? .New }
    set {
        willChangeValueForKey(Field.Account.rawValue)
        defer { didChangeValueForKey(Field.Account.rawValue) }
        primitiveAccountType = newValue.rawValue }}
@NSManaged private var primitiveAccountType: String?

Dans ce cas, l'énumération est assez simple:

public enum AccountType: String {
    case New = "new"
    case Registered = "full"
}

et l'appelle pédant, mais j'utilise des énumérations pour les noms de champs, comme ceci:

public enum Field:String {

    case Account = "account"
}

Comme cela peut devenir laborieux pour les modèles de données complexes, j'ai écrit un générateur de code qui consomme le MOM / les entités pour cracher tous les mappages. Mes entrées finissent par être un dictionnaire du type Table / Row au type Enum. Pendant que j'y étais, j'ai également généré du code de sérialisation JSON. J'ai fait cela pour des modèles très complexes et cela s'est avéré être un gain de temps considérable.


0

Le code collé ci-dessous fonctionne pour moi, et je l'ai ajouté comme exemple de travail complet. J'aimerais entendre des opinions sur cette approche, car je prévois de l'utiliser largement dans mes applications.

  • J'ai laissé le @dynamic en place, car il est ensuite satisfait par le getter / setter nommé dans la propriété.

  • Selon la réponse d'iKenndac, je n'ai pas remplacé les noms de getter / setter par défaut.

  • J'ai inclus une vérification de plage via un NSAssert sur les valeurs valides typedef.

  • J'ai également ajouté une méthode pour obtenir une valeur de chaîne pour le typedef donné.

  • Je préfixe les constantes avec "c" plutôt que "k". Je connais le raisonnement derrière "k" (origines mathématiques, historique), mais j'ai l'impression de lire du code ESL avec, donc j'utilise "c". Juste une chose personnelle.

Il y a une question similaire ici: typedef comme type de données Core

J'apprécierais toute contribution sur cette approche.

Word.h

#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>

typedef enum {
    cPresent            = 0,    
    cFuturProche        = 1,    
    cPasseCompose       = 2,    
    cImparfait          = 3,    
    cFuturSimple        = 4,    
    cImperatif          = 5     
} TenseTypeEnum;

@class Word;
@interface Word : NSManagedObject

@property (nonatomic, retain) NSString * word;
@property (nonatomic, getter = tenseRaw, setter = setTenseRaw:) TenseTypeEnum tense;

// custom getter & setter methods
-(void)setTenseRaw:(TenseTypeEnum)newValue;
-(TenseTypeEnum)tenseRaw;
- (NSString *)textForTenseType:(TenseTypeEnum)tenseType;

@end


Word.m


#import "Word.h"

@implementation Word

@dynamic word;
@dynamic tense;

// custom getter & setter methods
-(void)setTenseRaw:(TenseTypeEnum)newValue
{
    NSNumber *numberValue = [NSNumber numberWithInt:newValue];
    [self willChangeValueForKey:@"tense"];
    [self setPrimitiveValue:numberValue forKey:@"tense"];
    [self didChangeValueForKey:@"tense"];
}


-(TenseTypeEnum)tenseRaw
{
    [self willAccessValueForKey:@"tense"];
    NSNumber *numberValue = [self primitiveValueForKey:@"tense"];
    [self didAccessValueForKey:@"tense"];
    int intValue = [numberValue intValue];

    NSAssert(intValue >= 0 && intValue <= 5, @"unsupported tense type");
    return (TenseTypeEnum) intValue;
}


- (NSString *)textForTenseType:(TenseTypeEnum)tenseType
{
    NSString *tenseText = [[NSString alloc] init];

    switch(tenseType){
        case cPresent:
            tenseText = @"présent";
            break;
        case cFuturProche:
            tenseText = @"futur proche";
            break;
        case cPasseCompose:
            tenseText = @"passé composé";
            break;
        case cImparfait:
            tenseText = @"imparfait";
            break;
        case cFuturSimple:
            tenseText = @"futur simple";
            break;
        case cImperatif:
            tenseText = @"impératif";
            break;
    }
    return tenseText;
}


@end

0

Solution pour les classes générées automatiquement

à partir du générateur de code de Xcode (ios 10 et supérieur)

Si vous créez une entité nommée "YourClass", Xcode choisira automatiquement "Définition de classe" comme type de Codegen par défaut dans "Data Model Inspector". cela générera des classes ci-dessous:

Version Swift:

// YourClass+CoreDataClass.swift
  @objc(YourClass)
  public class YourClass: NSManagedObject {
  }

Version Objective-C:

// YourClass+CoreDataClass.h
  @interface YourClass : NSManagedObject
  @end

  #import "YourClass+CoreDataProperties.h"

  // YourClass+CoreDataClass.m
  #import "YourClass+CoreDataClass.h"
  @implementation YourClass
  @end

Nous choisirons "Catégorie / Extension" dans l'option Codegen au lieu de "Définition de classe" dans Xcode.

Maintenant, si nous voulons ajouter une énumération, allez créer une autre extension pour votre classe générée automatiquement et ajoutez vos définitions d'énumération ici comme ci-dessous:

// YourClass+Extension.h

#import "YourClass+CoreDataClass.h" // That was the trick for me!

@interface YourClass (Extension)

@end


// YourClass+Extension.m

#import "YourClass+Extension.h"

@implementation YourClass (Extension)

typedef NS_ENUM(int16_t, YourEnumType) {
    YourEnumTypeStarted,
    YourEnumTypeDone,
    YourEnumTypePaused,
    YourEnumTypeInternetConnectionError,
    YourEnumTypeFailed
};

@end

Désormais, vous pouvez créer des accesseurs personnalisés si vous souhaitez limiter les valeurs à une énumération. Veuillez vérifier la réponse acceptée par le propriétaire de la question . Ou vous pouvez convertir vos énumérations pendant que vous les définissez avec une méthode de conversion explicite en utilisant l'opérateur de cast comme ci-dessous:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Vérifiez également

Génération automatique de sous-classes Xcode

Xcode prend désormais en charge la génération automatique de sous-classes NSManagedObject dans l'outil de modélisation. Dans l'inspecteur d'entités:

Manuel / Aucun est le comportement par défaut et précédent; dans ce cas, vous devez implémenter votre propre sous-classe ou utiliser NSManagedObject. Category / Extension génère une extension de classe dans un fichier nommé comme ClassName + CoreDataGeneratedProperties. Vous devez déclarer / implémenter la classe principale (si dans Obj-C, via un en-tête l'extension peut importer nommée ClassName.h). La définition de classe génère des fichiers de sous-classe nommés comme ClassName + CoreDataClass ainsi que les fichiers générés pour Category / Extension. Les fichiers générés sont placés dans DerivedData et reconstruits lors de la première construction après l'enregistrement du modèle. Ils sont également indexés par Xcode, donc un clic sur les références et une ouverture rapide par nom de fichier fonctionnent.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.