web-dev-qa-db-ja.com

コアデータで列挙型を実装する最良の方法

コアデータエンティティを列挙値にバインドして、タイププロパティをエンティティに割り当てることができる最良の方法は何ですか?つまり、ItemというエンティティにitemTypeプロパティがあり、enumにバインドする必要があります。これが最善の方法です。

108
Michael Gaylord

値を列挙に制限する場合は、カスタムアクセサーを作成する必要があります。そのため、最初に次のように列挙型を宣言します。

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

この方法で、もっと簡単にできます:

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

@property (nonatomic) Types_t itemType;

そして、モデルでitemTypeを16ビットの数値に設定します。全部できた。追加のコードは必要ありません。いつものように入れて

@dynamic itemType;

Xcodeを使用してNSManagedObjectサブクラスを作成する場合、「プリミティブデータ型にスカラープロパティを使用」設定がチェックされていることを確認してください。

79
Daniel Eggert

私が検討している代替アプローチは、enumをまったく宣言するのではなく、NSNumberのカテゴリメソッドとして値を宣言することです。

22
Mike Abdullah

Mogeneratorを使用している場合は、これをご覧ください: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types 。ユーザー情報のitemTypeの値がattributeValueScalarTypeであるItemというInteger 16属性を持つことができます。次に、エンティティのユーザー情報で、additionalHeaderFileNameItem enumが定義されているヘッダーの名前に設定します。ヘッダーファイルを生成するとき、mogeneratorは自動的にプロパティにItemタイプ。

5
jfla

属性タイプを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
2
malhal

列挙型は標準のshortによってサポートされているため、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];
1
puppybits

以下に貼り付けたコードは私のために機能し、完全な作業例として追加しました。アプリ全体でこのアプローチを広く使用する予定なので、このアプローチについて意見を聞きたいと思います。

  • プロパティで指定されたゲッター/セッターによって満たされるため、@ dynamicをそのまま残しました。

  • IKenndacの回答によると、デフォルトのゲッター/セッター名はオーバーライドしていません。

  • Typedefの有効な値にNSAssertを介していくつかの範囲チェックを含めました。

  • また、特定のtypedefの文字列値を取得するメソッドを追加しました。

  • 定数の前に「k」ではなく「c」を付けます。 "k"(数学の起源、歴史的)の背後にある理由を知っていますが、ESLコードを読んでいるように感じるので、 "c"を使用します。ただ個人的なもの。

同様の質問があります: Coreデータ型としての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
0
ardochhigh

自動生成されたクラスのソリューション

xcodeのコードジェネレーターから(ios 10以降)

「YourClass」という名前のエンティティを作成すると、Xcodeは「Data Model Inspector」でデフォルトのCodegenタイプとして「Class Definition」を自動的に選択します。これにより、以下のクラスが生成されます。

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

Xcodeの「クラス定義」ではなく、Codegenオプションから「Category/Extension」を選択します。

さて、列挙型を追加したい場合は、自動生成されたクラス用に別の拡張機能を作成し、以下のようにここに列挙型定義を追加します。

// 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サブクラスの自動生成をサポートするようになりました。エンティティインスペクターで:

Manual/Noneはデフォルトであり、以前の動作です。この場合、独自のサブクラスを実装するか、NSManagedObjectを使用する必要があります。カテゴリ/拡張機能は、ClassName + CoreDataGeneratedPropertiesのような名前のファイルにクラス拡張機能を生成します。メインクラスを宣言/実装する必要があります(Obj-Cの場合、ヘッダーを介して拡張機能はClassName.hという名前をインポートできます)。クラス定義は、ClassName + CoreDataClassのような名前のサブクラスファイルと、カテゴリ/拡張用に生成されたファイルを生成します。生成されたファイルはDerivedDataに配置され、モデルが保存された後の最初のビルドで再構築されます。また、Xcodeによってインデックスが作成されるため、コマンドキーを押しながら参照をクリックし、ファイル名ですばやく開くことができます。

0
mgyky

私はこれをよくやったので、次のフォームが役に立つとわかりました。

// 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
Chris Conover