Лучшая практика для реализации отказоустойчивого инициализатора в 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 ответов:
обновление: С 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) } }