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.
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"];
}
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é.
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.
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é.
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
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];
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
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:
// YourClass+CoreDataClass.Swift
@objc(YourClass)
public class YourClass: NSManagedObject {
}
// 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;
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.
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.