Closures: Inicialización de Variables en Swift

Closures: Inicialización de Variables en Swift

Juan Gabriel Gomila Juan Gabriel Gomila
7 minutos

Leer el artículo
Audio generated by DropInBlog's Blog Voice AI™ may have slight pronunciation nuances. Learn more

Closures en Swift

De todas las muchas, bellas y variadas maneras en que se puede inicializar algo en Swift, con la ayuda de closures no es como se hace normalmente, pero también es un método válido para llevar la inicialización a cabo. Por suerte o por desgracia, puede hacer que toda la parafernalia del método init() sea mucho menos dolorosa y un poco más manejable.

Para los desarrolladores de interfaz de usuario a través de código, ¡esta va por vosotros 🍻!

closures en swift 

UIKit == UIDolorDeCabeza()

Mira, no es culpa de UIKit. Los componentes que necesitan ser interactuados por parte del usuario se prestan a una montaña de código de configuración, porque las preferencias son múltiples. Por lo general, mucho de esto se encuentra en cualquiera de las funciones viewDidLoad loadView:

override func loadView()
{
    let helloWorldLbl = UILabel()
    helloWorldLbl.text = NSLocalizedString(“controller.topLbl.helloWorld”, comment: “Hello World!”)
    helloWorldLbl.font =   UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
    helloWorldLbl.textColor = UIColor.whiteColor()
    helloWorldLbl.textAlignment = .Center
    self.view.addSubview(helloWorldLbl)
}

Esto es bastante estándar para aquellos de nosotros que nos adentramos en las tormentosas aguas de Cocoa Touch sin un .xib o .storyboard a la vista. Sin embargo, si vosotros compartís mi escaso amor por los métodos viewDidLoad o loadView, se puede poner todo este código en otro lugar.

Por ejemplo, una propiedad:

let helloWorldLbl:UILabel = {
    let lbl = UILabel()
    lbl.text = NSLocalizedString(“controller.topLbl.helloWorld”, comment: “Hello World!”)
    lbl.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
    lbl.textColor = UIColor.whiteColor()
    lbl.textAlignment = .Center
    return lbl
}()

¡Mola! En el propio libro de Apple en Swift, se señala que "si hay valor predeterminado de una propiedad que requiere algún tipo de personalización o configuración, se puede utilizar uno de los bloques de closures o una función global para proporcionar un valor predeterminado personalizado para dicha propiedad."

Como acabamos de mencionar, UIKit controla el rendimiento de una gran cantidad de personalización y configuración.

Una de las consecuencias hermosas de este cambio es, sin embargo, cómo se ve ahora loadView:

override func loadView
{
    self.view.addSubview(self.helloWorldLbl)
}

Toma nota de los "()" al final del cierre de la declaración de propiedad. Esto es dejar que los pequeños magos Swift que compilan el código sepan que la instancia se asigna al tipo de retorno del closure. Si  omitimos esto, es posible que asignemos el closure en sí a la instancia. Y en este caso, eso es 🙅

Las reglas son las reglas

A pesar de que tenemos un juguete nuevo, es imprescindible recordar las reglas de los mortales, ya que estamos asignando una propiedad a un closure. El resto de la instancia que contiene podría no haber sido inicializado todavía debido a que, cuando se ejecuta el closure, no es posible hacer referencia a otros valores de la propiedad o de uno mismo dentro de ella.

Por ejemplo

let helloWorldLbl:UILabel = {
    let lbl = UILabel()
    lbl.text = self.someFunctionToDetermineText() //Compiler error
    lbl.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
    lbl.textColor = self.myAppTheme.textColor() //Another error
    lbl.textAlignment = .Center
    return lbl
}()

La instancia de self puede no ser segura de utilizar con todo, o puede que no sea a través de un proceso de inicialización de dos fases de Swift. Lo mismo se puede decir de cualquier propiedad de la instancia, que puede o no puede ser asignada e inicializada cuando el closure se ejecuta inmediatamente. Esta es una clara, pero justificada desventaja del uso de closures para la inicialización. Tiene sentido y es justo en línea con uno de los tres objetivos de diseño de Swift: la seguridad.

Un ejemplo de closures en Swift con colecciones

Un ámbito en el que he encontrado que esta técnica es particularmente útil es con instancias que representan a una de las muchas formas diferentes de una colección en Swift. De los muchos talentos de Swift, rebanar y tamizar a través de colecciones con el poder de un millar de titanes es uno de mis favoritos.

Considera el siguiente ejemplo, tomado de un inicializador en un proyecto en el que trabajé hace unos días. La clase que alberga este código tiene una propiedad [Employee]. En un primer lanzamiento, puse sus valores iniciales de un archivo plist. Después, éstos se almacenan a través de NSKeyedArchiver.

guard let empls = NSKeyedUnarchiver.unarchiveObjectWithFile(EmployeeDataManager.ArchiveURL.path!) as? [Developer] else
{
    self.developers = {
        let pListData = //Get plist data
        var emplArray:[Employee] = [Employee]()
        //Set up devArray from plist data
        return emplArray.map{ $0.setLocation() }
                       .filter{ $0.isRentable }
                       .sort{ $0.name < $1.name }
     }()
    return
}
self.employees = empls

Me gusta bastante este enfoque, porque a pesar de que no se use fuera de un inicializador, la intención del código es muy clara en que sólo es responsable de establecer una propiedad, lo cual nos trae código más limpio y bien definido. A medida que los init y viewDidLoad se hacen más grandes, seccionar las propiedades de este modo es un buen regalo de bienvenida en términos de facilidad de lectura.

La guinda del pastel

Si realmente quieres presumir de la inicialización de variables en un closure, pero estás sufriendo de una grave falta de uso de esos $ funcionales en el código, ¡ánimo! Con ayuda de algunos expertos del tema, uno puede tener un código que infiere el tipo dentro del propio closure, lo que da una cierta configuración de estilo profesional. Consideremos este código, que vi por primera vez a través de la NSHipster:

@warn_unused_result
public func Init<Type>(value : Type, @noescape block: (object: Type) -> Void) -> Type
{
    block(object: value)
    return value
}

Me encanta. Una función pública que tiene un closure con un objeto tecleado mediante los genéricos, que luego es devuelto. Esto significa que podría dar la vuelta e inicializar las cosas con más información de tipo. Nuestra primera muestra de código entonces, a su vez, podría tener ahora este aspecto:

let helloWorldLbl = Init(UILabel()) {
    $0.text = NSLocalizedString(“controller.topLbl.helloWorld”, comment: “Hello World!”)
    $0.font = UIFont.preferredFontForTextStyle(UIFontTextStyleBody)
    $0.textColor = UIColor.whiteColor()
    $0.textAlignment = .Center
}

Magnífico pues, en efecto, llega a acabar con la necesidad de la variable de instancia dentro del closure, y se deshace de la exigencia de los "()". 👏 Excelente.

Closures para todos

Se podría decir que el uso de una técnica así es de un repoker de ases en la manga. Si bien es cierto, las líneas de código creadas por el programador permanecen en gran parte en el mismo número, yo diría que su colocación y flexibilidad lo hace ideal para muchos escenarios.

Es una forma divertida de hacer las cosas, e incluso hay algunas maneras de hacer lo mismo en nuestro viejo amigo Objective-C. Pero bueno, cuanto más se sabe, mejor ¿no?

Ahora que ya dominas los closures en Swift, te invitamos a leer uno de nuestros artículos relacionados con los datos genéricos en iOS con Swift.

« Volver al Blog

Obtener mi regalo ahora