Najlepsza praktyka wdrażania nieudanego inicjalizatora w Swift

Za pomocą poniższego kodu próbuję zdefiniować prostą klasę modelu i jest to failable initializer, który przyjmuje jako parametr słownik (json -). Inicjalizator powinien zwrócić nil, Jeśli nazwa użytkownika nie jest zdefiniowana w oryginalnym json.

1. Dlaczego Kod się nie kompiluje? Komunikat o błędzie mówi:

Wszystkie Przechowywane właściwości instancji klasy muszą być zainicjalizowane przed zwróceniem nil z inicjalizatora.

To nie ma sensu. Dlaczego powinienem zainicjować te właściwości, kiedy planuję zwrócić nil?

2. Czy moje podejście jest właściwe, czy też istnieją inne pomysły lub wspólne wzorce, aby osiągnąć mój cel?

class User: NSObject {

    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        if let value: String = dictionary["user_name"] as? String {
            userName = value
        }
        else {
           return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()
    }
}
Author: Jens, 2014-10-22

8 answers

Aktualizacja: z Swift 2.2 Change Log (wydany 21 marca 2016):

Wyznaczone inicjalizatory klas zadeklarowane jako failable lub throwing mogą teraz zwracać nil lub rzucać błąd, odpowiednio, zanim obiekt zostanie w pełni zainicjalizowany.


Dla Swift 2.1 i wcześniejszych:

Zgodnie z dokumentacją Apple (i błędem kompilatora), klasa musi zainicjować wszystkie swoje zapisane właściwości przed zwróceniem nil z błąd inicjalizacji:

Dla klas, jednak nieudany inicjalizator może wywołać błąd inicjalizacji dopiero po wszystkich zapisanych właściwościach wprowadzonych przez ta klasa została ustawiona na wartość początkową i dowolny inicjalizator delegacja odbyła się.

Notatka: to faktycznie działa dobrze dla struktur i wyliczeń, tylko nie klas.

Sugerowany sposób obsługi przechowywanych właściwości, których nie można zainicjować przed niepowodzenie inicjalizatora polega na zadeklarowaniu ich jako domyślnie rozpakowanych opcji.

Przykład z dokumentów:

class Product {
    let name: String!
    init?(name: String) {
        if name.isEmpty { return nil }
        self.name = name
    }
}

W powyższym przykładzie właściwość name klasy produktów jest definiowane jako posiadające domyślnie rozpakowany opcjonalny typ string / Align = "left" / ). Ponieważ jest typu opcjonalnego, oznacza to, że nazwa właściwość ma domyślną wartość nil, zanim zostanie przypisana konkretna wartość podczas inicjalizacji. Ta domyślna wartość nil z kolei oznacza że wszystkie właściwości wprowadzone przez klasę produktów mają poprawna wartość początkowa. W rezultacie, niesprawny inicjalizator dla produktu może wywołać błąd inicjalizacji na początku inicjalizatora jeśli przekazywany jest pusty łańcuch, przed przypisaniem określonej wartości do właściwość name wewnątrz inicjalizatora.

W Twoim przypadku jednak samo zdefiniowanie userName jako String! nie naprawia błędu kompilacji, ponieważ nadal musisz martwić się o inicjalizację właściwości na twojej klasie bazowej, NSObject. Na szczęście, z userName zdefiniowanym jako String!, możesz faktycznie wywołać super.init() przed return nil, co spowoduje init twojej podstawowej klasy NSObject i naprawi błąd kompilacji.

class User: NSObject {

    let userName: String!
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: NSDictionary) {
        super.init()

        if let value = dictionary["user_name"] as? String {
            self.userName = value
        }
        else {
            return nil
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            self.isSuperUser = value
        }

        self.someDetails = dictionary["some_details"] as? Array
    }
}
 67
Author: Mike S,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 03:55:47
To nie ma sensu. Dlaczego powinienem zainicjować te właściwości, gdy Planuję zwrócić zero?
Według Chrisa Lattnera jest to błąd. Oto co on mówi:

Jest to ograniczenie implementacji w kompilatorze swift 1.1, udokumentowane w Uwagach do wydania. Kompilator nie jest obecnie w stanie niszczyć częściowo zainicjowane klasy we wszystkich przypadkach, więc uniemożliwia tworzenie sytuacji, w której musiałby. Uważamy to za bug to be naprawiono w przyszłych wydaniach, a nie funkcję.

Źródło

EDIT:

Więc swift jest teraz open source i zgodnie z ten changelog {[12] } jest teraz naprawiony w migawkach swift 2.2

Wyznaczone inicjalizatory klas zadeklarowane jako failable lub throwing mogą teraz zwracać nil lub rzucać błąd, odpowiednio, zanim obiekt zostanie w pełni zainicjalizowany.

 130
Author: mustafa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-19 21:34:27

Zgadzam się, że odpowiedź Mike ' a S jest rekomendacją Apple, ale nie sądzę, że to najlepsza praktyka. Głównym celem silnego systemu typów jest przeniesienie błędów runtime do czasu kompilacji. To "rozwiązanie" pokonuje ten cel. IMHO, lepiej byłoby przejść do przodu i zainicjować nazwę użytkownika "", a następnie sprawdzić go po super.init(). Jeśli puste nazwy użytkowników są dozwolone, Ustaw flagę.

class User: NSObject {
    let userName: String = ""
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init?(dictionary: [String: AnyObject]) {
        if let user_name = dictionary["user_name"] as? String {
            userName = user_name
        }

        if let value: Bool = dictionary["super_user"] as? Bool {
            isSuperUser = value
        }

        someDetails = dictionary["some_details"] as? Array

        super.init()

        if userName.isEmpty {
            return nil
        }
    }
}
 7
Author: Daniel T.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-21 21:50:03

Innym sposobem obejścia tego ograniczenia jest praca z klasą-funkcjami do inicjalizacji. Możesz nawet chcieć przenieść tę funkcję do rozszerzenia:

class User: NSObject {

    let username: String
    let isSuperUser: Bool
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {

         self.userName = userName
         self.isSuperUser = isSuperUser
         self.someDetails = someDetails

         super.init()
    }
}

extension User {

    class func fromDictionary(dictionary: NSDictionary) -> User? {

        if let username: String = dictionary["user_name"] as? String {

            let isSuperUser = (dictionary["super_user"] as? Bool) ?? false
            let someDetails = dictionary["some_details"] as? [String]

            return User(username: username, isSuperUser: isSuperUser, someDetails: someDetails)
        }

        return nil
    }
}

Używając go stałoby się:

if let user = User.fromDictionary(someDict) {

     // Party hard
}
 6
Author: Kevin R,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-13 10:23:10

Chociaż Swift 2.2 został wydany i nie musisz już w pełni inicjalizować obiektu przed niepowodzeniem inicjalizacji, musisz wstrzymać konie do https://bugs.swift.org/browse/SR-704 jest stałe.

 3
Author: sssilver,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-22 22:17:26

Dowiedziałem się, że to Można {[3] } zrobić w Swift 1.2

Istnieją pewne warunki:

  • wymagane właściwości powinny być zadeklarowane jako domyślnie rozpakowane opcje
  • przypisz wartość do wymaganych właściwości dokładnie raz. Wartość ta może być zerowa.
  • Więc zadzwoń do super.init () jeśli twoja klasa dziedziczy po innej klasie.
  • Po wszystkie wymagane właściwości zostały przypisane wartości, sprawdź, czy ich wartość jest zgodnie z oczekiwaniami. Jeśli nie, zwróć zero.

Przykład:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

        if self.property == nil {
            return nil
        }
    }
}
 1
Author: Pim,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-06 19:55:47

Błąd inicjalizacji typu wartości (czyli struktury lub wyliczenia) może wywołać błąd inicjalizacji w dowolnym momencie w its initializer implementation

Dla klas, jednak nieudany inicjalizator może wywołać błąd inicjalizacji dopiero po wszystkich zapisanych właściwościach wprowadzonych przez ta klasa została ustawiona na wartość początkową i dowolny inicjalizator delegacja odbyła się.

Fragment Z: Apple Inc. "The Swift Język Programowania." iBooks. https://itun.es/sg/jEUH0.l

 0
Author: user1046037,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-23 01:34:20

Możesz użyć convenience INIT :

class User: NSObject {
    let userName: String
    let isSuperUser: Bool = false
    let someDetails: [String]?

    init(userName: String, isSuperUser: Bool, someDetails: [String]?) {
        self.userName = userName
        self.isSuperUser = isSuperUser
        self.someDetails = someDetails
    }     

    convenience init? (dict: NSDictionary) {            
       guard let userName = dictionary["user_name"] as? String else { return nil }
       guard let isSuperUser = dictionary["super_user"] as? Bool else { return nil }
       guard let someDetails = dictionary["some_details"] as? [String] else { return nil }

       self.init(userName: userName, isSuperUser: isSuperUser, someDetails: someDetails)
    } 
}
 0
Author: Максим Петров,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-17 08:06:40