Лучший способ реализовать Enums с Core Data

109

Как лучше всего привязать сущности Core Data к значениям перечисления, чтобы я мог назначить объекту свойство типа? Другими словами, у меня есть сущность, вызываемая Itemсо itemTypeсвойством, которое я хочу связать с перечислением, как лучше всего это сделать.

Майкл Гейлорд
источник

Ответы:

130

Вам нужно будет создать собственные средства доступа, если вы хотите ограничить значения перечислением. Итак, сначала вы должны объявить перечисление, например:

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

Затем объявите геттеры и сеттеры для своего свойства. Переопределение существующих - плохая идея, поскольку стандартные средства доступа ожидают объект NSNumber, а не скалярный тип, и вы столкнетесь с проблемами, если что-либо в привязках или системах KVO попытается получить доступ к вашему значению.

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

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

Наконец, вы должны реализовать + keyPathsForValuesAffecting<Key>так, чтобы получать уведомления KVO для itemTypeRaw при изменении itemType.

+ (NSSet *)keyPathsForValuesAffectingItemTypeRaw {
    return [NSSet setWithObject:@"itemType"];
}
iKenndac
источник
2
Спасибо - очень плохо, что Core Data не поддерживает это изначально. Я имею в виду: Xcode генерирует файлы классов, почему не enums?
Константино Царухас 05
Последний код - если вы хотите наблюдать за элементом itemTypeRaw. Однако вы можете просто наблюдать item itemType вместо itemTypeRaw, верно?
Anonymous White
2
С Xcode 4.5 вам ничего этого не нужно. Взгляните на мой ответ. Вам просто нужно определить перечисление как int16_tи все готово.
Daniel Eggert
79

Вы можете сделать это проще:

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

@property (nonatomic) Types_t itemType;

В вашей модели задайте itemType16-битное число. Все сделано. Дополнительный код не требуется. Просто введите свой обычный

@dynamic itemType;

Если вы используете Xcode для создания своего NSManagedObjectподкласса, убедитесь, что установлен флажок « использовать скалярные свойства для примитивных типов данных ».

Даниэль Эггерт
источник
4
Нет, это не имеет ничего общего с C ++ 11. Это часть clang 3.3, поддерживающая перечисления с фиксированным базовым типом для ObjC. Cf clang.llvm.org/docs/…
Дэниел Эггерт
6
Как избежать потери этого кода каждый раз при повторном создании класса модели? Я использовал категории, чтобы можно было регенерировать сущности основного домена.
Роб
2
Это retainсвязано с управлением памятью, а не с тем, сохраняется она в базе данных или нет.
Даниэль Эггерт
2
Я согласен с Робом. Я не хочу, чтобы это приходилось восстанавливать снова и снова. Я предпочитаю категорию.
Кайл Редферн
3
@Rob Categories - это способ сделать это, но вместо этого вы также можете использовать mogenerator: github.com/rentzsch/mogenerator . M Generator будет генерировать 2 класса для каждой сущности, причем один класс всегда будет перезаписан при изменении модели данных, а другие подклассы этого класса - для пользовательского материала и никогда не будут перезаписаны.
tapmonkey
22

Альтернативный подход, который я рассматриваю, состоит в том, чтобы вообще не объявлять перечисление, а вместо этого объявлять значения как методы категории в NSNumber.

Майк Абдулла
источник
Интересный. Это определенно кажется выполнимым.
Майкл Гейлорд
великолепная идея! намного проще, чем создавать таблицы в db, если ваш db не заполнен из веб-службы, тогда, вероятно, лучше всего использовать таблицу db!
TheLearner
6
Вот пример: renovatioboy.wordpress.com/2011/10/06/...
ardochhigh
Мне это нравится. Я собираюсь использовать этот подход в своем проекте. Мне нравится, что я также могу содержать всю остальную мою метаинформацию о метаданных в категории NSNumber. (т.е. связывание строк со значениями перечисления)
DonnaLea
Действительно отличная идея! Очень полезно для связывания строковых идентификаторов, использования непосредственно в JSON, Core Data и т. Д.
Gregarious
5

Если вы используете mogenerator, взгляните на это: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Вы можете вызвать атрибут Integer 16 itemTypeсо attributeValueScalarTypeзначением Itemв информации о пользователе. Затем в информации о пользователе для вашей сущности задайте additionalHeaderFileNameимя заголовка, в котором Itemопределено перечисление. При создании файлов заголовков mogenerator автоматически присвоит свойству Itemтип.

jfla
источник
2

Я установил тип атрибута как 16-битное целое число, затем использую это:

#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
источник
1

Поскольку перечисления поддерживаются стандартным сокращением, вы также не можете использовать оболочку NSNumber и установить свойство напрямую как скалярное значение. Обязательно установите тип данных в основной модели данных как «Целое число 32».

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

В другом месте кода

myEntityInstance.coreDataEnumStorage = kEnumThing;

Или парсинг из строки JSON или загрузка из файла

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];
щенки
источник
1

Я делал это много и считаю полезной следующую форму:

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

В этом случае перечисление довольно простое:

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

и называю это педантичным, но я использую перечисления для имен полей, например:

public enum Field:String {

    case Account = "account"
}

Поскольку для сложных моделей данных это может оказаться трудоемким, я написал генератор кода, который использует MOM / сущности для вывода всех сопоставлений. Мои входные данные в конечном итоге являются словарем от типа Table / Row до Enum. Пока я занимался этим, я также сгенерировал код сериализации JSON. Я проделал это с очень сложными моделями, и это очень сэкономило время.

Крис Коновер
источник
0

Приведенный ниже код мне подходит, и я добавил его как полноценный рабочий пример. Я хотел бы услышать мнения об этом подходе, так как планирую широко использовать его в своих приложениях.

  • Я оставил @dynamic на месте, так как тогда он будет удовлетворен получателем / установщиком, указанным в свойстве.

  • Согласно ответу iKenndac, я не переопределил имена получателей / установщиков по умолчанию.

  • Я включил проверку диапазона с помощью NSAssert для допустимых значений typedef.

  • Я также добавил метод получения строкового значения для данного typedef.

  • Я ставлю перед константами префикс «c», а не «k». Я знаю причину «k» (математическое происхождение, историческое), но мне кажется, что я читаю код ESL вместе с ним, поэтому я использую «c». Просто личное дело.

Здесь есть аналогичный вопрос: typedef как тип данных Core

Буду признателен за любой вклад в этот подход.

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
источник
0

Решение для автоматически созданных классов

из генератора кода Xcode (iOS 10 и выше)

Если вы создаете объект с именем «YourClass», Xcode автоматически выберет «Определение класса» по умолчанию в качестве типа Codegen в «Инспекторе модели данных». это сгенерирует классы ниже:

Быстрая версия:

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

Версия Objective-C:

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

  #import "YourClass+CoreDataProperties.h"

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

Мы выберем «Категория / Расширение» из опции Codegen вместо «Определение класса» в Xcode.

Теперь, если мы хотим добавить перечисление, создайте другое расширение для вашего автоматически сгенерированного класса и добавьте сюда свои определения перечисления, как показано ниже:

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

Теперь вы можете создавать собственные средства доступа, если хотите ограничить значения перечислением. Пожалуйста, проверьте принятый ответ владельцем вопроса . Или вы можете преобразовать свои перечисления, задав их явным методом преобразования, используя оператор приведения, как показано ниже:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Также проверьте

Автоматическое создание подкласса Xcode

Xcode теперь поддерживает автоматическое создание подклассов NSManagedObject в средстве моделирования. В инспекторе сущностей:

Вручную / Нет - по умолчанию и предыдущее поведение; в этом случае вы должны реализовать свой собственный подкласс или использовать NSManagedObject. Категория / расширение создает расширение класса в файле с именем типа ClassName + CoreDataGeneratedProperties. Вам необходимо объявить / реализовать основной класс (если в Obj-C, через заголовок, который расширение может импортировать с именем ClassName.h). Определение класса создает файлы подкласса с именами типа ClassName + CoreDataClass, а также файлы, созданные для категории / расширения. Сгенерированные файлы помещаются в DerivedData и перестраиваются при первой сборке после сохранения модели. Они также индексируются Xcode, поэтому работает щелчок по ссылкам и быстрое открытие по имени файла.

mgyky
источник