Swift From Scratch Inicialización y delegación de inicialización

En la lección anterior de Swift From Scratch, creamos una aplicación funcional de tareas. Sin embargo, el modelo de datos podría usar algo de amor. En esta lección final, vamos a refactorizar el modelo de datos implementando una clase de modelo personalizado.

1. El modelo de datos

El modelo de datos que vamos a implementar incluye dos clases, una Tarea clase y un Que hacer clase que hereda de la Tarea clase. Mientras creamos e implementamos estas clases de modelo, continuaremos nuestra exploración de la programación orientada a objetos en Swift. En esta lección, ampliaremos la inicialización de las instancias de clase y el papel que juega la herencia durante la inicialización..

los Tarea Clase

Comencemos con la implementación del Tarea clase. Crea un nuevo archivo Swift seleccionando Nuevo> Archivo ... de Xcode's Expediente menú. Escoger Archivo rápido desde el iOS> Fuente sección. Nombra el archivo Cambio de tarea y golpear Crear.

La implementación básica es corta y simple. los Tarea clase hereda de NSObject, definido en el Fundación Marco, y tiene una propiedad variable. nombre de tipo Cuerda. La clase define dos inicializadores., en eso() y init (nombre :). Hay algunos detalles que podrían hacerte tropezar, así que déjame explicarte lo que está pasando.

importar clase de la Fundación Tarea: NSObject var nombre: Cadena anular conveniencia init () self.init (name: "New Task") init (name: String) self.name = name

Porque el en eso() método también se define en el NSObject clase, tenemos que prefijar el inicializador con el anular palabra clave. Cubrimos los métodos de anulación anteriormente en esta serie. En el en eso() método, invocamos el init (nombre :) método, pasar "Nueva tarea" como el valor para el nombre parámetro.

los init (nombre :) El método es otro inicializador, aceptando un solo parámetro. nombre de tipo Cuerda. En este inicializador, el valor del nombre parámetro asignado a la nombre propiedad. Esto es bastante fácil de entender. Derecha?

Inicializadores designados y de conveniencia

¿Qué pasa con el conveniencia palabra clave prefijando el en eso() ¿método? Las clases pueden tener dos tipos de inicializadores., designada inicializadores y conveniencia Inicializadores. Los inicializadores de conveniencia tienen el prefijo conveniencia palabra clave, lo que implica que init (nombre :) es un inicializador designado. ¿Porqué es eso? ¿Cuál es la diferencia entre los inicializadores designados y convenientes??

Inicializadores designados Inicialice completamente una instancia de una clase, lo que significa que cada propiedad de la instancia tiene un valor inicial después de la inicialización. Mirando a la Tarea clase, por ejemplo, vemos que la nombre propiedad se establece con el valor de la nombre parámetro de la init (nombre :) inicializador El resultado después de la inicialización es completamente inicializado. Tarea ejemplo.

Inicializadores de conveniencia, sin embargo, confíe en un inicializador designado para crear una instancia completamente inicializada de la clase. Por eso el en eso() inicializador de la Tarea clase invoca el init (nombre :) Inicializador en su implementación. Esto se conoce como delegación inicializadora. los en eso() el inicializador delega la inicialización a un inicializador designado para crear una instancia completamente inicializada del Tarea clase.

Inicializadores de conveniencia son opcionales. No todas las clases tienen un inicializador de conveniencia. Se requieren inicializadores designados, y una clase debe tener al menos un inicializador designado para crear una instancia completamente inicializada de sí misma.

los NSCoding Protocolo

La implementación de la Tarea Sin embargo, la clase no está completa. Más adelante en esta lección, escribiremos una serie de Que hacer instancias al disco. Esto solo es posible si las instancias de Que hacer La clase puede ser codificada y decodificada.

Sin embargo, no se preocupe, esto no es ciencia espacial. Solo necesitamos hacer el Tarea y Que hacer las clases se ajustan a la NSCoding protocolo. Por eso el Tarea clase hereda de la NSObject clase desde el NSCoding el protocolo solo puede ser implementado por clases heredadas directa o indirectamente de NSObject. Como el NSObject clase, la NSCoding protocolo se define en el Fundación marco de referencia.

Adoptar un protocolo es algo que ya hemos cubierto en esta serie, pero hay algunos errores que quiero señalar. Vamos a empezar diciéndole al compilador que el Tarea clase se ajusta a la NSCoding protocolo.

Importar clase de Foundation Tarea: NSObject, NSCoding var name: String…

A continuación, necesitamos implementar los dos métodos declarados en el NSCoding protocolo, init? (codificador :) y codificar (con :). La implementación es sencilla si está familiarizado con el NSCoding protocolo.

Importar clase de la Fundación Tarea: NSObject, NSCoding var nombre: String @objc required init? (coder aDecoder: NSCoder) name = aDecoder.decodeObject (forKey: "name") as! Cadena @objc func encode (con aCoder: NSCoder) aCoder.encode (name, forKey: "name") conveniencia anular init () self.init (name: "New Task") init (name: String) self.name = name

los init? (codificador :) el inicializador es un inicializador designado que inicializa un Tarea ejemplo. Aunque implementamos el init? (codificador :) método para ajustarse a la NSCoding protocolo, nunca necesitarás invocar este método directamente. Lo mismo es cierto para codificar (con :), que codifica una instancia de la Tarea clase.

los necesario palabra clave prefijando el init? (codificador :) método indica que cada subclase de la Tarea La clase necesita implementar este método. los necesario la palabra clave solo se aplica a los inicializadores, por lo que no necesitamos agregarla a la codificar (con :) método.

Antes de continuar, necesitamos hablar de @objc atributo. Porque el NSCoding protocolo es un protocolo de Objective-C, conformidad del protocolo Solo se puede verificar agregando el @objc atributo. En Swift, no existe tal cosa como la conformidad del protocolo o los métodos de protocolo opcionales. En otras palabras, si una clase se adhiere a un protocolo particular, el compilador verifica y espera que todos los métodos del protocolo se implementen..

los Que hacer Clase

Con el Tarea clase implementada, es hora de implementar el Que hacer clase. Crea un nuevo archivo Swift y llámalo ToDo.swift. Echemos un vistazo a la implementación de la Que hacer clase.

importar clase de base ToDo: Tarea var done: Bool @objc required init? (coder aDecoder: NSCoder) self.done = aDecoder.decodeBool (forKey: "done") super.init (coder: aDecoder) @objc override func codificar (con aCoder: NSCoder) aCoder.encode (hecho, forKey: "done") super.encode (con: aCoder) init (nombre: String, done: Bool) self.done = hecho super.init (nombre : nombre)  

los Que hacer clase hereda de la Tarea Clase y declara una propiedad variable. hecho de tipo Bool. Además de los dos métodos requeridos de la NSCoding protocolo que hereda de la Tarea clase, también declara un inicializador designado, init (nombre: hecho :).

Como en Objective-C, el súper palabra clave se refiere a la superclase, la Tarea clase en este ejemplo. Hay un detalle importante que merece atención. Antes de invocar el init (nombre :) método en la superclase, cada propiedad declarada por la Que hacer clase necesita ser inicializada En otras palabras, antes de la Que hacer La clase delega la inicialización a su superclase, cada propiedad definida por el Que hacer La clase necesita tener un valor inicial válido. Puede verificar esto cambiando el orden de las declaraciones e inspeccionando el error que aparece.

Lo mismo se aplica a la init? (codificador :) método. Primero inicializamos hecho propiedad antes de invocar init? (codificador :) en la superclase.

Inicializadores y herencia

Cuando se trata de la herencia y la inicialización, hay algunas reglas a tener en cuenta. La regla para los inicializadores designados es simple..

  • Un inicializador designado necesita invocar un inicializador designado desde su superclase. En el Que hacer clase, por ejemplo, la init? (codificador :) método invoca el init? (codificador :) Método de su superclase. Esto también se conoce como delegar hasta.

Las reglas para los inicializadores de conveniencia son un poco más complejas. Hay dos reglas a tener en cuenta.

  • Un inicializador de conveniencia siempre necesita invocar otro inicializador de la clase en la que se define. Tarea clase, por ejemplo, la en eso() El método es un inicializador conveniente y la inicialización de los delegados a otro inicializador., init (nombre :) en el ejemplo Esto se conoce como delegar a través de.
  • Aunque un inicializador de conveniencia no tiene que delegar la inicialización a un inicializador designado, un inicializador de conveniencia debe llamar a un inicializador designado en algún momento. Esto es necesario para inicializar completamente la instancia que se está inicializando..

Con ambas clases modelo implementadas, es hora de refactorizar el ViewController y AddItemViewController clases Empecemos por lo último..

2. Refactorización AddItemViewController

Paso 1: Actualiza el AddItemViewControllerDelegate Protocolo

Los únicos cambios que necesitamos hacer en el AddItemViewController clase están relacionados con la AddItemViewControllerDelegate protocolo. En la declaración de protocolo, cambie el tipo de didAddItem desde Cuerda a Que hacer, la clase modelo que implementamos anteriormente.

protocol AddItemViewControllerDelegate func controller (_ controller: AddItemViewController, didAddItem: ToDo)

Paso 2: Actualiza el crear(_:) Acción

Esto significa que también necesitamos actualizar el crear(_:) Acción en la que invocamos el método delegado. En la implementación actualizada, creamos un Que hacer instancia, pasándolo al método delegado.

@IBAction func create (_ sender: Any) if let name = textField.text // Create Item let item = ToDo (nombre: nombre, hecho: falso) // Notificar al delegado delegado? .Controller (self, didAddItem: item )

3. Refactorización ViewController

Paso 1: Actualiza el artículos Propiedad

los ViewController La clase requiere un poco más de trabajo. Primero necesitamos cambiar el tipo de artículos propiedad a [Que hacer], una serie de Que hacer instancias.

var items: [ToDo] = [] didSet (oldValue) let hasItems = items.count> 0 tableView.isHidden =! hasItems messageLabel.isHidden = hasItems

Paso 2: Métodos de fuente de datos de vista de tabla

Esto también significa que necesitamos refactorizar algunos otros métodos, como el tableView (_: cellForRowAt :) método que se muestra a continuación. Porque el artículos matriz ahora contiene Que hacer instancias, verificar si un elemento está marcado como hecho es mucho más simple. Utilizamos el operador condicional ternario de Swift para actualizar el tipo de accesorio de la celda de vista de tabla.

func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell // Fetch Item let item = items [indexPath.row] // Dequeue Cell let cell = tableView.dequeueReusableCell (withIdentifier: "TableViewCell", para: indexPath ) // Configurar Cell cell.textLabel? .Text = item.name cell.accessoryType = item.done? .checkmark: .none devolver celda

Cuando el usuario elimina un elemento, solo necesitamos actualizar el artículos propiedad mediante la eliminación de la correspondiente Que hacer ejemplo. Esto se refleja en la implementación del tableView (_: commit: forRowAt :) método mostrado abajo.

func tableView (_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) if editingStyle == .delete // Update Items items.remove (at: indexPath.row) // Update Table View tableView.deleteRows (at : [indexPath], con: .right) // Save State saveItems ()

Paso 3: Métodos de delegado de vista de tabla

La actualización del estado de un elemento cuando el usuario toca una fila se maneja en el tableView (_: didSelectRowAt :) método. La implementación de este UITableViewDelegate El método es mucho más sencillo gracias a la Que hacer clase.

func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) tableView.deselectRow (at: indexPath, animated: true) // Fetch Item let item = items [indexPath.row] // Actualizar item item.done =! item. hecho // Recuperar celda dejar celda = tableView.cellForRow (at: indexPath) // Actualizar celda? .accessoryType = item.done? .checkmark: .none // Save State saveItems ()

El correspondiente Que hacer La instancia se actualiza y este cambio se refleja en la vista de tabla. Para salvar el estado, invocamos. saveItems () en lugar de saveCheckedItems ().

Paso 4: Agregar métodos de delegado de controlador de vista de elemento

Porque actualizamos el AddItemViewControllerDelegate protocolo, también necesitamos actualizar el ViewControllerImplementación de este protocolo. El cambio, sin embargo, es simple. Solo necesitamos actualizar la firma del método..

func controller (_ controller: AddItemViewController, didAddItem: ToDo) // Actualizar la fuente de datos items.append (didAddItem) // Guardar estado saveItems () // Volver a cargar la tabla tableView.reloadData () // Descartar Add Item View Controller despide ( animado: verdadero)

Paso 5: Guardar elementos

los pathForItems () Método

En lugar de almacenar los elementos en la base de datos predeterminada del usuario, los almacenaremos en el directorio de documentos de la aplicación. Antes de actualizar el loadItems () y saveItems () métodos, vamos a implementar un método auxiliar llamado pathForItems (). El método es privado y devuelve una ruta, la ubicación de los elementos en el directorio de documentos.

private func pathForItems () -> String guard let documentsDirectory = NSSearchPathForDirectoriesInDomains (.documentDirectory, .userDomainMask, true) .first, let url = URL (string: documentsDirectory) else fatalError ("Documents Directory Not Found")) return url. appendingPathComponent ("items"). path

Primero obtenemos la ruta al directorio de documentos en el entorno limitado de la aplicación invocando NSSearchPathForDirectoriesInDomains (_: _: _ :). Debido a que este método devuelve una matriz de cadenas, tomamos el primer elemento.

Tenga en cuenta que usamos un Guardia declaración para asegurarse de que el valor devuelto por NSSearchPathForDirectoriesInDomains (_: _: _ :) es válida. Lanzamos un error fatal si esta operación falla. Esto termina inmediatamente la aplicación. ¿Por qué hacemos esto? Si el sistema operativo no puede entregarnos la ruta al directorio de documentos, tenemos más problemas de los que preocuparnos..

El valor del que volvemos pathForItems () se compone de la ruta al directorio de documentos con la cadena "artículos" anexado a ello.

los loadItems () Método

El método loadItems cambia bastante. Primero almacenamos el resultado de pathForItems () en una constante, camino. Luego, desarchivamos el objeto archivado en esa ruta y lo convertimos a una matriz opcional de Que hacer instancias. Utilizamos el enlace opcional para desenvolver el opcional y asignarlo a una constante, artículos. En el Si cláusula, le asignamos el valor almacenado en artículos al artículos propiedad.

private function loadItems () let path = pathForItems () si let items = NSKeyedUnarchiver.unarchiveObject (withFile: path) como? [ToDo] self.items = items

los saveItems () Método

los saveItems () El método es corto y simple. Almacenamos el resultado de pathForItems () en una constante, camino, y invocar archiveRootObject (_: toFile :) en NSKeyedArchiver, pasando en el artículos propiedad y camino. Imprimimos el resultado de la operación a la consola..

private func saveItems () let path = pathForItems () if NSKeyedArchiver.archiveRootObject (self.items, toFile: path) print ("Successfully Saved") else print ("Saving Failed")

Paso 6: Limpiar

Acabemos con la parte divertida, borrando código. Comience por quitar el artículos revisados Propiedad en la parte superior ya que ya no la necesitamos. Como resultado, también podemos eliminar el loadCheckedItems () y saveCheckedItems () métodos, y toda referencia a estos métodos en el ViewController clase.

Construye y ejecuta la aplicación para ver si todo sigue funcionando. El modelo de datos hace que el código de la aplicación sea mucho más simple y confiable. Gracias a Que hacer clase, administrar los elementos de nuestra lista es ahora mucho más fácil y menos propenso a errores.

Conclusión

En esta lección, hemos refactorizado el modelo de datos de nuestra aplicación. Aprendiste más sobre la programación orientada a objetos y la herencia. La inicialización de instancias es un concepto importante en Swift, así que asegúrese de entender lo que hemos cubierto en esta lección. Puede leer más sobre la delegación de inicialización y inicialización en The Swift Programming Language.

Mientras tanto, echa un vistazo a algunos de nuestros otros cursos y tutoriales sobre el desarrollo de Swift en iOS.!