Best way to implement Enums with Core Data

前端 未结 9 1543
天命终不由人
天命终不由人 2020-11-28 17:55

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<

相关标签:
9条回答
  • 2020-11-28 18:16

    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.

    0 讨论(0)
  • 2020-11-28 18:18

    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
    
    0 讨论(0)
  • 2020-11-28 18:20

    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.

    0 讨论(0)
  • 2020-11-28 18:22

    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"];
    }
    
    0 讨论(0)
  • 2020-11-28 18:23

    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.

    0 讨论(0)
  • 2020-11-28 18:24

    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;
    

    Also check

    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.

    0 讨论(0)
提交回复
热议问题