unwrapping the returning protocol paths must isn from extension designated called before all swift object-initializers

the - protocol swift



La mejor práctica para implementar un inicializador failable en Swift (8)

Con el siguiente código intento definir una clase de modelo simple y su inicializador failable, que toma un diccionario (json-) como parámetro. El inicializador debe devolver nil si el nombre de usuario no está definido en el json original.

1. ¿Por qué no compila el código? El mensaje de error dice:

Todas las propiedades almacenadas de una instancia de clase deben inicializarse antes de devolver nil desde un inicializador.

Eso no tiene sentido. ¿Por qué debería inicializar esas propiedades cuando planeo devolver nil ?

2. ¿Mi enfoque es el correcto o habría otras ideas o patrones comunes para lograr mi objetivo?

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


Eso no tiene sentido. ¿Por qué debería inicializar esas propiedades cuando planeo devolver nada?

Según Chris Lattner, esto es un error. Esto es lo que dice:

Esta es una limitación de implementación en el compilador swift 1.1, documentado en las notas de la versión. El compilador actualmente no puede destruir clases parcialmente inicializadas en todos los casos, por lo que no permite la formación de una situación en la que debería hacerlo. Consideramos que esto es un error que debe corregirse en versiones futuras, no en una función.

Source

EDITAR:

Tan rápido es ahora de código abierto y, de acuerdo con este registro de cambios , ahora se repara en instantáneas de 2.2.

Los inicializadores de clase declarados como failable o throwing ahora pueden devolver nil o lanzar un error, respectivamente, antes de que el objeto se haya inicializado por completo.


Un inicializador failable para un tipo de valor (es decir, una estructura o enumeración) puede desencadenar un error de inicialización en cualquier punto dentro de su implementación de inicializador.

Para las clases, sin embargo, un inicializador failable puede desencadenar un error de inicialización solo después de que todas las propiedades almacenadas introducidas por esa clase se hayan establecido en un valor inicial y se haya llevado a cabo cualquier delegación de inicializador.

Extracto de: Apple Inc. " El lenguaje de programación Swift. "IBooks. https://itun.es/sg/jEUH0.l


Acepto que la respuesta de Mike S es la recomendación de Apple, pero no creo que sea una buena práctica. El objetivo de un sistema de tipo fuerte es mover los errores de tiempo de ejecución para compilar el tiempo. Esta "solución" derrota ese propósito. En mi humilde opinión, mejor sería seguir adelante e inicializar el nombre de usuario en "" y luego comprobarlo después de la super.init (). Si se permiten los Nodos de usuario en blanco, establezca un indicador.

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


Aunque Swift 2.2 se ha liberado y ya no tiene que inicializar completamente el objeto antes de fallar el inicializador, debe mantener sus caballos hasta que se solucione https://bugs.swift.org/browse/SR-704 .


Descubrí que esto se puede hacer en Swift 1.2

Hay algunas condiciones:

  • Las propiedades requeridas deben declararse como opcionales implícitamente desempaquetados
  • Asigne un valor a sus propiedades requeridas exactamente una vez. Este valor puede ser nil.
  • Luego llame a super.init () si su clase hereda de otra clase.
  • Después de que se le haya asignado un valor a todas las propiedades requeridas, verifique si su valor es el esperado. Si no, devuelve nil.

Ejemplo:

class ClassName: NSObject { let property: String! init?(propertyValue: String?) { self.property = propertyValue super.init() if self.property == nil { return nil } } }


Otra forma de eludir la limitación es trabajar con una clase-funciones para hacer la inicialización. Es posible que desee mover esa función a una extensión:

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

Usarlo se convertiría en:

if let user = User.fromDictionary(someDict) { // Party hard }


Puede usar la conveniencia 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) } }


Actualización: del Swift 2.2 Change Log (publicado el 21 de marzo de 2016):

Los inicializadores de clase declarados como failable o throwing ahora pueden devolver nil o lanzar un error, respectivamente, antes de que el objeto se haya inicializado por completo.

Para Swift 2.1 y anteriores:

De acuerdo con la documentación de Apple (y el error del compilador), una clase debe inicializar todas sus propiedades almacenadas antes de devolver nil desde un inicializador failable:

Para las clases, sin embargo, un inicializador failable puede desencadenar un error de inicialización solo después de que todas las propiedades almacenadas introducidas por esa clase se hayan establecido en un valor inicial y se haya llevado a cabo cualquier delegación de inicializador.

Nota: en realidad funciona bien para estructuras y enumeraciones, simplemente no clases.

La forma sugerida para manejar las propiedades almacenadas que no se pueden inicializar antes de que el inicializador falle es declararlas como opciones opcionales implícitamente desempaquetadas.

Ejemplo de los documentos:

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

En el ejemplo anterior, la propiedad del nombre de la clase Product se define como un tipo de cadena opcional implícitamente desenvuelto (String!). Debido a que es de tipo opcional, esto significa que la propiedad del nombre tiene un valor predeterminado de nil antes de que se le asigne un valor específico durante la inicialización. Este valor predeterminado de cero significa a su vez que todas las propiedades introducidas por la clase Producto tienen un valor inicial válido. Como resultado, el inicializador failable para Producto puede desencadenar un error de inicialización al inicio del inicializador si se le pasa una cadena vacía, antes de asignar un valor específico a la propiedad de nombre dentro del inicializador.

¡En su caso, sin embargo, simplemente definiendo userName como una String! no soluciona el error de compilación porque todavía debe preocuparse por inicializar las propiedades en su clase base, NSObject . Afortunadamente, ¡con userName definido como String! , puede llamar a super.init() antes de return nil que iniciará su clase base NSObject y arreglará el error de compilación.

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