Лучшая практика для реализации отказоустойчивого инициализатора в Swift


С помощью следующего кода я пытаюсь определить простой класс модели, и это отказоустойчивый инициализатор, который принимает (json-) словарь в качестве параметра. Инициализатор должен возвращать nil если имя пользователя не определено в исходном json.

1. Почему код не компилируется? В сообщении об ошибке говорится:

все сохраненные свойства экземпляра класса должны быть инициализированы перед возвратом nil из инициализатора.

Это не делает чувство. Почему я должен инициализировать эти свойства, когда я планирую вернуть nil?

2. Является ли мой подход правильным или есть другие идеи или общие шаблоны для достижения моей цели?

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()
    }
}
8 93

8 ответов:

обновление: С Swift 2.2 Журнал Изменений (выпущен 21 марта 2016 года):

назначенные инициализаторы класса, объявленные как failable или throwing, теперь могут возвращать nil или вызывать ошибку, соответственно, до полной инициализации объекта.


для Swift 2.1 и более ранние версии:

согласно документации Apple (и вашей ошибке компилятора), класс должен инициализировать все его сохраненные свойства перед возвратом nil из неисправного инициализатора:

для классов, однако, отказоустойчивый инициализатор может вызвать сбой инициализации только после всех сохраненных свойств, введенных этот класс был установлен в начальное значение и любой инициализатор делегация состоялась.

Примечание: он на самом деле отлично работает для структур и перечислений, просто нет занятия.

предлагаемый способ обработки сохраненных свойств, которые не могут быть инициализированы до сбоя инициализатора, заключается в объявлении их как неявно развернутых опциональных объектов.

пример из документации:

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

в приведенном выше примере свойство name класса Product является определяется как имеющий неявно развернутый необязательный строковый тип (Строка!). Поскольку это необязательный тип, это означает, что имя свойство имеет значение по умолчанию nil перед назначением определенного значения значение во время инициализации. Это значение по умолчанию nil в свою очередь означает что все свойства, введенные классом продукта, имеют допустимое начальное значение. В результате отказоустойчивый инициализатор для продукта может вызвать сбой инициализации при запуске инициализатора если передается пустая строка, то перед присвоением определенного значения свойство Name в инициализатор.

в вашем случае, однако, просто определить userName как String! не исправляет ошибку компиляции, потому что вам все еще нужно беспокоиться об инициализации свойств в вашем базовом классе, NSObject. К счастью, с userName определяется как String!, вы действительно можете позвонить super.init() перед return nil который будет инициализировать ваш NSObject базовый класс и исправить ошибки компиляции.

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

это не имеет смысла. Почему я должен инициализировать эти свойства, когда Я планирую вернуть ноль?

по словам Криса Латтнера, это ошибка. Вот что он говорит:

это ограничение реализации в компиляторе swift 1.1, описаны в примечаниях к выпуску. Компилятор не может уничтожить частично инициализированные классы во всех случаях, поэтому он запрещает формирование ситуации, когда это было бы необходимо. Мы считайте, что это ошибка, которая будет исправлена в будущих версиях, а не функция.

источник

EDIT:

таким образом, swift теперь является открытым исходным кодом и согласно этот список изменений теперь это исправлено в моментальных снимках swift 2.2

назначенные инициализаторы класса, объявленные как failable или throwing, теперь могут возвращать nil или бросать ошибку, соответственно, до того, как объект был полностью инициализированный.

Я согласен, что ответ Майка S-это рекомендация Apple, но я не думаю, что это лучшая практика. Весь смысл сильной системы типов заключается в перемещении ошибок времени выполнения во время компиляции. Это "решение" разрушает эту цель. ИМХО, лучше было бы пойти дальше и инициализировать имя пользователя в "" а затем проверить его после супер.инициализация.)( Если пустые имена пользователей разрешены, установите флаг.

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

используя его станет:

if let user = User.fromDictionary(someDict) {

     // Party hard
}

хотя Swift 2.2 был выпущен, и вам больше не нужно полностью инициализировать объект до отказа инициализатора, вам нужно держать лошадей до https://bugs.swift.org/browse/SR-704 исправлено.

я узнал, что это можете быть сделано в Swift 1.2

есть несколько условий:

  • необходимые свойства должны быть объявлены как неявно развернутые опции
  • назначьте значение для ваших необходимых свойств ровно один раз. Это значение может быть равно нулю.
  • затем позвоните супер.init () если ваш класс наследуется от другого класса.
  • после все необходимые свойства были присвоены значения, проверьте, если их стоимость, как и ожидалось. Если нет, верните ноль.

пример:

class ClassName: NSObject {

    let property: String!

    init?(propertyValue: String?) {

        self.property = propertyValue

        super.init()

        if self.property == nil {
            return nil
        }
    }
}

отказоустойчивый инициализатор для типа значения (то есть структуры или перечисление) может вызвать сбой инициализации в любой точке внутри его реализация инициализатора

для классов, однако, отказоустойчивый инициализатор может вызвать сбой инициализации только после всех сохраненных свойств, введенных этот класс был установлен в начальное значение и любой инициализатор делегация состоялась.

Отрывок Из: Apple Inc. "Язык Программирования Swift. " iBooks. https://itun.es/sg/jEUH0.l

можно использовать удобство 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)
    } 
}