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.
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"];
}
int16_t
and you're set. –
Deangelo 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.
retain
keyword, will it still get stored in the database? –
Lajoie retain
is related to memory management, not whether it gets stored into the database or not. –
Deangelo enum
is something very, very different from a C / ObjC enum. I'm not sure if there's any good way to fudge it… –
Deangelo int16_t
is already defined if you import Foundation or CoreData. Are you using Swift? You need to make sure that your scalar type in the entity definition matches the int16_t
-> 16 bit. –
Deangelo int16_t
) is set in your model file. If you're using Xcode to create your NSManagedObject
subclass, make sure that the "use scalar properties for primitive data types" setting is checked. –
Trilbee 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];
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.
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
Solution for Auto Generated Classes
from Xcode's Code Generator (ios 10 and above)
If you create an Entity named "YourClass", Xcode automatically will choose "Class Definition" as default a Codegen type at "Data Model Inspector". this will generate classes below:
Swift version:
// YourClass+CoreDataClass.swift
@objc(YourClass)
public class YourClass: NSManagedObject {
}
Objective-C version:
// YourClass+CoreDataClass.h
@interface YourClass : NSManagedObject
@end
#import "YourClass+CoreDataProperties.h"
// YourClass+CoreDataClass.m
#import "YourClass+CoreDataClass.h"
@implementation YourClass
@end
We'll choose "Category/Extension" from Codegen option instead of "Class Definition" in Xcode.
Now, If we want to add an enum, go and create another extension for your auto-generated class, and add your enum definitions here like below:
// 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
Now, you can create custom accessors if you want to restrict the values to an enum. Please check the accepted answer by question owner. Or you can convert your enums while you set them with explicitly conversion method using the cast operator like below:
model.yourEnumProperty = (int16_t)YourEnumTypeStarted;
Xcode automatic subclass generation
Xcode now supports automatic generation of NSManagedObject subclasses in the modeling tool. In the entity inspector:
Manual/None is the default, and previous behavior; in this case, you should implement your own subclass or use NSManagedObject. Category/Extension generates a class extension in a file named like ClassName+CoreDataGeneratedProperties. You need to declare/implement the main class (if in Obj-C, via a header the extension can import named ClassName.h). Class Definition generates subclass files named like ClassName+CoreDataClass as well as the files generated for Category/Extension. The generated files are placed in DerivedData and rebuilt on the first build after the model is saved. They are also indexed by Xcode, so command-clicking on references and fast-opening by filename works.
© 2022 - 2024 — McMap. All rights reserved.
enum
s? – Froemming