Najlepszy sposób na wdrożenie wyliczeń z danymi podstawowymi

109

Jaki jest najlepszy sposób powiązania jednostek danych podstawowych z wartościami wyliczeniowymi, aby można było przypisać właściwość typu do jednostki? Innymi słowy, mam obiekt nazwany Itemz itemTypewłaściwością, który chcę być powiązany z wyliczeniem, jaki jest najlepszy sposób na zrobienie tego.

Michael Gaylord
źródło

Odpowiedzi:

130

Jeśli chcesz ograniczyć wartości do wyliczenia, musisz utworzyć niestandardowe metody dostępu. Więc najpierw zadeklarowałbyś wyliczenie, na przykład:

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

Następnie zadeklaruj metody pobierające i ustawiające dla swojej właściwości. To zły pomysł, aby zastąpić istniejące, ponieważ standardowe akcesory oczekują obiektu NSNumber, a nie typu skalarnego, i będziesz miał kłopoty, jeśli cokolwiek w powiązaniach lub systemach KVO spróbuje uzyskać dostęp do twojej wartości.

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

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

Na koniec należy wdrożyć, + keyPathsForValuesAffecting<Key>aby otrzymywać powiadomienia KVO dla itemTypeRaw, gdy itemType się zmieni.

+ (NSSet *)keyPathsForValuesAffectingItemTypeRaw {
    return [NSSet setWithObject:@"itemType"];
}
iKenndac
źródło
2
Dziękuję - szkoda, że ​​podstawowe dane nie obsługują tego natywnie. Mam na myśli: Xcode generuje pliki klas, dlaczego nie enum?
Constantino Tsarouhas
Ostatni kod dotyczy sytuacji, w której chcesz obserwować element itemTypeRaw. Możesz jednak po prostu obserwować element itemType zamiast itemTypeRaw, prawda?
Anonimowy biały
2
Dzięki Xcode 4.5 nie potrzebujesz tego wszystkiego. Spójrz na moją odpowiedź. Wystarczy zdefiniować wyliczenie jako an int16_ti gotowe.
Daniel Eggert
79

Możesz to zrobić w ten sposób, o wiele prostsze:

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

@property (nonatomic) Types_t itemType;

A w swoim modelu ustaw itemTypeliczbę 16-bitową. Gotowe. Nie potrzeba dodatkowego kodu. Po prostu włóż swój zwykły

@dynamic itemType;

Jeśli używasz Xcode do tworzenia NSManagedObjectpodklasy, upewnij się, że jest zaznaczone ustawienie „ Użyj właściwości skalarnych dla pierwotnych typów danych ”.

Daniel Eggert
źródło
4
Nie, to nie ma nic wspólnego z C ++ 11. Jest częścią clang 3.3 obsługującego wyliczenia ze stałym typem bazowym dla ObjC. Por. Clang.llvm.org/docs/…
Daniel Eggert
6
Jak uniknąć utraty tego kodu za każdym razem, gdy ponownie generujesz klasę modelu? Korzystałem z kategorii, aby można było ponownie wygenerować podstawowe jednostki domeny.
Rob
2
Jest retainto związane z zarządzaniem pamięcią, a nie z tym, czy jest ona zapisywana w bazie danych, czy nie.
Daniel Eggert,
2
Zgadzam się z Robem. Nie chcę, aby to musiało być ciągle regenerowane. Wolę tę kategorię.
Kyle Redfearn
3
@Rob Categories to sposób na zrobienie tego, ale zamiast tego możesz również użyć mogenerator: github.com/rentzsch/mogenerator . Mogenerator wygeneruje 2 klasy na jednostkę, przy czym jedna klasa będzie zawsze nadpisywana po zmianie modelu danych, a inne podklasy tej klasy na potrzeby niestandardowych elementów i nigdy nie zostaną nadpisane.
tapmonkey
22

Alternatywnym podejściem, które rozważam, nie jest deklarowanie wyliczenia w ogóle, ale zamiast tego deklarowanie wartości jako metod kategorii w NSNumber.

Mike Abdullah
źródło
Ciekawy. Zdecydowanie wydaje się to wykonalne.
Michael Gaylord
genialny pomysł! o wiele łatwiejsze niż tworzenie tabel w bazie danych, chyba że baza danych jest wypełniona z usługi internetowej, wtedy prawdopodobnie najlepiej jest użyć tabeli db!
TheLearner
6
Oto przykład: renovatioboy.wordpress.com/2011/10/06/...
ardochhigh
Lubię to. Zamierzam zastosować to podejście w swoim projekcie. Podoba mi się, że mogę również zawrzeć wszystkie moje inne metadane dotyczące metadanych w kategorii NSNumber. (tj. łączenie ciągów z wartościami wyliczenia)
DonnaLea
Naprawdę świetny pomysł! Bardzo przydatne do kojarzenia identyfikatorów ciągów, używania bezpośrednio w JSON, Core Data itp.
Gregarious,
5

Jeśli używasz mogeneratora, spójrz na to: https://github.com/rentzsch/mogenerator/wiki/Using-enums-as-types . Możesz wywołać atrybut Integer 16 itemTypez attributeValueScalarTypewartością Itemw informacji o użytkowniku. Następnie w informacjach o użytkowniku dla Twojej encji ustaw additionalHeaderFileNamenazwę nagłówka, w którym Itemjest zdefiniowane wyliczenie. Podczas generowania plików nagłówkowych mogenerator automatycznie ustawi właściwość na Itemtyp.

jfla
źródło
2

Ustawiam typ atrybutu na 16-bitową liczbę całkowitą, a następnie używam tego:

#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
źródło
1

Ponieważ wyliczenia są obsługiwane przez standardowy skrót, nie można również użyć opakowania NSNumber i ustawić właściwości bezpośrednio jako wartość skalarną. Upewnij się, że typ danych w podstawowym modelu danych to „Integer 32”.

MyEntity.h

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

@interface myEntity : NSManagedObject

@property (nonatomic) int32_t coreDataEnumStorage;

W innym miejscu w kodzie

myEntityInstance.coreDataEnumStorage = kEnumThing;

Lub parsowanie z ciągu JSON lub ładowanie z pliku

myEntityInstance.coreDataEnumStorage = [myStringOfAnInteger intValue];
szczeniaki
źródło
1

Robiłem to często i uważam, że poniższy formularz jest przydatny:

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

W tym przypadku wyliczenie jest dość proste:

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

i nazywam to pedantycznym, ale używam wyliczeń do nazw pól, na przykład:

public enum Field:String {

    case Account = "account"
}

Ponieważ może to być pracochłonne w przypadku złożonych modeli danych, napisałem generator kodu, który zużywa MOM / jednostki, aby wypluć wszystkie mapowania. Moje dane wejściowe kończą się jako słownik od typu Table / Row do Enum. W tym czasie wygenerowałem również kod serializacji JSON. Zrobiłem to dla bardzo złożonych modeli i okazało się, że to duża oszczędność czasu.

Chris Conover
źródło
0

Kod wklejony poniżej działa dla mnie i dodałem go jako pełny przykład roboczy. Chciałbym poznać opinie na temat tego podejścia, ponieważ planuję szeroko stosować je w moich aplikacjach.

  • Zostawiłem @dynamic na miejscu, ponieważ jest wtedy spełniany przez getter / setter nazwany we właściwości.

  • Zgodnie z odpowiedzią udzieloną przez iKenndac, nie nadpisałem domyślnych nazw getter / setter.

  • Dołączyłem sprawdzanie zakresu za pośrednictwem NSAssert na poprawnych wartościach typedef.

  • Dodałem również metodę uzyskiwania wartości ciągu dla podanego typu.

  • Stałe poprzedzam znakiem „c” zamiast „k”. Znam powód „k” (pochodzenie matematyczne, historyczne), ale wydaje mi się, że czytam nim kod ESL, więc używam litery „c”. Po prostu sprawa osobista.

Jest tu podobne pytanie: typedef jako typ danych Core

Byłbym wdzięczny za wszelkie uwagi dotyczące tego podejścia.

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
źródło
0

Rozwiązanie dla klas generowanych automatycznie

z generatora kodu Xcode (ios 10 i nowsze)

Jeśli utworzysz jednostkę o nazwie „YourClass”, Xcode automatycznie wybierze „Class Definition” jako domyślny typ Codegen w „Data Model Inspector”. spowoduje to wygenerowanie poniższych klas:

Wersja Swift:

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

Wersja Objective-C:

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

  #import "YourClass+CoreDataProperties.h"

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

Z opcji Codegen wybierzemy „Category / Extension” zamiast „Class Definition” w Xcode.

Teraz, jeśli chcemy dodać wyliczenie, przejdź i utwórz kolejne rozszerzenie dla automatycznie wygenerowanej klasy i dodaj tutaj definicje wyliczenia, jak poniżej:

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

Teraz możesz tworzyć niestandardowe metody dostępu, jeśli chcesz ograniczyć wartości do wyliczenia. Sprawdź zaakceptowaną odpowiedź właściciela pytania . Lub możesz przekonwertować swoje wyliczenia, ustawiając je za pomocą jawnej metody konwersji, używając operatora rzutowania, jak poniżej:

model.yourEnumProperty = (int16_t)YourEnumTypeStarted;

Sprawdź też

Automatyczne generowanie podklas Xcode

Xcode obsługuje teraz automatyczne generowanie podklas NSManagedObject w narzędziu do modelowania. W inspektorze podmiotowym:

Ręczny / Brak jest domyślnym i poprzednim zachowaniem; w takim przypadku należy zaimplementować własną podklasę lub użyć NSManagedObject. Kategoria / rozszerzenie generuje rozszerzenie klasy w pliku o nazwie ClassName + CoreDataGeneratedProperties. Musisz zadeklarować / zaimplementować główną klasę (jeśli w Obj-C, poprzez nagłówek rozszerzenie może importować o nazwie ClassName.h). Definicja klasy generuje pliki podklas o nazwie ClassName + CoreDataClass, a także pliki wygenerowane dla kategorii / rozszerzenia. Wygenerowane pliki są umieszczane w DerivedData i odbudowywane przy pierwszej kompilacji po zapisaniu modelu. Są również indeksowane przez Xcode, więc klikanie poleceń i szybkie otwieranie według nazwy pliku działa.

mgyky
źródło