Beste Möglichkeit, Enums mit Kerndaten zu implementieren

109

Was ist der beste Weg, um Core Data-Entitäten an Aufzählungswerte zu binden, damit ich der Entität eine Typeigenschaft zuweisen kann? Mit anderen Worten, ich habe eine Entität Itemmit einer itemTypeEigenschaft, die an eine Aufzählung gebunden werden soll. Was ist der beste Weg, dies zu tun?

Michael Gaylord
quelle

Antworten:

130

Sie müssen benutzerdefinierte Accessoren erstellen, wenn Sie die Werte auf eine Aufzählung beschränken möchten. Zuerst deklarieren Sie eine Aufzählung wie folgt:

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

Dann deklarieren Sie Getter und Setter für Ihr Eigentum. Es ist eine schlechte Idee, die vorhandenen zu überschreiben, da die Standard-Accessoren eher ein NSNumber-Objekt als einen skalaren Typ erwarten und Sie auf Probleme stoßen, wenn irgendetwas in den Bindungen oder KVO-Systemen versucht, auf Ihren Wert zuzugreifen.

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

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

Schließlich sollten Sie implementieren, + keyPathsForValuesAffecting<Key>damit Sie KVO-Benachrichtigungen für itemTypeRaw erhalten, wenn sich itemType ändert.

+ (NSSet *)keyPathsForValuesAffectingItemTypeRaw {
    return [NSSet setWithObject:@"itemType"];
}
iKenndac
quelle
2
Vielen Dank - schade, dass Core Data dies nicht von Haus aus unterstützt. Ich meine: Xcode generiert Klassendateien, warum nicht enums?
Constantino Tsarouhas
Der letzte Code ist, wenn Sie item itemTypeRaw beobachten möchten. Sie können jedoch einfach item itemType anstelle von itemTypeRaw beobachten, oder?
Anonym Weiß
2
Mit Xcode 4.5 brauchen Sie nichts davon. Schauen Sie sich meine Antwort an. Sie müssen nur die Aufzählung als definieren int16_tund schon sind Sie fertig.
Daniel Eggert
79

Sie können dies viel einfacher machen:

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

@property (nonatomic) Types_t itemType;

Stellen Sie in Ihrem Modell itemTypeeine 16-Bit-Zahl ein. Alles erledigt. Kein zusätzlicher Code erforderlich. Gib einfach dein übliches ein

@dynamic itemType;

Wenn Sie Xcode zum Erstellen Ihrer NSManagedObjectUnterklasse verwenden, stellen Sie sicher, dass die Einstellung " Skalareigenschaften für primitive Datentypen verwenden " aktiviert ist.

Daniel Eggert
quelle
4
Nein, das hat nichts mit C ++ 11 zu tun. Es ist Teil von Clang 3.3, das Aufzählungen mit einem festen zugrunde liegenden Typ für ObjC unterstützt. Vgl. Clang.llvm.org/docs/…
Daniel Eggert
6
Wie vermeiden Sie, dass dieser Code jedes Mal verloren geht, wenn Sie die Modellklasse neu generieren? Ich habe Kategorien verwendet, damit die Kerndomänenentitäten neu generiert werden können.
Rob
2
Das retainhängt mit der Speicherverwaltung zusammen, nicht damit, ob es in der Datenbank gespeichert wird oder nicht.
Daniel Eggert
2
Ich stimme Rob zu. Ich möchte nicht, dass dies immer wieder regeneriert werden muss. Ich bevorzuge die Kategorie.
Kyle Redfearn
3
@Rob Categories ist eine Möglichkeit, dies zu tun. Stattdessen können Sie auch mogenerator verwenden: github.com/rentzsch/mogenerator . Mogenerator generiert 2 Klassen pro Entität, wobei eine Klasse bei Datenmodelländerungen immer überschrieben wird und die anderen Unterklassen diese Klasse für benutzerdefinierte Inhalte und niemals überschrieben werden.
Tapmonkey
22

Ein alternativer Ansatz, den ich in Betracht ziehe, besteht darin, überhaupt keine Aufzählung zu deklarieren, sondern die Werte stattdessen als Kategoriemethoden in NSNumber zu deklarieren.

Mike Abdullah
quelle
Interessant. Es scheint definitiv machbar.
Michael Gaylord
Brilliante Idee! So viel einfacher als das Erstellen von Tabellen in der Datenbank. Wenn Ihre Datenbank nicht von einem Webdienst ausgefüllt wird, ist es wahrscheinlich am besten, eine Datenbank-Tabelle zu verwenden!
TheLearner
6
Hier ist ein Beispiel: renovatioboy.wordpress.com/2011/10/06/…
ardochhigh
Ich mag das. Ich werde diesen Ansatz in meinem Projekt verwenden. Mir gefällt, dass ich auch alle meine anderen Metainformationen zu den Metadaten in der Kategorie NSNumber enthalten kann. (dh Verknüpfen von Zeichenfolgen mit den Enum-Werten)
DonnaLea
Wirklich tolle Idee! Sehr nützlich für die Zuordnung von Zeichenfolgenkennungen, direkt in JSON, Core Data usw.
Gregarious
5

Wenn Sie Mogenerator verwenden, sehen Sie sich dies an: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Sie können ein Integer 16-Attribut itemTypemit dem attributeValueScalarTypeWert Itemin den Benutzerinformationen aufrufen lassen. Stellen Sie dann in den Benutzerinformationen für Ihre Entität additionalHeaderFileNameden Namen des Headers ein, in dem die ItemAufzählung definiert ist. Beim Generieren Ihrer Header-Dateien lässt mogenerator die Eigenschaft automatisch den ItemTyp haben.

jfla
quelle
2

Ich setze den Attributtyp als 16-Bit-Ganzzahl und verwende dann Folgendes:

#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
malhal
quelle
1

Da Aufzählungen durch einen Standardkurzschluss unterstützt werden, können Sie den NSNumber-Wrapper auch nicht verwenden und die Eigenschaft direkt als Skalarwert festlegen. Stellen Sie sicher, dass der Datentyp im Kerndatenmodell "Integer 32" ist.

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

An anderer Stelle im Code

myEntityInstance.coreDataEnumStorage = kEnumThing;

Oder Parsen aus einer JSON-Zeichenfolge oder Laden aus einer Datei

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];
Welpenbits
quelle
1

Ich habe dies viel getan und finde das folgende Formular nützlich:

// 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 diesem Fall ist die Aufzählung ziemlich einfach:

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

und nenne es pedantisch, aber ich benutze Aufzählungen für Feldnamen wie diese:

public enum Field:String {

    case Account = "account"
}

Da dies für komplexe Datenmodelle mühsam werden kann, habe ich einen Codegenerator geschrieben, der die MOM / Entitäten verwendet, um alle Zuordnungen auszuspucken. Meine Eingaben sind letztendlich ein Wörterbuch vom Typ Tabelle / Zeile bis Aufzählung. Während ich dabei war, habe ich auch JSON-Serialisierungscode generiert. Ich habe dies für sehr komplexe Modelle getan und es hat sich als große Zeitersparnis herausgestellt.

Chris Conover
quelle
0

Der unten eingefügte Code funktioniert für mich und ich habe ihn als voll funktionsfähiges Beispiel hinzugefügt. Ich würde gerne Meinungen zu diesem Ansatz hören, da ich beabsichtige, ihn in meinen Apps ausgiebig zu nutzen.

  • Ich habe die @dynamic an Ort und Stelle belassen, da sie dann von dem in der Eigenschaft genannten Getter / Setter erfüllt wird.

  • Gemäß der Antwort von iKenndac habe ich die Standardnamen für Getter / Setter nicht überschrieben.

  • Ich habe einige Bereichsüberprüfungen über einen NSAssert für die gültigen Werte von typedef eingefügt.

  • Ich habe auch eine Methode hinzugefügt, um einen Zeichenfolgenwert für das angegebene typedef zu erhalten.

  • Ich stelle Konstanten "c" anstelle von "k" voran. Ich kenne die Gründe für "k" (mathematische Ursprünge, historisch), aber es fühlt sich an, als würde ich ESL-Code damit lesen, also verwende ich "c". Nur eine persönliche Sache.

Hier gibt es eine ähnliche Frage: typedef als Core-Datentyp

Ich würde mich über jeden Beitrag zu diesem Ansatz freuen.

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
ardochhigh
quelle
0

Lösung für automatisch generierte Klassen

vom Code-Generator von Xcode (ios 10 und höher)

Wenn Sie eine Entität mit dem Namen "YourClass" erstellen, wählt Xcode automatisch "Klassendefinition" als Standard-Codegen-Typ bei "Data Model Inspector". Dadurch werden die folgenden Klassen generiert:

Schnelle 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

Wir wählen "Kategorie / Erweiterung" aus der Codegen-Option anstelle von "Klassendefinition" in Xcode.

Wenn wir nun eine Aufzählung hinzufügen möchten, erstellen Sie eine weitere Erweiterung für Ihre automatisch generierte Klasse und fügen Sie Ihre Aufzählungsdefinitionen hier wie folgt hinzu:

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

Jetzt können Sie benutzerdefinierte Accessoren erstellen, wenn Sie die Werte auf eine Aufzählung beschränken möchten. Bitte überprüfen Sie die akzeptierte Antwort des Frageninhabers . Oder Sie können Ihre Aufzählungen konvertieren, während Sie sie mit der expliziten Konvertierungsmethode mithilfe des folgenden Darstellungsoperators festlegen:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Überprüfen Sie auch

Xcode automatische Unterklassengenerierung

Xcode unterstützt jetzt die automatische Generierung von NSManagedObject-Unterklassen im Modellierungswerkzeug. Im Entitätsinspektor:

Manuell / Keine ist die Standardeinstellung und das vorherige Verhalten. In diesem Fall sollten Sie Ihre eigene Unterklasse implementieren oder NSManagedObject verwenden. Kategorie / Erweiterung generiert eine Klassenerweiterung in einer Datei mit dem Namen ClassName + CoreDataGeneratedProperties. Sie müssen die Hauptklasse deklarieren / implementieren (wenn in Obj-C die Erweiterung über einen Header den Namen ClassName.h importieren kann). Die Klassendefinition generiert Unterklassendateien mit dem Namen ClassName + CoreDataClass sowie die für Category / Extension generierten Dateien. Die generierten Dateien werden in DerivedData abgelegt und beim ersten Build nach dem Speichern des Modells neu erstellt. Sie werden auch durch Xcode indiziert, sodass das Klicken mit der Befehlsnummer auf Referenzen und das schnelle Öffnen nach Dateiname funktioniert.

mgyky
quelle