วิธีที่ดีที่สุดในการนำ Enums ไปใช้กับ Core Data


109

วิธีใดที่ดีที่สุดในการผูกเอนทิตีข้อมูลหลักกับค่า enum เพื่อให้ฉันสามารถกำหนดคุณสมบัติประเภทให้กับเอนทิตีได้ กล่าวอีกนัยหนึ่งฉันมีเอนทิตีที่เรียกItemด้วยitemTypeคุณสมบัติที่ฉันต้องการผูกมัดกับ enum วิธีที่ดีที่สุดในการดำเนินการนี้คืออะไร

คำตอบ:


130

คุณจะต้องสร้างตัวเข้าถึงที่กำหนดเองหากคุณต้องการ จำกัด ค่าไว้ที่ enum ก่อนอื่นคุณต้องประกาศ enum ดังนี้:

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

จากนั้นประกาศ getters และ setters สำหรับทรัพย์สินของคุณ เป็นความคิดที่ไม่ดีที่จะลบล้างสิ่งที่มีอยู่เนื่องจากตัวเข้าถึงมาตรฐานคาดหวังอ็อบเจ็กต์ NSNumber มากกว่าประเภทสเกลาร์และคุณจะประสบปัญหาหากมีสิ่งใดในการเชื่อมโยงหรือระบบ KVO พยายามและเข้าถึงค่าของคุณ

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

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

สุดท้ายคุณควรใช้+ keyPathsForValuesAffecting<Key>เพื่อให้คุณได้รับการแจ้งเตือน KVO สำหรับ itemTypeRaw เมื่อ itemType เปลี่ยนไป

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

2
ขอบคุณ - ข้อมูลหลักที่แย่เกินไปไม่สนับสนุนสิ่งนี้โดยกำเนิด ฉันหมายถึง: Xcode สร้างไฟล์คลาสทำไมไม่enums?
Constantino Tsarouhas

รหัสสุดท้ายคือหากคุณต้องการสังเกตรายการ itemTypeRaw อย่างไรก็ตามคุณสามารถสังเกต ItemType แทน itemTypeRaw ได้ใช่ไหม?
Anonymous White

2
ด้วย Xcode 4.5 คุณไม่จำเป็นต้องมีสิ่งนี้ ลองดูคำตอบของฉัน คุณเพียงแค่กำหนด enum เป็นint16_tและคุณตั้งค่าไว้
Daniel Eggert

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 Cf clang.llvm.org/docs/…
Daniel Eggert

6
คุณจะหลีกเลี่ยงการสูญเสียรหัสนี้ทุกครั้งที่สร้างคลาสโมเดลใหม่ได้อย่างไร ฉันใช้หมวดหมู่เพื่อให้สามารถสร้างเอนทิตีของโดเมนหลักได้
ร็อบ

2
retainที่เกี่ยวข้องกับการจัดการหน่วยความจำไม่ได้ว่าจะได้รับการจัดเก็บไว้ในฐานข้อมูลหรือไม่
Daniel Eggert

2
ฉันเห็นด้วยกับร็อบ ฉันไม่อยากให้เรื่องนี้ต้องสร้างซ้ำแล้วซ้ำเล่า ฉันชอบหมวดหมู่มากกว่า
Kyle Redfearn

3
@ Rob หมวดหมู่เป็นวิธีที่จะทำมัน แต่คุณยังสามารถใช้ mogenerator: github.com/rentzsch/mogenerator Mogenerator จะสร้าง 2 คลาสต่อเอนทิตีโดยที่คลาสหนึ่งจะถูกเขียนทับเสมอในการเปลี่ยนแปลงโมเดลข้อมูลและคลาสย่อยอื่น ๆ ที่คลาสนั้นสำหรับสิ่งที่กำหนดเองและจะไม่ถูกเขียนทับ
tapmonkey

22

แนวทางอื่นที่ฉันกำลังพิจารณาคือไม่ต้องประกาศ enum เลย แต่เป็นการประกาศค่าเป็นวิธีการหมวดหมู่ใน NSNumber แทน


น่าสนใจ. ดูเหมือนว่าทำได้แน่นอน
Michael Gaylord

ความคิดหลักแหลม! ง่ายกว่าการสร้างตารางใน db มากเว้นแต่ฐานข้อมูลของคุณจะถูกเติมจากบริการเว็บดังนั้นควรใช้ตาราง db!
TheLearner

6
นี่คือตัวอย่างrenovatioboy.wordpress.com/2011/10/06/…
ardochhigh

ฉันชอบมัน. ฉันจะใช้แนวทางนี้ในโครงการของฉัน ฉันชอบที่ฉันสามารถมีข้อมูลเมตาอื่น ๆ ทั้งหมดของฉันเกี่ยวกับข้อมูลเมตาในหมวดหมู่ NSNumber (เช่นการเชื่อมโยงสตริงกับค่า enum)
DonnaLea

ไอเดียเยี่ยมจริงๆ! มีประโยชน์มากสำหรับการเชื่อมโยงตัวระบุสตริงโดยใช้โดยตรงใน JSON, Core Data และอื่น ๆ
Gregarious

5

หากคุณกำลังใช้ mogenerator ดูได้ที่นี้: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types คุณสามารถเรียกแอตทริบิวต์จำนวนเต็ม 16 itemTypeได้โดยมีattributeValueScalarTypeค่าItemเป็นข้อมูลผู้ใช้ จากนั้นในข้อมูลผู้ใช้สำหรับเอนทิตีของคุณให้ตั้งadditionalHeaderFileNameเป็นชื่อของส่วนหัวที่Itemenum กำหนดไว้เมื่อสร้างไฟล์ส่วนหัวของคุณ 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

เนื่องจาก enums ได้รับการสนับสนุนโดย short มาตรฐานคุณจึงไม่สามารถใช้ NSNumber wrapper และตั้งค่าคุณสมบัติเป็นค่าสเกลาร์ได้โดยตรง อย่าลืมตั้งค่าประเภทข้อมูลในโมเดลข้อมูลหลักเป็น "จำนวนเต็ม 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?

ในกรณีนี้ enum ค่อนข้างง่าย:

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

และเรียกมันว่าอวดรู้ แต่ฉันใช้ enums สำหรับชื่อฟิลด์เช่นนี้:

public enum Field:String {

    case Account = "account"
}

เนื่องจากสิ่งนี้อาจยุ่งยากสำหรับโมเดลข้อมูลที่ซับซ้อนฉันจึงเขียนตัวสร้างโค้ดที่ใช้ MOM / เอนทิตีเพื่อคายการแมปทั้งหมด อินพุตของฉันกลายเป็นพจนานุกรมจาก Table / Row เป็น Enum type ในขณะที่ฉันอยู่ที่นั่นฉันก็สร้างรหัสซีเรียลไลเซชัน JSON ด้วย ฉันได้ทำสิ่งนี้สำหรับโมเดลที่ซับซ้อนมากและมันกลายเป็นการประหยัดครั้งใหญ่


0

รหัสที่วางด้านล่างใช้ได้ผลสำหรับฉันและฉันได้เพิ่มเป็นตัวอย่างการทำงานเต็มรูปแบบ ฉันต้องการรับฟังความคิดเห็นเกี่ยวกับแนวทางนี้เนื่องจากฉันวางแผนที่จะใช้มันอย่างกว้างขวางตลอดทั้งแอปของฉัน

  • ฉันได้ทิ้ง @dynamic ไว้แล้วเนื่องจากเป็นที่พอใจของ getter / setter ที่มีชื่อในคุณสมบัติ

  • ตามคำตอบของ iKenndac ฉันไม่ได้ลบล้างชื่อ getter / setter เริ่มต้น

  • ฉันได้รวมการตรวจสอบช่วงผ่าน NSAssert ในค่าที่ถูกต้องของ typedef

  • ฉันยังได้เพิ่มวิธีการรับค่าสตริงสำหรับ typedef ที่กำหนด

  • ฉันเติมค่าคงที่ด้วย "c" แทนที่จะเป็น "k" ฉันรู้เหตุผลเบื้องหลัง "k" (ต้นกำเนิดทางคณิตศาสตร์ประวัติศาสตร์) แต่รู้สึกว่าฉันกำลังอ่านรหัส ESL ด้วยดังนั้นฉันจึงใช้ "c" แค่เรื่องส่วนตัว.

มีคำถามที่คล้ายกันที่นี่: 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

โซลูชันสำหรับคลาสที่สร้างขึ้นโดยอัตโนมัติ

จาก Code Generator ของ Xcode (ios 10 ขึ้นไป)

หากคุณสร้างเอนทิตีชื่อ "YourClass" Xcode จะเลือก "คำจำกัดความของคลาส" โดยอัตโนมัติเป็นค่าเริ่มต้นประเภท Codegen ที่ "Data Model Inspector" สิ่งนี้จะสร้างคลาสด้านล่าง:

เวอร์ชัน 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

เราจะเลือก "หมวดหมู่ / ส่วนขยาย" จากตัวเลือก Codegen แทน "Class Definition" ใน Xcode

ตอนนี้หากเราต้องการเพิ่ม enum ให้ไปและสร้างส่วนขยายอื่นสำหรับคลาสที่สร้างขึ้นโดยอัตโนมัติของคุณและเพิ่มคำจำกัดความ enum ของคุณที่นี่ดังนี้:

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

ตอนนี้คุณสามารถสร้างตัวเข้าถึงที่กำหนดเองได้หากคุณต้องการ จำกัด ค่าไว้ที่ enum โปรดตรวจสอบคำตอบที่ได้รับการยอมรับโดยเจ้าของคำถาม หรือคุณสามารถแปลง enums ของคุณในขณะที่คุณตั้งค่าด้วยวิธีการแปลงอย่างชัดเจนโดยใช้ตัวดำเนินการ cast ดังต่อไปนี้:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

ตรวจสอบด้วย

การสร้างคลาสย่อยอัตโนมัติ Xcode

ขณะนี้ Xcode รองรับคลาสย่อย NSManagedObject ในเครื่องมือการสร้างแบบจำลองโดยอัตโนมัติ ในตัวตรวจสอบเอนทิตี:

คู่มือ / ไม่มีเป็นค่าเริ่มต้นและพฤติกรรมก่อนหน้านี้ ในกรณีนี้คุณควรใช้คลาสย่อยของคุณเองหรือใช้ NSManagedObject ประเภท / ส่วนขยายสร้างส่วนขยายคลาสในไฟล์ชื่อ ClassName + CoreDataGeneratedProperties คุณต้องประกาศ / ใช้งานคลาสหลัก (ถ้าอยู่ใน Obj-C ผ่านส่วนหัวส่วนขยายสามารถนำเข้าชื่อ ClassName.h) นิยามคลาสสร้างไฟล์คลาสย่อยที่ชื่อ ClassName + CoreDataClass เช่นเดียวกับไฟล์ที่สร้างขึ้นสำหรับ Category / Extension ไฟล์ที่สร้างขึ้นจะถูกวางไว้ใน DerivedData และสร้างขึ้นใหม่ในบิลด์แรกหลังจากบันทึกโมเดลแล้ว นอกจากนี้ยังมีการจัดทำดัชนีโดย Xcode ดังนั้นการคลิกคำสั่งที่การอ้างอิงและการเปิดอย่างรวดเร็วตามชื่อไฟล์จึงใช้งานได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.