Лучший способ реализации Enums с базовыми данными

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

Ответ 1

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

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"];
}

Ответ 2

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

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

@property (nonatomic) Types_t itemType;

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

@dynamic itemType;

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

Ответ 3

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

Ответ 4

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

Ответ 5

Я устанавливаю тип атрибута как 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

Ответ 6

Так как перечисления поддерживаются стандартным коротким, вы также не можете использовать оболочку 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];

Ответ 7

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

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

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

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

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

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

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

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

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

Ответ 8

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

// 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 to Enum. Пока я был на нем, я также создал код сериализации JSON. Я сделал это для очень сложных моделей, и это оказалось большой экономией времени.

Ответ 9

Решение для iOS 10 и выше Auto Generated Class

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

Swift версия:

// 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. Category/Extension генерирует расширение класса в файле с именем, подобным ClassName + CoreDataGeneratedProperties. Вам необходимо объявить/реализовать основной класс (если в Obj-C через заголовок расширение может импортировать с именем ClassName.h). Определение класса генерирует файлы подкласса с именами, такими как ClassName + CoreDataClass, а также файлы, созданные для категории/расширения. Сгенерированные файлы помещаются в DerivedData и перестраиваются при первой сборке после сохранения модели. Они также индексируются Xcode, поэтому щелчок по командам по ссылкам и быстрое открытие по имени файла работает.