2015-01-08 16 views
6

mam rozszerzenia przez około 20 teksty stałe, które wyglądają tak:Jak uprościć niemal równe rozszerzeń enum w Swift

extension CurrencyValue : JSONDecodable { 
    static func create(rawValue: String) -> CurrencyValue { 
     if let value = CurrencyValue(rawValue: rawValue) { 
      return value 
     } 
     return .unknown 
    } 

    static func decode(j: JSONValue) -> CurrencyValue? { 
     return CurrencyValue.create <^> j.value() 
    } 
} 

extension StatusValue : JSONDecodable { 
    static func create(rawValue: String) -> StatusValue { 
     if let value = StatusValue(rawValue: rawValue) { 
      return value 
     } 
     return .unknown 
    } 

    static func decode(j: JSONValue) -> StatusValue? { 
     return StatusValue.create <^> j.value() 
    } 
} 

Są prawie takie same, z wyjątkiem nazwy typu enum i mam 20 z nich - który jest oczywiście bardzo głupi. Czy ktoś ma pomysł, jak zmniejszyć je do jednego, może używając generycznych? W tej chwili nie mam pojęcia.

UPDATE

Te teksty stałe są tak proste, jak to:

enum CurrencyValue : String { 
    case EUR = "EUR" 
    case unknown = "unknown" 
} 

enum StatusValue : String { 
    case ok = "ok" 
    case pending = "pending" 
    case error = "error" 
    case unknown = "unknown" 
} 

i pozwala przyjęto następujące założenia:

  1. Każdy ENUM ma .unknown sprawę
  2. muszę wymieniać typ wyliczeniowy w rozszerzeniu na coś ogólnego.

Musi istnieć jakaś sztuczka, aby nie zaimplementować tego samego rozszerzenia wiele razy i po prostu zmienić jego typ na.

UPDATE

Jak y stwierdził Gregory Higley poniżej użyć lib JSON Argo. Możesz przeczytać o tamtejszych operatorach.

+1

Pomocny może być * samodzielny * przykład. –

Odpowiedz

3

Istotą Twoje pytanie brzmi, że chcesz uniknąć pisania kodu standardowego dla wszystkich tych wyliczeń podczas implementacji Argo 's JSONDecodable. Wygląda na to, jakbyś też dodał create metodę, która nie jest częścią podpisu typu JSONDecodable:

public protocol JSONDecodable { 
    typealias DecodedType = Self 
    class func decode(JSONValue) -> DecodedType? 
} 

Niestety nie można tego zrobić. Protokoły SWIFT nie są miksami. Z wyjątkiem operatorów, nie mogą zawierać żadnego kodu. (Mam nadzieję, że to robi się „stały” w przyszłej aktualizacji Swift przeciążać implementacje domyślne dla protokołów byłoby niesamowite.).

Można oczywiście uproszczenie implementacji w kilka sposobów:

  1. As Tony DiPasquale zasugerował, pozbyć się .unknown i użyć opcji. (Powinieneś był również nazwać to .Unknown. Konwencja dla wartości wyliczeniowych Swift polega na uruchomieniu ich wielką literą Dowód? Sprawdź, ile wyliczył Apple. Nie mogę znaleźć jednego przykładu, w którym zaczynają się od małej litery letter.)
  2. Używając opcji, twój create jest teraz tylko funkcjonalnym aliasem dla init? i może być zaimplementowany bardzo prosto.
  3. Zgodnie z sugestią Tony, utwórz globalną funkcję ogólną do obsługi decode. To, czego nie sugerował, choć mógł zakładać, że to było dorozumiane, to użyć tego do wdrożenia JSONDecodable.decode.
  4. Jako meta-sugestię, użyj funkcji kodu urywków kodu Xcode, aby utworzyć fragment kodu do tego celu. Powinien być bardzo szybki.

Na prośbę pytającego, oto szybkie wdrożenie z placu zabaw. Nigdy nie używałem Argo. Właściwie nigdy o tym nie słyszałem, dopóki nie zobaczyłem tego pytania. Odpowiedziałem na to pytanie, po prostu stosując to, co wiem o Swift, do zbadania źródła Argo i wyjaśnienia go. Ten kod jest kopiowany bezpośrednio z placu zabaw. Nie używa Argo, ale używa sensownego faksu odpowiednich części. Ostatecznie, to pytanie jest nie o Argo. Chodzi o systemie typu Swift, a wszystko w poniższym kodzie skutecznie odpowiada na pytanie i okazuje się, że jest to wykonalne:

enum JSONValue { 
    case JSONString(String) 
} 

protocol JSONDecodable { 
    typealias DecodedType = Self 
    class func decode(JSONValue) -> DecodedType? 
} 

protocol RawStringInitializable { 
    init?(rawValue: String) 
} 

enum StatusValue: String, RawStringInitializable, JSONDecodable { 
    case Ok = "ok" 
    case Pending = "pending" 
    case Error = "error" 

    static func decode(j: JSONValue) -> StatusValue? { 
     return decodeJSON(j) 
    } 
} 

func decodeJSON<E: RawStringInitializable>(j: JSONValue) -> E? { 
    // You can replace this with some fancy Argo operators, 
    // but the effect is the same. 
    switch j { 
    case .JSONString(let string): return E(rawValue: string) 
    default: return nil 
    } 
} 

let j = JSONValue.JSONString("ok") 
let statusValue = StatusValue.decode(j) 

To nie jest pseudokod. Jest kopiowany bezpośrednio z działającego Xcode placu zabaw.

Jeśli utworzysz protokół RawStringInitializable i wszystkie twoje wyliczenia go wdrożą, będziesz złoty. Ponieważ wszystkie wyliczenia mają powiązane nieprzetworzone wartości, implicite implementują ten interfejs. Musisz tylko złożyć deklarację. Globalna funkcja decodeJSON używa tego protokołu do traktowania polimorficznego wszystkich swoich wyliczeń.

+0

Jeśli ktoś może sprawdzić, czy to działa, proszę zostaw komentarz tutaj i zaznaczę to dla zaakceptowanej odpowiedzi. Niestety nie mam więcej czasu, aby wkopać się w głębiny Argonu. Jest to niewątpliwie bardzo fajny sposób analizowania JSON. Nie przestawaj [Tony] (http://stackoverflow.com/users/2140218/tony-dipasquale) – blackjacx

+0

Zrobiłem to, o co prosiłeś, chociaż jest to bardzo nietypowe żądanie na SO. Zazwyczaj weryfikator odpowiada za weryfikację odpowiedzi. –

+1

To dość dobra odpowiedź. Chciałbym oznaczyć to jako zaakceptowane dwukrotnie, gdybym mógł. Bardzo dziękuję za trud. ** Commet do poprzedniego komentarza: ** _ Tak, ale to jest społeczność, w której każdy próbuje sobie pomóc. I nie mam więcej czasu, aby zagłębić się w ten temat. Ponadto pomyślałem, że lepiej jest mieć zaakceptowaną odpowiedź zamiast odpowiedzi, w której nie jest jasne, czy to działa. – blackjacx

1

Jeśli możesz podać przykład wyłudzenia, może być łatwiej zobaczyć, co można poprawić.

Patrząc na to chciałbym powiedzieć, że warto rozważyć usunięcie .unknown ze wszystkich wyliczeń i po prostu mieć opcjonalny typ reprezentujący wartość .unknown z .None. Jeśli to zrobiłeś, możesz napisać:

extension StatusValue: JSONDecodable { 
    static func decode(j: JSONValue) -> StatusValue? { 
     return j.value() >>- { StatusValue(rawValue: $0) } 
    } 
} 

Nie potrzebujesz teraz wszystkich funkcji tworzenia. To zmniejszy wiele powielania.

Edit:

Można ewentualnie użyć Generics. Jeśli utworzysz protokół DecodableStringEnum tak:

protocol DecodableStringEnum { 
    init?(rawValue: String) 
} 

Następnie zrób wszystkie teksty stałe są z nim zgodne. Nie musisz już pisać żadnego kodu, ponieważ ten init pochodzi z wyliczaniem wartości surowych.

enum CreatureType: String, DecodableStringEnum { 
    case Fish = "fish" 
    case Cat = "cat" 
} 

Teraz napisać funkcję globalnego obsłużyć wszystkie te przypadki:

func decodeStringEnum<A: DecodableStringEnum>(key: String, j: JSONValue) -> A? { 
    return j[key]?.value() >>- { A(rawValue: $0) } 
} 

Wreszcie w Argo można mieć swoją funkcję dekodowania istota wyglądać następująco:

static func decode(j: JSONValue) -> Creature? { 
    return Creature.create 
    <^> j <| "name" 
    <*> decodeStringEnum("type", j) 
    <*> j <| "description" 
} 
+0

dlaczego używasz 0 dolarów zamiast "j"? – blackjacx

+0

, ale nadal mam 20 rozszerzeń, które wyglądają dokładnie tak samo z innym typem wyliczenia. – blackjacx

+0

Używam 0 USD, ponieważ jestem w zamknięciu. Operator '>> -' przekaże wartość nie-opcjonalną do tego zamknięcia i otrzymasz od niego 0 $. Jeśli j.value() zwróci .None wtedy '>> -' po prostu wróci. Brak. –

1

dla nikogo innego idzie, najbardziej aktualne odpowiedzi jest następujące: https://github.com/thoughtbot/Argo/blob/td-decode-enums/Documentation/Decode-Enums.md

Zasadniczo, za teksty stałe

enum CurrencyValue : String { 
    case EUR = "EUR" 
    case unknown = "unknown" 
} 

enum StatusValue : String { 
    case ok = "ok" 
    case pending = "pending" 
    case error = "error" 
    case unknown = "unknown" 
} 

Wystarczy tylko zrobić

extension CurrencyValue: Decodable {} 
extension StatusValue: Decodable {} 

także ponieważ wydaje się, że zostało to wielokrotnie podkreślone, jeśli po prostu pozbędziesz się pola .Unknown i zamiast tego użyjesz opcji, możesz użyć wbudowanego wsparcia dla wyliczeń jako typ RawRepresentable.

Zobacz także: https://github.com/thoughtbot/Argo/blob/master/Argo/Extensions/RawRepresentable.swift dla implementacji, która to umożliwia.