Il modo migliore per implementare Enums con Core Data


109

Qual è il modo migliore per associare le entità Core Data ai valori enum in modo da poter assegnare una proprietà di tipo all'entità? In altre parole, ho un'entità chiamata Itemcon una itemTypeproprietà che voglio essere associata a un'enumerazione, qual è il modo migliore per farlo.

Risposte:


130

Dovrai creare funzioni di accesso personalizzate se desideri limitare i valori a un'enumerazione. Quindi, prima devi dichiarare un enum, in questo modo:

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

Quindi, dichiara getter e setter per la tua proprietà. È una cattiva idea sovrascrivere quelli esistenti, poiché le funzioni di accesso standard si aspettano un oggetto NSNumber piuttosto che un tipo scalare, e ti imbatterai in problemi se qualcosa nei collegamenti o nei sistemi KVO tenterà di accedere al tuo valore.

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

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

Infine, dovresti implementare in + keyPathsForValuesAffecting<Key>modo da ricevere le notifiche KVO per itemTypeRaw quando cambia itemType.

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

2
Grazie - peccato che Core Data non lo supporti in modo nativo. Voglio dire: Xcode genera file di classe, perché non enums?
Constantino Tsarouhas il

L'ultimo codice è se vuoi osservare l'elemento itemTypeRaw. Tuttavia, puoi semplicemente osservare item itemType invece di itemTypeRaw, giusto?
Anonymous White

2
Con Xcode 4.5 non hai bisogno di niente di tutto questo. Dai un'occhiata alla mia risposta. Hai solo bisogno di definire l'enumerazione come un int16_te sei a posto.
Daniel Eggert

79

Puoi farlo in questo modo, molto più semplice:

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

@property (nonatomic) Types_t itemType;

E nel tuo modello, impostato itemTypeper essere un numero a 16 bit. Tutto fatto. Nessun codice aggiuntivo necessario. Metti solo il tuo solito

@dynamic itemType;

Se stai usando Xcode per creare la tua NSManagedObjectsottoclasse, assicurati che l' impostazione " usa proprietà scalari per tipi di dati primitivi " sia selezionata.


4
No, questo non ha nulla a che fare con C ++ 11. Fa parte di clang 3.3 che supporta le enumerazioni con un tipo sottostante fisso per ObjC. Cf clang.llvm.org/docs/…
Daniel Eggert

6
Come si evita di perdere questo codice ogni volta che si rigenera la classe del modello? Ho utilizzato le categorie in modo che le entità del dominio principale possano essere rigenerate.
Rob

2
Il retainè legato alla gestione della memoria, non se si viene memorizzato nel database o meno.
Daniel Eggert

2
Sono d'accordo con Rob. Non voglio che questo debba essere rigenerato più e più volte. Preferisco la categoria.
Kyle Redfearn

3
@Rob Categories è un modo per farlo, ma invece potresti anche usare mogenerator: github.com/rentzsch/mogenerator . Mogenerator genererà 2 classi per entità, dove una classe verrà sempre sovrascritta in caso di modifiche al modello di dati e le altre sottoclassi quella classe per cose personalizzate e non verranno mai sovrascritte.
tapmonkey

22

Un approccio alternativo che sto considerando è di non dichiarare affatto un enum, ma di dichiarare invece i valori come metodi di categoria su NSNumber.


Interessante. Sembra decisamente fattibile.
Michael Gaylord,

idea brillante! molto più facile che creare tabelle nel db, a meno che il tuo db non sia riempito da un servizio web, probabilmente è meglio usare una tabella db!
TheLearner


Mi piace. Userò questo approccio nel mio progetto. Mi piace il fatto di poter contenere anche tutte le mie altre meta informazioni sui metadati all'interno della categoria NSNumber. (ovvero collegamento di stringhe ai valori enum)
DonnaLea

Ottima idea! Molto utile per associare identificatori di stringa, utilizzando direttamente in JSON, Core Data, ecc.
Gregario

5

Se stai usando mogenerator, dai un'occhiata a questo: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Puoi chiamare un attributo Integer 16 itemType, con un attributeValueScalarTypevalore di Itemnelle informazioni utente. Quindi, nelle informazioni utente per la tua entità, imposta additionalHeaderFileNameil nome dell'intestazione in cui Itemè definita l'enumerazione. Quando generi i tuoi file di intestazione, mogenerator renderà automaticamente la proprietà il Itemtipo.


2

Ho impostato il tipo di attributo come intero a 16 bit, quindi uso questo:

#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

Poiché le enumerazioni sono supportate da uno short standard, non è possibile utilizzare il wrapper NSNumber e impostare la proprietà direttamente come valore scalare. Assicurati di impostare il tipo di dati nel modello di dati di base come "Integer 32".

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

Altrove nel codice

myEntityInstance.coreDataEnumStorage = kEnumThing;

O l'analisi da una stringa JSON o il caricamento da un file

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];

1

L'ho fatto molto e trovo utile il seguente modulo:

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

In questo caso, l'enumerazione è piuttosto semplice:

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

e lo chiamo pedante, ma uso le enumerazioni per i nomi dei campi, in questo modo:

public enum Field:String {

    case Account = "account"
}

Poiché questo può diventare laborioso per modelli di dati complessi, ho scritto un generatore di codice che utilizza MOM / entità per sputare fuori tutte le mappature. I miei input finiscono per essere un dizionario dal tipo Table / Row a Enum. Già che ci lavoravo, ho anche generato il codice di serializzazione JSON. L'ho fatto per modelli molto complessi e si è rivelato un grande risparmio di tempo.


0

Il codice incollato di seguito funziona per me e l'ho aggiunto come esempio funzionante completo. Mi piacerebbe sentire opinioni su questo approccio, poiché ho intenzione di utilizzarlo ampiamente nelle mie app.

  • Ho lasciato @dynamic in posizione, in quanto viene quindi soddisfatto dal getter / setter nominato nella proprietà.

  • Secondo la risposta di iKenndac, non ho sovrascritto i nomi getter / setter predefiniti.

  • Ho incluso alcuni controlli dell'intervallo tramite un NSAssert sui valori validi typedef.

  • Ho anche aggiunto un metodo per ottenere un valore stringa per il typedef specificato.

  • Prefisso le costanti con "c" anziché "k". Conosco il ragionamento dietro "k" (origini matematiche, storico), ma mi sembra di leggere il codice ESL con esso, quindi uso "c". Solo una cosa personale.

C'è una domanda simile qui: typedef come tipo di dati Core

Apprezzerei qualsiasi contributo su questo approccio.

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

Soluzione per classi generate automaticamente

dal generatore di codice di Xcode (ios 10 e versioni successive)

Se crei un'entità chiamata "YourClass", Xcode sceglierà automaticamente "Class Definition" come tipo di Codegen predefinito in "Data Model Inspector". questo genererà le classi seguenti:

Versione rapida:

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

Versione Objective-C:

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

  #import "YourClass+CoreDataProperties.h"

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

Sceglieremo "Categoria / Estensione" dall'opzione Codegen invece di "Definizione classe" in Xcode.

Ora, se vogliamo aggiungere un'enumerazione, crea un'altra estensione per la tua classe generata automaticamente e aggiungi le definizioni dell'enumerazione qui come di seguito:

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

Ora puoi creare funzioni di accesso personalizzate se desideri limitare i valori a un'enumerazione. Si prega di controllare la risposta accettata dal proprietario della domanda . Oppure puoi convertire le tue enumerazioni mentre le imposti con un metodo di conversione esplicito utilizzando l'operatore cast come di seguito:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Controlla anche

Generazione automatica di sottoclassi Xcode

Xcode ora supporta la generazione automatica di sottoclassi NSManagedObject nello strumento di modellazione. Nella finestra di ispezione entità:

Manuale / Nessuno è il comportamento predefinito e precedente; in questo caso, è necessario implementare la propria sottoclasse o utilizzare NSManagedObject. Category / Extension genera un'estensione di classe in un file denominato come ClassName + CoreDataGeneratedProperties. È necessario dichiarare / implementare la classe principale (se in Obj-C, tramite un'intestazione l'estensione può importare denominata ClassName.h). La definizione della classe genera file di sottoclassi denominati come ClassName + CoreDataClass, nonché i file generati per Categoria / Estensione. I file generati vengono inseriti in DerivedData e ricostruiti nella prima build dopo il salvataggio del modello. Sono anche indicizzati da Xcode, quindi il clic del comando sui riferimenti e l'apertura rapida per nome del file funziona.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.