¿Cómo implementar dos inits con el mismo contenido sin duplicación de código en Swift?

Supongamos una clase que se deriva de UIView siguiente manera:

 class MyView: UIView { var myImageView: UIImageView init(frame: CGRect) { super.init(frame: frame) } init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) } ... 

Si quisiera tener el mismo código en los dos inicializadores, como

 self.myImageView = UIImageView(frame: CGRectZero) self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill 

y NO duplicar ese código dos veces en la implementación de la clase, ¿cómo estructuraría los métodos init ?

Enfoques probados:

  • Creado un método func commonInit() que se llama después de super.init -> el comstackdor Swift da un error sobre una variable no inicializada myImageView antes de llamar a super.init
  • La llamada a func commonInit() antes de super.init falla evidentemente con un error de comstackción “‘self’ usado antes de la llamada a super.init”

Solo tuve el mismo problema .

Como dijo GoZoner, marcar tus variables como opcional funcionará. No es una forma muy elegante porque luego tiene que desenvolver el valor cada vez que desea acceder a él.

Archivaré una solicitud de mejora con Apple, tal vez podríamos obtener algo así como un método “beforeInit” que se llama antes de cada init donde podemos asignar las variables para que no tengamos que usar vars opcionales.

Hasta entonces, pondré todas las asignaciones en un método commonInit que se llama desde los inicializadores dedicados. P.ej:

 class GradientView: UIView { var gradientLayer: CAGradientLayer? // marked as optional, so it does not have to be assigned before super.init func commonInit() { gradientLayer = CAGradientLayer() gradientLayer!.frame = self.bounds // more setup } init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) commonInit() } init(frame: CGRect) { super.init(frame: frame) commonInit() } override func layoutSubviews() { super.layoutSubviews() gradientLayer!.frame = self.bounds // unwrap explicitly because the var is marked optional } } 

Gracias a David volví a ver el libro y encontré algo que podría ser útil para nuestros esfuerzos de deduplicación sin tener que usar el hack variable opcional. Uno puede usar un cierre para inicializar una variable.

Establecer un valor de propiedad predeterminado con un cierre o función

Si el valor predeterminado de una propiedad almacenada requiere alguna personalización o configuración, puede utilizar una función de cierre o global para proporcionar un valor predeterminado personalizado para esa propiedad. Siempre que se inicializa una nueva instancia del tipo al que pertenece la propiedad, se llama al cierre o función, y su valor de retorno se asigna como el valor predeterminado de la propiedad. Este tipo de cierres o funciones típicamente crean un valor temporal del mismo tipo que la propiedad, ajustan ese valor para representar el estado inicial deseado y luego devuelven ese valor temporal para usar como valor predeterminado de la propiedad.

Aquí hay un esquema de cómo se puede usar un cierre para proporcionar un valor de propiedad predeterminado:

 class SomeClass { let someProperty: SomeType = { // create a default value for someProperty inside this closure // someValue must be of the same type as SomeType return someValue }() } 

Tenga en cuenta que la llave de cierre del cierre es seguida por un par de paréntesis vacíos. Esto le dice a Swift que ejecute el cierre de inmediato. Si omite estos paréntesis, está intentando asignar el cierre a la propiedad, y no el valor de retorno del cierre.

NOTA

Si usa un cierre para inicializar una propiedad, recuerde que el rest de la instancia aún no se ha inicializado en el momento en que se ejecuta el cierre. Esto significa que no puede acceder a ningún otro valor de propiedad desde su cierre, incluso si esas propiedades tienen valores predeterminados. Tampoco puede usar la propiedad propia implícita ni llamar a ninguno de los métodos de la instancia.

Extracto de: Apple Inc. “The Swift Programming Language”. IBooks. https://itun.es/de/jEUH0.l

Esta es la manera que usaré a partir de ahora, porque no elude la característica útil de no permitir variables nulas. Para mi ejemplo, se verá así:

 class GradientView: UIView { var gradientLayer: CAGradientLayer = { return CAGradientLayer() }() func commonInit() { gradientLayer.frame = self.bounds /* more setup */ } init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) commonInit() } init(frame: CGRect) { super.init(frame: frame) commonInit() } } 

Lo que necesitamos es un lugar común para poner nuestro código de inicialización antes de llamar a los inicializadores de cualquier superclase, de modo que lo que estoy usando actualmente, se muestra en un código a continuación. (También cubre el caso de interdependencia entre los valores predeterminados y mantenerlos constantes).

 import UIKit class MyView: UIView { let value1: Int let value2: Int enum InitMethod { case coder(NSCoder) case frame(CGRect) } override convenience init(frame: CGRect) { self.init(.frame(frame))! } required convenience init?(coder aDecoder: NSCoder) { self.init(.coder(aDecoder)) } private init?(_ initMethod: InitMethod) { value1 = 1 value2 = value1 * 2 //interdependence among defaults switch initMethod { case let .coder(coder): super.init(coder: coder) case let .frame(frame): super.init(frame: frame) } } } 

¿Qué tal esto?

 public class MyView : UIView { var myImageView: UIImageView = UIImageView() private func setup() { myImageView.contentMode = UIViewContentMode.ScaleAspectFill } override public init(frame: CGRect) { super.init(frame: frame) setup() } required public init(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setup() } } 

¿Tiene que venir necesariamente antes? Creo que esta es una de las cosas que las opciones opcionales sin envoltorio se pueden utilizar para:

 class MyView: UIView { var myImageView: UIImageView! init(frame: CGRect) { super.init(frame: frame) self.commonInit() } init(coder aDecoder: NSCoder!) { super.init(coder: aDecoder) self.commonInit() } func commonInit() { self.myImageView = UIImageView(frame: CGRectZero) self.myImageView.contentMode = UIViewContentMode.ScaleAspectFill } ... } 

Los opcionales implícitamente desempaquetados le permiten omitir la asignación de variables antes de llamar a super . Sin embargo, aún puede acceder a ellos como las variables normales:

var image: UIImageView = self.myImageView // no error

Otra opción es utilizar un método estático (se agregó ‘otherView’ para resaltar la escalabilidad)

 class MyView: UIView { var myImageView: UIImageView var otherView: UIView override init(frame: CGRect) { (myImageView,otherView) = MyView.commonInit() super.init(frame: frame) } required init(coder aDecoder: NSCoder) { (myImageView, otherView) = MyView.commonInit() super.init(coder: aDecoder)! } private static func commonInit() -> (UIImageView, UIView) { //do whatever initialization stuff is required here let someImageView = UIImageView(frame: CGRectZero) someImageView.contentMode = UIViewContentMode.ScaleAspectFill let someView = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) return (someImageView, someView) } } 

Asigna myImageView en ambos métodos init() basados ​​en una función de creación de imagen única. Como tal:

 self.myImageView = self.createMyImageView (); 

Por ejemplo, como tal:

 class Bar : Foo { var x : Int? func createX () -> Int { return 1 } init () { super.init () self.x = self.createX () } } 

Tenga en cuenta el uso ‘opcional’ en Int?