Melhor maneira de implementar Enums com dados centrais

109

Qual é a melhor maneira de vincular entidades de dados centrais a valores de enum para que eu possa atribuir uma propriedade de tipo à entidade? Em outras palavras, tenho uma entidade chamada Itemcom uma itemTypepropriedade que quero vincular a um enum, qual é a melhor maneira de fazer isso.

Michael Gaylord
fonte

Respostas:

130

Você terá que criar acessores personalizados se quiser restringir os valores a um enum. Então, primeiro você declararia um enum, assim:

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

Em seguida, declare getters e setters para sua propriedade. É uma má ideia substituir os existentes, uma vez que os acessadores padrão esperam um objeto NSNumber em vez de um tipo escalar, e você terá problemas se alguma coisa nas ligações ou sistemas KVO tentar acessar seu valor.

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

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

Finalmente, você deve implementar + keyPathsForValuesAffecting<Key>para obter notificações KVO para itemTypeRaw quando o itemType muda.

+ (NSSet *)keyPathsForValuesAffectingItemTypeRaw {
    return [NSSet setWithObject:@"itemType"];
}
iKenndac
fonte
2
Obrigado - uma pena que Core Data não suporta isso nativamente. Quer dizer: o Xcode gera arquivos de classe, por que não enums?
Constantino Tsarouhas
O último código é se você deseja observar o item itemTypeRaw. No entanto, você pode simplesmente observar o item itemType em vez de itemTypeRaw, certo?
Anonymous White,
2
Com o Xcode 4.5 você não precisa de nada disso. Dê uma olhada na minha resposta. Você só precisa definir o enum como um int16_te pronto.
Daniel Eggert
79

Você pode fazer assim, de maneira mais simples:

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

@property (nonatomic) Types_t itemType;

E em seu modelo, defina itemTypeum número de 16 bits. Tudo feito. Nenhum código adicional necessário. Basta colocar no seu habitual

@dynamic itemType;

Se você estiver usando o Xcode para criar sua NSManagedObjectsubclasse, certifique-se de que a configuração " usar propriedades escalares para tipos de dados primitivos " esteja marcada.

Daniel Eggert
fonte
4
Não, isso não tem nada a ver com C ++ 11. Faz parte do clang 3.3 que suporta Enumerações com um tipo subjacente fixo para ObjC. Cf clang.llvm.org/docs/…
Daniel Eggert,
6
Como você evita perder esse código toda vez que regenera a classe do modelo? Tenho usado categorias para que as entidades do domínio central possam ser regeneradas.
Rob
2
O retainestá relacionado ao gerenciamento de memória, não se ele é armazenado no banco de dados ou não.
Daniel Eggert,
2
Eu concordo com Rob. Não quero que isso tenha que ser regenerado continuamente. Eu prefiro a categoria.
Kyle Redfearn de
3
Categorias @Rob é uma maneira de fazer isso, mas em vez disso, você também pode usar o mogenerator: github.com/rentzsch/mogenerator . O Mogenerator irá gerar 2 classes por entidade, onde uma classe sempre será sobrescrita nas alterações do modelo de dados e as outras subclasses dessa classe para coisas personalizadas e nunca será sobrescrita.
tapmonkey
22

Uma abordagem alternativa que estou considerando é não declarar um enum, mas, em vez disso, declarar os valores como métodos de categoria em NSNumber.

Mike Abdullah
fonte
Interessante. Definitivamente parece factível.
Michael Gaylord
ideia brilhante! muito mais fácil do que criar tabelas no banco de dados, a menos que seu banco de dados seja preenchido por um serviço da web, então provavelmente é melhor usar uma tabela de banco de dados!
TheLearner,
6
Aqui está um exemplo: renovatioboy.wordpress.com/2011/10/06/…
ardochhigh
Eu gosto disso. Vou usar essa abordagem em meu projeto. Eu gosto de poder também conter todas as minhas outras metainformações sobre os metadados dentro da categoria NSNumber. (ou seja, vincular strings aos valores enum)
DonnaLea
Ótima ideia! Muito útil para associar identificadores de string, usando diretamente em JSON, Core Data, etc.
Gregário
5

Se você estiver usando o mogenerator, dê uma olhada em: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Você pode ter um atributo Integer 16 chamado itemType, com um attributeValueScalarTypevalor de Itemnas informações do usuário. Em seguida, nas informações do usuário para sua entidade, defina additionalHeaderFileNameo nome do cabeçalho no qual o Itemenum está definido. Ao gerar seus arquivos de cabeçalho, o mogenerator fará automaticamente com que a propriedade tenha o Itemtipo.

jfla
fonte
2

Eu defino o tipo de atributo como inteiro de 16 bits e uso isto:

#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
fonte
1

Como os enums são apoiados por um curto padrão, você também não pode usar o wrapper NSNumber e definir a propriedade diretamente como um valor escalar. Certifique-se de definir o tipo de dados no modelo de dados 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;

Em outro lugar no código

myEntityInstance.coreDataEnumStorage = kEnumThing;

Ou analisar de uma string JSON ou carregar de um arquivo

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];
filhotinhos
fonte
1

Tenho feito muito isso e acho o seguinte formulário útil:

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

Nesse caso, o enum é muito simples:

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

e chamo de pedante, mas eu uso enums para nomes de campo, assim:

public enum Field:String {

    case Account = "account"
}

Como isso pode ser trabalhoso para modelos de dados complexos, escrevi um gerador de código que consome o MOM / entidades para liberar todos os mapeamentos. Minhas entradas acabam sendo um dicionário do tipo Tabela / Linha para Enum. Enquanto fazia isso, também gerei o código de serialização JSON. Eu fiz isso para modelos muito complexos e acabou economizando muito tempo.

Chris Conover
fonte
0

O código colado abaixo funciona para mim e eu o adicionei como um exemplo funcional completo. Eu gostaria de ouvir opiniões sobre essa abordagem, já que pretendo usá-la extensivamente em todos os meus aplicativos.

  • Deixei o @dynamic no lugar, pois ele é satisfeito pelo getter / setter nomeado na propriedade.

  • De acordo com a resposta do iKenndac, não substituí os nomes getter / setter padrão.

  • Incluí alguma verificação de intervalo por meio de um NSAssert nos valores válidos de typedef.

  • Também adicionei um método para obter um valor de string para o typedef fornecido.

  • Prefixo as constantes com "c" em vez de "k". Eu sei o raciocínio por trás de "k" (origens matemáticas, histórico), mas parece que estou lendo código ESL com ele, então uso "c". Só uma coisa pessoal.

Há uma questão semelhante aqui: typedef como um tipo de dados Core

Eu apreciaria qualquer contribuição sobre esta abordagem.

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
fonte
0

Solução para classes geradas automaticamente

do gerador de código do Xcode (ios 10 e superior)

Se você criar uma entidade chamada "YourClass", o Xcode escolherá automaticamente "Class Definition" como padrão um tipo de Codegen em "Data Model Inspector". isso irá gerar classes abaixo:

Versão Swift:

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

Versão Objective-C:

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

  #import "YourClass+CoreDataProperties.h"

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

Escolheremos "Categoria / Extensão" na opção Codegen em vez de "Definição de Classe" no Xcode.

Agora, se quisermos adicionar um enum, vá e crie outra extensão para sua classe gerada automaticamente e adicione suas definições de enum aqui como abaixo:

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

Agora, você pode criar acessores personalizados se quiser restringir os valores a um enum. Verifique a resposta aceita pelo proprietário da pergunta . Ou você pode converter seus enums enquanto os define com o método de conversão explicitamente usando o operador cast como abaixo:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Verifique também

Geração automática de subclasse Xcode

O Xcode agora oferece suporte à geração automática de subclasses NSManagedObject na ferramenta de modelagem. No inspetor de entidade:

Manual / Nenhum é o comportamento padrão e anterior; neste caso, você deve implementar sua própria subclasse ou usar NSManagedObject. Categoria / extensão gera uma extensão de classe em um arquivo chamado ClassName + CoreDataGeneratedProperties. Você precisa declarar / implementar a classe principal (se em Obj-C, por meio de um cabeçalho, a extensão pode importar chamada ClassName.h). A definição de classe gera arquivos de subclasse nomeados como ClassName + CoreDataClass, bem como os arquivos gerados para categoria / extensão. Os arquivos gerados são colocados em DerivedData e reconstruídos na primeira construção após o modelo ser salvo. Eles também são indexados pelo Xcode, portanto, clicar com o comando nas referências e abrir rapidamente por nome de arquivo funciona.

mgyky
fonte