用核心数据实现枚举的最佳方法


109

将Core Data实体绑定到枚举值以使我能够为该实体分配类型属性的最佳方法是什么?换句话说,我有一个实体,它Item带有一个itemType我想绑定到枚举的属性,这是最好的方法。

Answers:


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>以便在itemType更改时获得itemTypeRaw的KVO通知。

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

2
谢谢您-太糟糕了,Core Data本机不支持此功能。我的意思是:Xcode生成类文件,为什么不enum呢?
君士坦丁撒鲁哈斯

最后一个代码是如果您要观察项目itemTypeRaw。但是,您可以简单地观察item itemType而不是itemTypeRaw对吗?
2012年

2
使用Xcode 4.5,您不需要任何这些。看看我的答案。您只需要将枚举定义为int16_t和就可以了。
丹尼尔·埃格特

79

您可以这样做,更简单:

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

@property (nonatomic) Types_t itemType;

在您的模型中,将其设置itemType为16位数字。全做完了。无需其他代码。照常放

@dynamic itemType;

如果使用Xcode创建NSManagedObject子类,请确保选中“ 对原始数据类型使用标量属性 ”设置。


4
不,这与C ++ 11无关。它是clang 3.3的一部分,它支持ObjC 具有固定基础类型的枚举。比照clang.llvm.org/docs/...
丹尼尔埃盖特

6
如何避免每次重新生成模型类时都丢失此代码?我一直在使用类别,以便可以重新生成核心域实体。
罗布2013年

2
retain是有关内存管理,不是它是否会被存储到数据库或没有。
丹尼尔·艾格特

2
我同意罗布。我不希望这一次又一次地重新生成。我更喜欢这个类别。
凯尔·雷德费恩

3
@Rob Categories是一种实现方法,但是您也可以使用mogenerator:github.com/rentzsch/mogenerator。Mogenerator将为每个实体生成2个类,其中一个类将始终在数据模型更改时被覆盖,而另一个子类则用于自定义内容,并且永远不会被覆盖。
tapmonkey 2014年

22

我正在考虑的另一种方法是根本不声明枚举,而是将值声明为NSNumber上的类别方法。


有趣。看来确实可行。
Michael Gaylord

卓见!这比在db中创建表要容易得多,除非您的db是通过Web服务填充的,否则最好使用db表!
TheLearner 2011年


我喜欢。我将在我的项目中使用这种方法。我喜欢我也可以在NSNumber类别中包含有关元数据的所有其他元信息。(即,将字符串链接到枚举值)
DonnaLea 2014年

真是个好主意!关联字符串标识符,直接在JSON,Core Data等中使用非常有用
。– Gregarious

5

如果您使用的是发电机,请看一下:https : //github.com/rentzsch/mogenerator/wiki/Using-enums-as-types。您可以拥有名为的Integer 16属性itemType,用户信息中的attributeValueScalarType值为Item。然后,在您实体的用户信息中,将其设置为定义枚举additionalHeaderFileName的标头的名称Item。生成标头文件时,mogenerator会自动使属性具有Item类型。


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

1

由于枚举由标准short支持,因此您也不能使用NSNumber包装器并将属性直接设置为标量值。确保将核心数据模型中的数据类型设置为“ Integer 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"
}

并将其称为pedantic,但我使用枚举来表示字段名称,如下所示:

public enum Field:String {

    case Account = "account"
}

由于这对于复杂的数据模型可能会很费力,因此我编写了一个代码生成器,该代码生成器使用MOM /实体吐出所有映射。我的输入最终是一个从表/行到枚举类型的字典。在此期间,我还生成了JSON序列化代码。我已经针对非常复杂的模型进行了此操作,事实证明这可以节省大量时间。


0

下面粘贴的代码对我有用,并且我将其添加为完整的工作示例。我希望听到有关此方法的意见,因为我计划在我的应用程序中广泛使用它。

  • 我将@dynamic保留在原处,因为该属性随后由名称中的getter / setter满足。

  • 根据iKenndac的回答,我没有覆盖默认的getter / setter名称。

  • 我已经通过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

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索引,因此可以在引用上单击命令并按文件名快速打开。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.