Cara terbaik untuk mengimplementasikan Enums dengan Core Data


109

Apa cara terbaik untuk mengikat entitas Data Inti ke nilai enum sehingga saya dapat menetapkan properti tipe ke entitas? Dengan kata lain, saya memiliki entitas yang disebut Itemdengan itemTypeproperti yang ingin saya ikat ke enum, apa cara terbaik untuk melakukannya.

Jawaban:


130

Anda harus membuat pengakses khusus jika ingin membatasi nilai ke enum. Jadi, pertama-tama Anda akan mendeklarasikan enum, seperti:

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

Kemudian, deklarasikan pengambil dan penyetel untuk properti Anda. Ide buruk untuk menimpa yang sudah ada, karena pengakses standar mengharapkan objek NSNumber daripada jenis skalar, dan Anda akan mengalami masalah jika ada yang ada di binding atau sistem KVO mencoba dan mengakses nilai Anda.

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

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

Terakhir, Anda harus mengimplementasikan + keyPathsForValuesAffecting<Key>agar Anda mendapatkan notifikasi KVO untuk itemTypeRaw saat itemType berubah.

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

2
Terima kasih - Core Data yang terlalu buruk tidak mendukung ini secara native. Maksud saya: Xcode menghasilkan file kelas, mengapa tidak enums?
Constantino Tsarouhas

Kode terakhir adalah jika Anda ingin mengamati item itemTypeRaw. Namun, Anda cukup mengamati item itemType daripada itemTypeRaw bukan?
Anonymous White

2
Dengan Xcode 4.5 Anda tidak membutuhkan semua ini. Lihatlah jawaban saya. Anda hanya perlu mendefinisikan enum sebagai int16_tdan Anda sudah siap.
Daniel Eggert

79

Anda dapat melakukan ini dengan cara yang lebih sederhana:

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

@property (nonatomic) Types_t itemType;

Dan dalam model Anda, setel itemTypemenjadi angka 16 bit. Semua selesai. Tidak perlu kode tambahan. Letakkan saja seperti biasa

@dynamic itemType;

Jika Anda menggunakan Xcode untuk membuat NSManagedObjectsubclass Anda , pastikan bahwa setelan " gunakan properti skalar untuk tipe data primitif " dicentang.


4
Tidak, ini tidak ada hubungannya dengan C ++ 11. Ini adalah bagian dari clang 3.3 yang mendukung Pencacahan dengan tipe dasar tetap untuk ObjC. Cf clang.llvm.org/docs/…
Daniel Eggert

6
Bagaimana Anda menghindari kehilangan kode ini setiap kali Anda membuat ulang kelas model? Saya telah menggunakan Kategori sehingga entitas domain inti dapat dibuat ulang.
Rob

2
Ini retainterkait dengan manajemen memori, bukan apakah itu disimpan ke dalam database atau tidak.
Daniel Eggert

2
Saya setuju dengan Rob. Saya tidak ingin ini harus diperbarui lagi dan lagi. Saya lebih suka kategorinya.
Kyle Redfearn

3
Kategori @Rob adalah cara untuk melakukannya, tetapi Anda juga dapat menggunakan mogenerator: github.com/rentzsch/mogenerator . Mogenerator akan menghasilkan 2 kelas per entitas, di mana satu kelas akan selalu ditimpa pada perubahan model data dan subkelas lainnya kelas itu untuk barang kustom dan tidak pernah ditimpa.
tapmonkey

22

Pendekatan alternatif yang saya pertimbangkan bukanlah mendeklarasikan enum sama sekali, melainkan mendeklarasikan nilai sebagai metode kategori di NSNumber.


Menarik. Sepertinya itu bisa dilakukan.
Michael Gaylord

ide cemerlang! jauh lebih mudah daripada membuat tabel di db, kecuali jika db Anda diisi dari layanan web maka mungkin yang terbaik adalah menggunakan tabel db!
TheLearner


Saya suka itu. Saya akan menggunakan pendekatan ini dalam proyek saya. Saya suka bahwa saya juga bisa berisi semua informasi meta saya yang lain tentang meta data dalam kategori NSNumber. (yaitu menghubungkan string ke nilai enum)
DonnaLea

Ide yang sangat bagus! Sangat berguna untuk mengaitkan pengenal string, menggunakan langsung di JSON, Data Inti, dll.
Bergaul

5

Jika Anda menggunakan mogenerator, lihat ini: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Anda dapat memiliki atribut Integer 16 yang dipanggil itemType, dengan attributeValueScalarTypenilai Itemdi info pengguna. Kemudian, di info pengguna untuk entitas Anda, setel additionalHeaderFileNameke nama header tempat Itemenum didefinisikan masuk Saat membuat file header Anda, mogenerator secara otomatis akan membuat properti memiliki Itemtipe.


2

Saya mengatur tipe atribut sebagai integer 16 bit kemudian menggunakan ini:

#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

Karena enum didukung oleh short standar, Anda juga tidak dapat menggunakan pembungkus NSNumber dan menyetel properti secara langsung sebagai nilai skalar. Pastikan untuk menyetel jenis data dalam model data inti sebagai "Integer 32".

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

Di tempat lain dalam kode

myEntityInstance.coreDataEnumStorage = kEnumThing;

Atau mengurai dari string JSON atau memuat dari file

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];

1

Saya telah sering melakukan ini dan merasa formulir berikut berguna:

// 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?

Dalam hal ini, enumnya cukup sederhana:

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

dan menyebutnya pedantic, tapi saya menggunakan enum untuk nama field, seperti ini:

public enum Field:String {

    case Account = "account"
}

Karena ini bisa melelahkan untuk model data yang kompleks, saya menulis generator kode yang menggunakan MOM / entitas untuk mengeluarkan semua pemetaan. Masukan saya akhirnya menjadi kamus dari Tabel / Baris ke tipe Enum. Saat saya melakukannya, saya juga membuat kode serialisasi JSON. Saya telah melakukan ini untuk model yang sangat kompleks dan ternyata sangat menghemat waktu.


0

Kode yang ditempel di bawah berfungsi untuk saya, dan saya telah menambahkannya sebagai contoh yang berfungsi penuh. Saya ingin mendengar opini tentang pendekatan ini, karena saya berencana untuk menggunakannya secara luas di seluruh aplikasi saya.

  • Saya telah meninggalkan @dynamic di tempatnya, karena kemudian dipuaskan oleh pengambil / penyetel yang disebutkan dalam properti.

  • Sesuai jawaban oleh iKenndac, saya belum mengganti nama pengambil / penyetel default.

  • Saya telah menyertakan beberapa pemeriksaan rentang melalui NSAssert pada nilai valid typedef.

  • Saya juga menambahkan metode untuk mendapatkan nilai string untuk typedef yang diberikan.

  • Saya mengawali konstanta dengan "c" bukan "k". Saya tahu alasan di balik "k" (asal mula matematika, historis), tetapi rasanya seperti saya membaca kode ESL dengannya, jadi saya menggunakan "c". Hanya masalah pribadi.

Ada pertanyaan serupa di sini: typedef sebagai tipe data Inti

Saya akan menghargai masukan apa pun tentang pendekatan ini.

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

Solusi untuk Kelas yang Dibuat Otomatis

dari Generator Kode Xcode (ios 10 dan lebih tinggi)

Jika Anda membuat Entitas dengan nama "KelasAnda", Xcode secara otomatis akan memilih "Definisi Kelas" sebagai default jenis Codegen di "Inspektur Model Data". ini akan menghasilkan kelas-kelas di bawah ini:

Versi Swift:

// YourClass+CoreDataClass.swift
  @objc(YourClass)
  public class YourClass: NSManagedObject {
  }

Versi Objective-C:

// YourClass+CoreDataClass.h
  @interface YourClass : NSManagedObject
  @end

  #import "YourClass+CoreDataProperties.h"

  // YourClass+CoreDataClass.m
  #import "YourClass+CoreDataClass.h"
  @implementation YourClass
  @end

Kami akan memilih "Kategori / Ekstensi" dari opsi Codegen daripada "Definisi Kelas" di Xcode.

Sekarang, Jika kita ingin menambahkan enum, buka dan buat ekstensi lain untuk kelas yang dibuat secara otomatis, dan tambahkan definisi enum Anda di sini seperti di bawah ini:

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

Sekarang, Anda dapat membuat pengakses kustom jika Anda ingin membatasi nilai ke enum. Silakan periksa jawaban yang diterima oleh pemilik pertanyaan . Atau Anda dapat mengonversi enum Anda saat Anda mengaturnya dengan metode konversi eksplisit menggunakan operator cast seperti di bawah ini:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Periksa juga

Pembuatan subclass otomatis Xcode

Xcode sekarang mendukung pembuatan subkelas NSManagedObject secara otomatis dalam alat pemodelan. Di inspektur entitas:

Manual / Tidak Ada adalah default, dan perilaku sebelumnya; dalam hal ini, Anda harus mengimplementasikan subclass Anda sendiri atau menggunakan NSManagedObject. Kategori / Ekstensi menghasilkan ekstensi kelas dalam file bernama seperti ClassName + CoreDataGeneratedProperties. Anda perlu mendeklarasikan / mengimplementasikan kelas utama (jika di Obj-C, melalui header ekstensi dapat mengimpor bernama ClassName.h). Definisi Kelas menghasilkan file subkelas bernama seperti ClassName + CoreDataClass serta file yang dihasilkan untuk Kategori / Ekstensi. File yang dihasilkan ditempatkan di DerivedData dan dibangun kembali pada build pertama setelah model disimpan. Mereka juga diindeks oleh Xcode, jadi klik perintah pada referensi dan buka cepat dengan nama file berfungsi.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.