La mejor manera de implementar Enums con Core Data

109

¿Cuál es la mejor manera de vincular las entidades de datos centrales a los valores de enumeración para poder asignar una propiedad de tipo a la entidad? En otras palabras, tengo una entidad llamada Itemcon una itemTypepropiedad que quiero vincular a una enumeración, cuál es la mejor manera de hacerlo.

Michael Gaylord
fuente

Respuestas:

130

Tendrá que crear descriptores de acceso personalizados si desea restringir los valores a una enumeración. Entonces, primero declararías una enumeración, así:

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

Luego, declare getters y setters para su propiedad. Es una mala idea anular los existentes, ya que los accesores estándar esperan un objeto NSNumber en lugar de un tipo escalar, y tendrá problemas si algo en los enlaces o sistemas KVO intenta acceder a su valor.

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

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

Finalmente, debe implementarlo + keyPathsForValuesAffecting<Key>para obtener notificaciones KVO para itemTypeRaw cuando cambia itemType.

+ (NSSet *)keyPathsForValuesAffectingItemTypeRaw {
    return [NSSet setWithObject:@"itemType"];
}
iKenndac
fuente
2
Gracias, lástima que Core Data no sea compatible con esto de forma nativa. Quiero decir: Xcode genera archivos de clase, ¿por qué no enums?
Constantino Tsarouhas
El último código es si desea observar el elemento itemTypeRaw. Sin embargo, puede simplemente observar itemType en lugar de itemTypeRaw, ¿verdad?
Anonymous White
2
Con Xcode 4.5 no necesitas nada de esto. Eche un vistazo a mi respuesta. Solo necesita definir la enumeración como un int16_ty listo.
Daniel Eggert
79

Puedes hacerlo de esta manera, mucho más simple:

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

@property (nonatomic) Types_t itemType;

Y en su modelo, establezca itemTypeun número de 16 bits. Todo listo. No se necesita código adicional. Solo pon tu habitual

@dynamic itemType;

Si está utilizando Xcode para crear su NSManagedObjectsubclase, asegúrese de que la configuración " usar propiedades escalares para tipos de datos primitivos " esté marcada.

Daniel Eggert
fuente
4
No, esto no tiene nada que ver con C ++ 11. Es parte de clang 3.3 que admite Enumeraciones con un tipo subyacente fijo para ObjC. Cf clang.llvm.org/docs/…
Daniel Eggert
6
¿Cómo evita perder este código cada vez que regenera la clase de modelo? He estado usando Categorías para que las entidades de dominio central se puedan regenerar.
Rob
2
El retaintiene que ver con la gestión de memoria, no si se almacena en la base de datos o no.
Daniel Eggert
2
Estoy de acuerdo con Rob. No quiero que esto tenga que regenerarse una y otra vez. Prefiero la categoría.
Kyle Redfearn
3
@Rob Categorías es una forma de hacerlo, pero en su lugar también puede usar mogenerator: github.com/rentzsch/mogenerator . Mogenerator generará 2 clases por entidad, donde una clase siempre se sobrescribirá en los cambios del modelo de datos y las otras subclases esa clase para cosas personalizadas y nunca se sobrescribirá.
Tapmonkey
22

Un enfoque alternativo que estoy considerando es no declarar una enumeración en absoluto, sino declarar los valores como métodos de categoría en NSNumber.

Mike Abdullah
fuente
Interesante. Definitivamente parece factible.
Michael Gaylord
¡brillante idea! mucho más fácil que crear tablas en la base de datos, a menos que su base de datos se complete desde un servicio web, ¡probablemente sea mejor usar una tabla de base de datos!
TheLearner
6
Aquí hay un ejemplo: renovatioboy.wordpress.com/2011/10/06/…
ardochhigh
Me gusta. Usaré este enfoque en mi proyecto. Me gusta que también puedo contener toda mi otra metainformación sobre los metadatos dentro de la categoría NSNumber. (es decir, vincular cadenas a los valores de enumeración)
DonnaLea
¡Realmente gran idea! Muy útil para asociar identificadores de cadena, usar directamente en JSON, Core Data, etc.
Gregarious
5

Si está utilizando mogenerator, eche un vistazo a esto: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Puede tener un atributo Integer 16 llamado itemType, con un attributeValueScalarTypevalor de Itemen la información del usuario. Luego, en la información de usuario de su entidad, establezca additionalHeaderFileNameel nombre del encabezado en el que Itemse define la enumeración. Al generar sus archivos de encabezado, mogenerator automáticamente hará que la propiedad tenga el Itemtipo.

jfla
fuente
2

Configuré el tipo de atributo como un entero de 16 bits y luego uso esto:

#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
malhal
fuente
1

Dado que las enumeraciones están respaldadas por un corto estándar, tampoco podría usar el contenedor NSNumber y establecer la propiedad directamente como un valor escalar. Asegúrese de establecer el tipo de datos en el modelo de datos principal como "Integer 32".

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

En otra parte del código

myEntityInstance.coreDataEnumStorage = kEnumThing;

O analizar desde una cadena JSON o cargar desde un archivo

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];
cachorros
fuente
1

He hecho esto mucho y encuentro útil el siguiente formulario:

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

En este caso, la enumeración es bastante simple:

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

y lo llamo pedante, pero uso enumeraciones para los nombres de campo, como este:

public enum Field:String {

    case Account = "account"
}

Dado que esto puede resultar laborioso para modelos de datos complejos, escribí un generador de código que consume las entidades MOM / para escupir todas las asignaciones. Mis entradas terminan siendo un diccionario desde Tabla / Fila hasta tipo Enum. Mientras estaba en eso, también generé código de serialización JSON. He hecho esto para modelos muy complejos y ha resultado ser un gran ahorro de tiempo.

Chris Conover
fuente
0

El código pegado a continuación funciona para mí y lo agregué como ejemplo de trabajo completo. Me gustaría escuchar opiniones sobre este enfoque, ya que planeo usarlo ampliamente en todas mis aplicaciones.

  • Dejé @dynamic en su lugar, ya que luego lo satisface el getter / setter nombrado en la propiedad.

  • Según la respuesta de iKenndac, no he anulado los nombres predeterminados de getter / setter.

  • He incluido una verificación de rango a través de un NSAssert en los valores válidos de typedef.

  • También agregué un método para obtener un valor de cadena para el typedef dado.

  • Prefijo constantes con "c" en lugar de "k". Conozco el razonamiento detrás de "k" (orígenes matemáticos, histórico), pero se siente como si estuviera leyendo código de ESL con él, así que uso "c". Es solo una cosa personal.

Aquí hay una pregunta similar: typedef como tipo de datos Core

Agradecería cualquier aportación sobre este enfoque.

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
ardochhigh
fuente
0

Solución para clases generadas automáticamente

del Generador de código de Xcode (ios 10 y superior)

Si crea una entidad llamada "YourClass", Xcode automáticamente elegirá "Definición de clase" como tipo de Codegen predeterminado en "Inspector de modelo de datos". esto generará clases a continuación:

Versión rápida:

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

Versión Objective-C:

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

  #import "YourClass+CoreDataProperties.h"

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

Elegiremos "Categoría / Extensión" de la opción Codegen en lugar de "Definición de clase" en Xcode.

Ahora, si queremos agregar una enumeración, cree otra extensión para su clase generada automáticamente y agregue sus definiciones de enumeración aquí como se muestra a continuación:

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

Ahora, puede crear descriptores de acceso personalizados si desea restringir los valores a una enumeración. Compruebe la respuesta aceptada por el propietario de la pregunta . O puede convertir sus enumeraciones mientras las configura con el método de conversión explícitamente utilizando el operador de conversión como se muestra a continuación:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

También comprobar

Generación automática de subclase Xcode

Xcode ahora admite la generación automática de subclases NSManagedObject en la herramienta de modelado. En el inspector de entidades:

Manual / Ninguno es el comportamiento predeterminado y anterior; en este caso, debe implementar su propia subclase o usar NSManagedObject. Categoría / Extensión genera una extensión de clase en un archivo llamado ClassName + CoreDataGeneratedProperties. Debe declarar / implementar la clase principal (si está en Obj-C, a través de un encabezado, la extensión puede importar con el nombre ClassName.h). La definición de clase genera archivos de subclase denominados como ClassName + CoreDataClass, así como los archivos generados para Categoría / Extensión. Los archivos generados se colocan en DerivedData y se reconstruyen en la primera compilación después de guardar el modelo. También están indexados por Xcode, por lo que hacer clic en el comando y hacer clic en las referencias y la apertura rápida por nombre de archivo funciona.

mgyky
fuente