Question

What is the best way to bind Core Data entities to enum values so that I am able to assign a type property to the entity? In other words, I have an entity called Item with an itemType property that I want to be bound to an enum, what is the best way of going about this.

Was it helpful?

Solution

You'll have to create custom accessors if you want to restrict the values to an enum. So, first you'd declare an enum, like so:

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

Then, declare getters and setters for your property. It's a bad idea to override the existing ones, since the standard accessors expect an NSNumber object rather than a scalar type, and you'll run into trouble if anything in the bindings or KVO systems try and access your value.

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

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

Finally, you should implement + keyPathsForValuesAffecting<Key> so you get KVO notifications for itemTypeRaw when itemType changes.

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

OTHER TIPS

You can do this way, way simpler:

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

@property (nonatomic) Types_t itemType;

And in your model, set itemType to be a 16 bit number. All done. No additional code needed. Just put in your usual

@dynamic itemType;

If you're using Xcode to create your NSManagedObject subclass, make sure that the "use scalar properties for primitive data types" setting is checked.

An alternative approach I'm considering is not to declare an enum at all, but to instead declare the values as category methods on NSNumber.

If you're using mogenerator, have a look at this: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types. You can have an Integer 16 attribute called itemType, with a attributeValueScalarType value of Item in the user info. Then, in the user info for your entity, set additionalHeaderFileName to the name of the header that the Item enum is defined in. When generating your header files, mogenerator will automatically make the property have the Item type.

I set the attribute type as 16 bit integer then use this:

#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

Since enums are backed by a standard short you could also not use the NSNumber wrapper and set the property directly as a scalar value. Make sure to set the data type in the core data model as "Integer 32".

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

Elsewhere in code

myEntityInstance.coreDataEnumStorage = kEnumThing;

Or parsing from a JSON string or loading from a file

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];

The code pasted below works for me, and I've added it as full working example. I'd like to hear opinions on this approach, as I plan to used it extensively throughout my apps.

  • I've left the @dynamic in place, as it is then satisfied by the getter/setter named in the property.

  • As per the answer by iKenndac, I have not overridden the default getter/setter names.

  • I've included some range checking via a NSAssert on the typedef valid values.

  • I've also added a method to obtain a string value for the given typedef.

  • I prefix constants with "c" rather than "k". I know the reasoning behind "k" (math origins, historical), but it feels like I am reading ESL code with it, so I use "c". Just a personal thing.

There is a similar question here: typedef as a Core data type

I'd appreciate any input on this approach.

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

I have done this a lot and find the following form to be useful:

// 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 this case, the enum is pretty simple:

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

and call it pedantic, but I use enums for field names, like this:

public enum Field:String {

    case Account = "account"
}

Since this can get laborious for complex data models, I wrote a code generator that consumes the MOM / entities to spit out all the mappings. My inputs end up being a dictionary from Table/Row to Enum type. While I was at it, I also generated JSON serialization code. I've done this for very complex models and it has turned out to be a big time saver.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top