web-dev-qa-db-fra.com

Meilleure façon d'implémenter Enums avec Core Data

Quel est le meilleur moyen de lier des entités Core Data à des valeurs énumérées afin que je puisse affecter une propriété de type à l'entité? En d'autres termes, j'ai une entité appelée Item avec une propriété itemType que je veux être lié à une enum, quelle est la meilleure façon de procéder.

106
Michael Gaylord

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

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

Ensuite, déclarez les getters et setters de votre propriété. Il est déconseillé de remplacer les éléments existants, car les accesseurs standard attendent un objet NSNumber plutôt qu'un type scalaire et vous rencontrerez des problèmes si un élément des liaisons ou des systèmes KVO tente 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> pour que vous obteniez des notifications KVO pour itemTypeRaw lorsque itemType est modifié.

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

Vous pouvez le faire 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éfinissez itemType sur un nombre de 16 bits. Terminé. Aucun code supplémentaire requis. Il suffit de mettre dans votre habitude

@dynamic itemType;

Si vous utilisez Xcode pour créer votre sous-classe NSManagedObject, vérifiez que le paramètre " utiliser les propriétés scalaires pour les types de données primitifs " est coché.

78
Daniel Eggert

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

22
Mike Abdullah

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

5
jfla

J'ai défini le type d'attribut sur 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
2
malhal

Comme les énumérations sont appuyées par un court-métrage standard, vous ne pouvez pas non plus utiliser l’encapsuleur NSNumber et définir directement la propriété en tant que valeur scalaire. Veillez à définir le type de données dans le modèle de données principal 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 l'analyse d'une chaîne JSON ou le chargement d'un fichier

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];
1
puppybits

Le code collé ci-dessous fonctionne pour moi et je l'ai ajouté à titre d'exemple complet. J'aimerais connaître les opinions sur cette approche, car je prévois de l'utiliser largement dans toutes mes applications. 

  • J'ai laissé le @dynamic à la place, car le getter/setter nommé dans la propriété le satisfait.

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

  • 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 le code ESL avec, donc j'utilise "c". Juste une chose personnelle.

Une question similaire se pose ici: typedef en tant que 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
ardochhigh

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

depuis le 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 Codegen par défaut dans "Inspecteur de modèle de données". cela générera des classes ci-dessous:

Version rapide:

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

Maintenant, vous pouvez créer des accesseurs personnalisés si vous souhaitez restreindre les valeurs à une énumération. Veuillez cocher la réponse acceptée par le propriétaire de la question . Ou vous pouvez convertir vos enums pendant que vous les définissez avec une méthode de conversion explicite en utilisant l'opérateur de conversion 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é ClassName + CoreDataGeneratedProperties. Vous devez déclarer/implémenter la classe principale (si dans Obj-C, l'extension peut importer via un en-tête nommée ClassName.h). La définition de classe génère des fichiers de sous-classe nommés ClassName + CoreDataClass, ainsi que les fichiers générés pour la catégorie/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. Un clic de commande sur les références et une ouverture rapide par nom de fichier fonctionnent.

0
mgyky

Je l'ai souvent fait et 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 être laborieux pour des modèles de données complexes, j'ai écrit un générateur de code qui utilise le MOM/les entités pour créer toutes les correspondances. Mes entrées finissent par être un dictionnaire de type Table/Row au type Enum. Pendant que j'y travaillais, j'ai également généré un code de sérialisation JSON. Je l'ai fait pour des modèles très complexes et cela s'est avéré être un gain de temps considérable. 

0
Chris Conover