Está en la página 1de 19

Diario Swift Da 1 Opcionales

Tal como dije en mi ltimo artculo del 2015, una de mis prioridades de 2016 es aprender Swift. Creo que lo mejor para eso es ir
registrando en mi blog lo que voy descubriendo y aprendiendo. Seguro que a alguien podr serle de utilidad y adems a mi me
ayudar a asentar los conocimientos que vaya adquiriendo.
Esta serie de artculos que empieza aqu tratar de Swift y de lo que ir aprendiendo.

Qu es un opcional?
La primera vez que intent hacer algo con Swift, una de las primeras cosas que me ech para atrs fueron los opcionales. En
ese momento, no entend ni su significado, ni su funcionamiento ni mucho menos su utilidad. No estaba concentrado
posiblemente y al ver ese nuevo concepto que para mi era completamente nuevo, decid dejar de lado Swift y programar mi app
con el viejo Objective C.
Empecemos con un ejemplo:
1 var nombre: String?
nombre es una variable del tipo String?, es decir Optional. No es un String! Esta variable puede contener un valor o no.
No, esto no es el experimento del gato de Schrdinger.
Para utilizar esta variable, primero debemos extraer (unwrap) su valor. No es posible realizar ninguna accin que se realiza con
un String antes de realizar el unwrap.
Por supuesto, es un error definir una constante como un opcional utilizando let. Una constante no puede ser de un tipo
opcional, ya que una constante slo puede contener un valor. Un opcional puede contener algo o puede ser nil. Definir un
opcional como una constante provocar un error en tiempo de compilacin.
Cmo se utiliza un opcional?
Existen varias tcnicas para utilizar un opcional. No puedo traducir la mayora de las expresiones porque la verdad no sabra
traducir algunas palabras. De todas formas, la mayor parte de la documentacin de Swift (y de programacin en general) est en
ingls.

Conditional binding
1 var nombre: String?
2 if let nombreExtraido = nombre {
3
print("nombre: \(nombreExtraido)")

4 } else {
5
print("nombre es nil.")
6 }
Este ejemplo muestra como extraer el valor utilizando una tcnica llamada Conditional binding. Primero asignamos la variable a
una constante y si esta constante no es nil, la utilizamos. En caso de ser nil, mostramos un mensaje indicando que la variable
es nil. Esta manera es la que ms he utilizado hasta hoy sin duda.

Forced unwrapping
1 var nombre: String?
2 nombre = "Miguel"
3 print("nombre: \(nombre!)")
Esta tcnica se utiliza slo cuando ests completamente seguro de que la variable nombre tiene efectivamente un valor. Al
utilizar el modificador ! ests forzando la extraccin de la variable. Esto slo funciona si la variable tiene un valor. Si la variable
no tiene valor, esto provocara un error en tiempo de ejecucin. Esta tcnica puede resultar peligrosa por lo tanto.

Nil coalescing
1 var nombre: String?
2 let nombreNuevo = nombre ?? "Miguel"
3 print("nombreNuevo: \(nombreNuevo)")
Vaya nombrecito Esta tcnica me recuerda al operador condicional y ternario ? de php. Consiste en algo muy parecido.
Comprueba si el opcional tiene un valor y si es as se lo asigna a tu constante, si no tiene valor, asigna el valor que t indiques.
En el caso de mi ejemplo, la constante nombreNuevo contendr el valor de la variable nombre slo si esta contiene un valor.
En cambio si no contiene nada nil, la constante nombreNuevo contendr el valor Miguel. En los dos casos, la constante
resultante ser del tipo String y no ser un opcional.

Optional chaining
La ltima tcnica para extraer un valor de un opcional es utilizando Optional chaining. Esta tcnica es muy utilizada cuando ests

tratando con objetos Veamos el siguiente ejemplo:


1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
1
5

class Post {
var autor: Persona?
}
class Persona {
var nombre: String?
}
var p: Post?
[...] // Aqu ocurren cosas maravillosas.
let nombreAutor = p?.autor?.nombre
print("nombreAutor: \(nombreAutor)")

nombreAutor contendr el nombre del autor del artculo p slo si se cumplen las siguientes condiciones:
p contiene un valor y no es nil. Este valor debe ser un objeto del tipo Post.
El autor de p contiene un valor y no es nil. Este valor debe ser un objeto del tipo Persona.
El nombre del autor contiene un valor y no es nil. Este valor debe ser un String.
En lugar de las [], escribo el siguiente cdigo:
1
2
3
4
5

var a: Autor? // a es nil.


p = Post() // p ya no es nil.
a = Persona() // a ya no es nil.
p?.autor = a // el autor de p ya no es nil, es a.
a?.nombre = "Miguel" // el nombre del autor a ya no es nil, es "Miguel".

Primero, defino una variable a del tipo opcional Autor. Despus inicializo p con el constructor por defecto. Ahora mismo p
ya no es nil, contiene un valor, pero el autor de p sigue siendo nil. Por lo tanto, nombreAutor seguir siendo nil.
Despus inicializo el autor a con el constructor por defecto de Persona. Ahora, a ya no es nil, pero el nombre sigue
sindolo. Por lo tanto, la ltima condicin sigue sin cumplirse as que nombreAutor sigue siendo nil.
Finalmente, asigno la cadena Miguel a la propiedad nombre de a. Por lo tanto, ahora s, el valor de nombreAutor ser
Miguel.
El cdigo todo junto:
1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8

class Post {
var autor: Persona?
}
class Persona {
var nombre: String?
}
var p: Post? // p es nil.
var a: Autor? // a es nil.
p = Post() // p ya no es nil.
a = Persona() // a ya no es nil.
p?.autor = a // el autor de p ya no es nil, es a.
a?.nombre = "Miguel" // el nombre del autor a ya no es nil, es "Miguel"
let nombreAutor = p?.autor?.nombre
print("nombreAutor: \(nombreAutor)")

// Si p no es nil Y el autor de p no es nil, se asigna el valor del nombre del autor d


// nombreAutor: Miguel

Implicitly unwrapped optional


1 var nombre: String! // Estoy seguro de que nombre tiene valor.
2 nombre = "Miguel" // Asigno un valor.
3 print("nombre: \(nombre)") // Si no tiene valor, tendr problemas.
Qu significa esto? En este caso, estamos definiendo una variable opcional String pero indicando que contiene un valor y por
lo tanto no es necesario extraerla. Es decir puedes utilizarla directamente como si fuera un String ahorrndote las tcnicas para
extraer el cdigo.
El problema es que si tu variable no contiene valor, tendrs un error en tiempo de ejecucin, as que ten mucho cuidado al utilizar
este mtodo.
Podrs ver que esta tcnica se utiliza con los IBOutlet, al conectar elementos de tu vista con tu controlador. Esto es porque
estos IBOutlet son nil hasta el momento en el que aparecen en pantalla. Por lo tanto en el momento en el que tu los vas a
utilizar y trabajar con ellos (viewDidLoad, viewWillAppear, ), no sern nil. Pero en el momento de inicializar el controlador,
estos elementos sern nil. Slo despus de cargar la vista, tendrn un valor.

Por qu usar un opcional?


Aunque al principio, suena raro y no lo entiendes del todo, poco a poco irs descubriendo que es muy cmodo trabajar con
opcionales. La mejor manera de verlo creo que es al utilizar Optional chaining.
Volviendo al cdigo de ejemplo que he puesto ms arriba, lo volver a escribir ahora sin utilizar opcionales y confrontar las dos
versiones.
1
2
3
4
5
6
7
8
9
1
0

class Post {
var autor: Persona

// Sin opcional, es obligatorio tener constructor.

init(autor: Persona) {
self.autor = autor
}
}
class Persona {
var nombre: String = ""
}

// En este caso, usar valor por defecto.

1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8

var a = Persona()
var p = Post(autor: a)
p.autor = a
a.nombre = "Miguel"
let nombreAutor = p.autor.nombre

En este caso, estoy obligado a definir valores por defecto o constructores ya que no puedo usar nil al no usar opcionales.
Cuando creo un objeto Post, tengo que pasarle el objeto Persona. Si se puede dar el caso de que un objeto Post no tenga autor,
entonces no puedo asignarlo a nil. Para utilizar nil, tengo que utilizar opcionales.
En Objective-C, lo que hacas era comprobar si tu variable era nil antes de utilizarlo o no? No! En Objective-C, podas
programar sin preocupaciones hasta que un buen da tu app dejaba de funcionar porque la variable que t pensabas deba
contener algo, contiene nil. S sincero y reconoce que has pasado horas y horas mirando tu cdigo sin entender por qu haba
fallado esto, hasta que te das cuenta que existe un remoto caso que t no habas previsto en el que esa variable que t
pensabas que tena que traer siempre un valor, de repente es nil. Los opcionales terminan con todo esto al menos en parte. Al
tener que extraer el valor de las variables no podrs tener ese error. Slo si te gusta vivir peligrosamente utilizando el mtodo
implcito podrs seguir cometiendo esos mismos errores.
http://donmik.com/diario-swift-dia-1-opcionales/

Diario Swift Da 2 Core Data


Core Data es otra de las palabrejas con las que tendrs que lidiar si haces una app para iOS ms o menos compleja.
Para mi aprender un nuevo lenguaje, es como aprender un nuevo idioma. Al principio, pienso en mi idioma nativo e intento
traducirlo al nuevo idioma. Con prctica, tiempo y trabajo, este proceso de traduccin acaba desapareciendo poco a poco. Mi

lenguaje nativo es PHP as que he tratado de buscar similitudes entre Swift y PHP
Conoc Core Data cuando empec con Objective C. Poco ha cambiado con Swift y lo he agradecido porque la primera vez que
me met con Core Data, me cost adaptarme a ello.

Core Data no es MySQL


Cuando me encontr con Core Data, yo estaba buscando el equivalente a una base de datos MySQL. Core Data es un
framework para manejar la persistencia de datos. Segn Apple, Core Data te permite ahorrar mucho cdigo para manejar tu
modelo de datos a la hora de guardar, eliminar, recuperar datos de tu modelo. No digo que no sea verdad pero como vers ms
adelante, utilizar Core Data puede no ser tan intuitivo como me gustara.

Core Data Stack


El stack de Core Data es el nombre que se le da al cdigo generado por Xcode que consiste en crear una serie de objetos que
nos permitirn guardar y recuperar informacin de Core Data.
Existen cuatro clases que es imprescindible conocer cuando utilizas Core Data. Las cuatro clases conforman nuestro Core Data
Stack.

NSManagedObjectModel
Es el modelo de datos. Representa cada objeto de nuestro modelo con sus propiedades, relaciones, Para dejarlo claro, es
como el esquema de una base de datos de MySQL.
En el proyecto de Xcode, tendrs un archivo con la extensin .xcdatamodeld que contendr el modelo de datos. Aqu se
pueden crear entidades con atributos y relaciones entre ellas.

NSPersistentStore

Es el almacenamiento que utiliza nuestro stack. Se pueden escoger cuatro tipos diferentes de almacenamiento:
NSQLiteStoreType: Una base de datos SQLite. Es la opcin ms utilizada y seleccionada por defecto.
NSXMLStoreType: Un archivo XML. Slo se puede utilizar para OS X, as que tampoco me interesa.
NSBinaryStoreType: Un archivo binario de datos. Tanto este tipo como el XML, son muy poco utilizados porque requieren ser
cargados completamente en memoria antes de poder utilizarlos, lo cual requiere un consumo de recursos poco eficiente.
NSInMemoryStoreType: Un almacenamiento en memoria. Lo curioso aqu es que este tipo de almacenamiento no persiste si
cierras tu app o apagas tu dispositivo. Por lo tanto, no es de mucha utilidad.

NSPersistentStoreCoordinator
Esta clase hace de intermediaria entre el modelo de datos (NSManagedObjectModel) y el almacenamiento (NSPersistentStore).
Permite por ejemplo abstraerte del tipo de almacenamiento seleccionado. Hace las funciones de un ORM, me recuerda a
Doctrine por ejemplo que me permite abstraer el tipo de base de datos que estoy utilizando.

NSManagedObjectContext
Esta clase es la ms importante y la clase con la que trabajars da a da constantemente. Representa el contexto en el que
ests trabajando. Todas tus operaciones de lectura y escritura se desarrollan en el contexto. Los cambios que realices en el
contexto no sern almacenados en disco hasta que hagas una llamada al mtodo save().
Un objeto est fuertemente ligado a un contexto y no puede cambiar de contexto a lo largo de su ciclo de vida. Esto se convertir
en algo muy a tener en cuenta cuando utilices en tu app mltiples hilos de ejecucin y necesites acceder a un mismo objeto
desde ellos y modificarlo.

Cuatro operaciones bsicas: Insertar, Recuperar, Modificar y Eliminar


Voy a crear un proyecto de pruebas de Xcode para mostrar como realizar las cuatro operaciones bsicas. Me voy a saltar todos
los pasos previos de crear proyecto, seleccionar Core Data, Todo esto, lo podrs conseguir t slo estoy seguro y sino, no
dudes en preguntar en los comentarios.

Modelo de datos

Sin complicaciones, mi modelo de datos se compone de 2 entidades con una relacin entre ellas.
Entidad: Persona
nombre String
edad Integer 32
Entidad: Marca
marca String
ano_compra Integer 32
Relacin: Una persona puede ser propietaria de muchas coches (To Many) y un coche slo tiene un propietario (To One).

Insertar
1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3

func insertar() {
// Insertar una persona.
// Crear un tipo de entidad: Persona.
let personaEntity = NSEntityDescription.entityForName("Persona", inManagedObjectContext: managedContext)
// Crear una entidad del tipo: personaEntity o sea Persona.
let persona = Persona(entity: personaEntity!, insertIntoManagedObjectContext: managedContext)
// Asignar valores.
persona.nombre = "Miguel"
persona.edad = 32
// Insertar un coche.
// Crear un tipo de entidad: Coche.
let cocheEntity = NSEntityDescription.entityForName("Coche", inManagedObjectContext: managedContext)
// Crear una entidad del tipo: cocheEntity o sea Coche.
let coche = Coche(entity: cocheEntity!, insertIntoManagedObjectContext: managedContext)
// Asignar valores.
coche.marca = "Ferrari"

1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2
2
3
2
4
2
5
2
6
2
7
2
8

coche.ano_compra = 2045
// Relacionar el propietario con el coche.
coche.propietario = persona
// Guardar en disco.
do {
try managedContext.save()
} catch let error as NSError {
print("Error al insertar: \(error)")
}
}

El cdigo no es muy complicado pero intuitivo, intuitivo no es. Primero, debes insertar en el contexto una nueva entidad del tipo
que necesitas. Por eso, necesitas el primer paso para crear el tipo de entidad que necesitas, luego insertas un nuevo
NSManagedObject de ese tipo. Finalmente, debes llamar al mtodo save() para que tu contexto termine guardando los cambios
en disco, sino ninguno de tus cambios permanecer en el siguiente arranque de la app.

Recuperar
El siguiente paso es recuperar los datos de esa persona que acabo de crear y su coche. As de paso, compruebo que he
insertado algo realmente en la base de datos y no estoy haciendo esto para nada.
1
2
3
4
5
6
7
8
9
1
0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1

func recuperarPersona(nombre: String?) -> Persona? {


let fetchRequest = NSFetchRequest(entityName: "Persona")
if let nombre = nombre {
fetchRequest.predicate = NSPredicate(format: "nombre == %@", nombre)
}
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
if results.count > 0 {
let p = results.first! as! Persona
print("Encontrada una persona: \(p.nombre!) - \(p.edad!) con \(p.coches!.count) coche.")
if let coches_de_p = p.coches {
for c in coches_de_p {
let c = c as! Coche
print("Coche de \(p.nombre!): \(c.marca!) - \(c.ano_compra!)")
}
}
return p
} else {
print("No hay personas.")
return nil
}
} catch let error as NSError {
print("Error al recuperar: \(error)")
}
return nil
}

2
2
2
3
2
4
2
5
2
6
2
7
2
8
Parece que s que hay datos por lo tanto, est funcionando todo correctamente.
Para recuperar informacin, he utilizado NSFetchRequest con un simple predicado. No voy a entrar en detalles en este artculo
con esta clase pero es donde reposa toda la dificultad y la utilidad de Core Data. Con esta clase, podrs recuperar la informacin
de tu base de datos. Tiene muchas posibilidades, podrs utilizar predicados con NSPredicate para filtrar tus datos, podrs
ordenarlos utilizando NSSortDescriptor, limitar la cantidad de datos,
En otro artculo, tratar de hablar de todo lo que se puede hacer con NSFetchRequest. Bueno, quizs no todo, pero s muchas
cosas.

Modificar
1
2
3
4
5
6
7
8
9
1

func modificar(p: Persona) {


// Cambiar los datos de la Persona p.
p.nombre = "Manolo"
p.edad = 23
// Insertar un nuevo coche.
// Crear un tipo de entidad: Coche.
let cocheEntity = NSEntityDescription.entityForName("Coche", inManagedObjectContext: managedContext)
// Crear una entidad del tipo: cocheEntity o sea Coche.
let coche = Coche(entity: cocheEntity!, insertIntoManagedObjectContext: managedContext)

0
1
1
1
2
1
3
1
4
1
5
1
6
1
7
1
8
1
9
2
0
2
1
2
2

// Asignar valores.
coche.marca = "Seat"
coche.ano_compra = 1989
// Relacionar el propietario con el coche.
coche.propietario = p
do {
try managedContext.save()
} catch let error as NSError {
print("Error al modificar: \(error)")
}
}

Posiblemente, de lo ms sencillo, una vez que tienes el objeto en una variable. Modificarlo es coser y cantar.

Eliminar
1
2
3
4
5
6

func eliminar(p: Persona) {


managedContext.deleteObject(p)
do {
try managedContext.save()

7
8
9
1
0

} catch let error as NSError {


print("Error al eliminar: \(error)")
}
}

Igual que con modificar, si tienes el objeto recuperado, eliminarlo es muy sencillo. Puedes ver en la consola que ya no hay
personas en la base de datos, por lo tanto ha sido eliminada correctamente.

Una simple introduccin


Core Data es mucho ms complejo de lo que acabo de mostrarte en este artculo. Si quieres utilizarlo, tendrs que investigar
NSFetchRequest, NSPredicate, Acabars trabajando con NSFetchedResultsController y NSAsynchronousFetchRequest.
Todos estos temas son lo suficientemente amplios como para ocupar un post entero cada uno, as que hablar de ellos ms
adelante.
A continuacin, os dejo el cdigo completo de mi ViewController.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

import UIKit
import CoreData
class ViewController: UIViewController {
var managedContext: NSManagedObjectContext!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
// Insertar persona y coche.
insertar()
// Recuperar persona.
if let p = recuperarPersona("Miguel") {
// Modificar persona.
modificar(p)
}

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

// Recuperar persona modificada.


if let p_modificada = recuperarPersona("Manolo") {
// Eliminar persona modificada.
eliminar(p_modificada)
}
// Recuperar persona eliminada, o sea no obtendrs nada.
let _ = recuperarPersona("Manolo")
}
func insertar() {
// Insertar una persona.
// Crear un tipo de entidad: Persona.
let personaEntity = NSEntityDescription.entityForName("Persona",
inManagedObjectContext: managedContext)
// Crear una entidad del tipo: personaEntity o sea Persona.
let persona = Persona(entity: personaEntity!, insertIntoManagedObjectContext:
managedContext)
// Asignar valores.
persona.nombre = "Miguel"
persona.edad = 32
// Insertar un coche.
// Crear un tipo de entidad: Coche.
let cocheEntity = NSEntityDescription.entityForName("Coche",
inManagedObjectContext: managedContext)
// Crear una entidad del tipo: cocheEntity o sea Coche.
let coche = Coche(entity: cocheEntity!, insertIntoManagedObjectContext:
managedContext)
// Asignar valores.
coche.marca = "Ferrari"
coche.ano_compra = 2045
// Relacionar el propietario con el coche.
coche.propietario = persona
// Guardar en disco.
do {

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92

try managedContext.save()
} catch let error as NSError {
print("Error al insertar: \(error)")
}
}
func recuperarPersona(nombre: String?) -> Persona? {
let fetchRequest = NSFetchRequest(entityName: "Persona")
if let nombre = nombre {
fetchRequest.predicate = NSPredicate(format: "nombre == %@", nombre)
}
do {
let results = try managedContext.executeFetchRequest(fetchRequest)
if results.count > 0 {
let p = results.first! as! Persona
print("Encontrada una persona: \(p.nombre!) - \(p.edad!) con \
(p.coches!.count) coche.")
if let coches_de_p = p.coches {
for c in coches_de_p {
let c = c as! Coche
print("Coche de \(p.nombre!): \(c.marca!) - \(c.ano_compra!)")
}
}
return p
} else {
print("No hay personas.")
return nil
}
} catch let error as NSError {
print("Error al recuperar: \(error)")
}
return nil
}
func modificar(p: Persona) {

93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121

// Cambiar los datos de la Persona p.


p.nombre = "Manolo"
p.edad = 23
// Insertar un nuevo coche.
// Crear un tipo de entidad: Coche.
let cocheEntity = NSEntityDescription.entityForName("Coche",
inManagedObjectContext: managedContext)
// Crear una entidad del tipo: cocheEntity o sea Coche.
let coche = Coche(entity: cocheEntity!, insertIntoManagedObjectContext:
managedContext)
// Asignar valores.
coche.marca = "Seat"
coche.ano_compra = 1989
// Relacionar el propietario con el coche.
coche.propietario = p
do {
try managedContext.save()
} catch let error as NSError {
print("Error al modificar: \(error)")
}
}
func eliminar(p: Persona) {
managedContext.deleteObject(p)
do {
try managedContext.save()
} catch let error as NSError {
print("Error al eliminar: \(error)")
}
}
}

http://donmik.com/diario-swift-dia-2-core-data/

Diario Swift Da 3 Core Data y NSFetchRequest


En este tercer artculo sobre mi aprendizaje de Swift, vuelvo a hablar de Core Data pero esta vez me centrar en
NSFetchRequest o cmo recuperar la informacin almacenada en mi base de datos.

Cuatro mtodos de utilizar NSFetchRequest


Existen cuatro, s, cuatro mtodos de utilizar NSFetchRequest. Yo tambin me sorprend al descubrirlo.
Mtodo 1:
1 // Creo una Fetch Request con el constructor sin parmetros.
2
let fetchRequest = NSFetchRequest()
3
// Creo una entidad "Persona"
4
let entity = NSEntityDescription.entityForName("Persona", inManagedObjectContext: managedContext)
5
// Asigno la entidad a mi Fetch Request.
6
fetchRequest.entity = entity
Creo una instancia de NSFetchRequest y una instancia de la entidad que quiero utilizar. Despus asigno esa entidad a la
instancia de NSFetchRequest. El segundo mtodo es mucho ms sencillo y por eso ser seguramente el que ms utilices.
Mtodo 2:
1 let fetchRequest = NSFetchRequest(entityName: "Persona")
En una simple lnea hago exactamente lo mismo que el primer mtodo. Utilizo un constructor que me permite pasarle el nombre
de la entidad que quiero utilizar.
Mtodo 3:

También podría gustarte