Ao crear un tipo, xa sexa Class
ou Struct
, creamos unha abstracción para poder traballar no noso código:
- Son construcións flexibles de propósito xeral que se converten como se fosen os bloques de construción do código.
- Definimos propiedades e métodos para agregarlle funcionalidade, utilizando a mesma sintaxe que utilizamos para definir constantes, variables e funcións.
No seguinte exemplo, vou crear unha Struct
para crear un tipo Usuario
que teña unha serie de propiedades e métodos:
struct Usuario {
let nome: String
let cidade: String
func createUser() {
print("Creando usuario...")
}
func removeUser() {
print("Borrando usuario")
}
}
Acabamos de crear o noso primeiro tipo, creamos unha Struct
que encapsula os datos que debe almacenar un Usuario
(propiedades) e certas operacións que podo facer con eses datos (métodos para crear ou borrar un Usuario
).
NOTA:
Que diferenza hai entre un método e unha función? Ningunha, Os métodos son funcións que están asociados a un tipo en particular.
Class vs Struct #
Aínda que unha Class
e Struct
úsanse con propósitos diferentes, as dúas teñen cousas en común:
- Crear propiedades para almacenar valores
- Crear métodos para crear lóxica
- Crear inicializadores
- Conformar protocolos para engadir máis funcionalidade
Pero a Class
ten funcionalidade adicional que non atopamos na Struct
:
- Podemos usar herdanza só en
Class
- Podemos deinicializar unha
Class
para liberar recursos - Podemos ter a mesma referencia da instancia dunha Class en varias partes do noso código (a Class é reference type e Struct é value type)”
Sintaxe #
Vamos a comparar a sintaxe dunha estrutura cunha clase equivalente:
struct ExemploEstrutura {
var nome: String
func construirSaudo() -> String{
return "Ola " + nome
}
}
class ExemploClase {
var nome: String
func construirSaudo() -> String{
return "Ola " + nome
}
}
Ademais do uso da palabra chave struct
en lugar de class
, as dúas declaracións son idénticas.
Instancias #
Para poder usar no noso código os novos tipos ExemploClase
e ExemploEstrutura
o primeiro que debemos facer é crear unha instancia deles.
En Swift, unha instancia é un obxecto concreto dunha Class
ou Struct
.
- Cando creas unha instancia, asígnase espazo en memoria para almacenar os datos desa
Class
ouStruct
- Tamén se inicializan cos seus valores iniciais.
- Ao crear a instancia podes usar as propiedades e métodos do tipo.
Ao crear unha instancia dos nosos tipos é obrigatorio dar un estado inicial a todas as súas propiedades.
- Para dar un estado inicial adóitase utilizar un inicializador, unha función que recibe valores para asignalos ás propiedades, desta maneira poden ter un valor e pódese crear a instancia dun tipo correctamente.
- Para crear un inicializador usamos o nome
init
e pasamos tantos parámetros como propiedades debamos inicializar no noso tipo.
Así, para poder instanciar a Class
ou a Struct
que acabamos de crear, necesitamos darlle un valor por defecto ás propiedades nome
. Para facelo usamos o inicializador, o noso inicializador acepta un parámetro de entrada que se acaba asignando á propiedade nome
(liñas 4 e 16):
struct ExemploEstrutura {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
class ExemploClase {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
Agora xa so falta crear as instancias da Class
e da Struct
, no seguinte exemplo se corresponde coas dúas últimas liñas. A instancia da clase é miClase
e a da estructura miEstrutura
:
struct ExemploEstrutura {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
class ExemploClase {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
var miEstrutura = ExemploEstrutura(nome: "Lola Mento")
var miClase = ExemploClase(nome: "Aitor Tilla")
Agora, xa podemos acceder aos métodos da clase e da estrutura a través das súas instancias. Velaquí o exemplo completo:
struct ExemploEstrutura {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
class ExemploClase {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
var miEstrutura = ExemploEstrutura(nome: "Lola Mento")
var miClase = ExemploClase(nome: "Aitor Tilla")
print("\(miEstrutura.construirSaudo())")
//Imprime: Ola Lola Mento
print("\(miClase.construirSaudo())")
//Imprime: Ola Aitor Tilla
Paso por valor vs Paso por referencia #
Dada a similitude entre clases e estruturas, é importante comprender como difiren as dúas. Antes de explorar a diferenza máis significativa, primeiro é necesario comprender os conceptos paso por valor e paso por referencia.
A maior diferencia entre as estruturas e as clases está cando as instancias de estrutura e clase se pasan como argumentos a métodos ou funcións.
- As instancias de estrutura pásanse por valor. Esto significa que se crea unha copia da instancia cos datos contidos na estrutura orixinal. Se cambiamos a copia, non se producen cambios na estrutura orixinal
- As instancias de clase pásanse por referencia. Neste caso, non se crea unha copia, senón que pasamos a referencia da localización na memoria onde está a instancia. Noutras palabras, só hai unha instancia de clase, pero poden existir varias referencias que apuntan a ela
Vemos o explicado anteriormente no seguinte exemplo:
struct ExemploEstrutura {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
class ExemploClase {
var nome: String
init(nome: String) {
self.nome = nome
}
func construirSaudo() -> String{
return "Ola " + nome
}
}
var miEstrutura = ExemploEstrutura(nome: "Lola Mento")
var miEstruturaCopia = miEstrutura
miEstruturaCopia.nome = "Pepe Viyuela"
print("Nome: \(miEstrutura.nome)") //Imprime: Nome: Lola Mento
print("Nome: \(miEstruturaCopia.nome)") //Imprime: Nome: Pepe Viyuela
var miClase = ExemploClase(nome: "Aitor Tilla")
print("Nome: \(miClase.nome)") //Imprime: Nome: Aitor Tilla
var miClaseCopia = miClase
miClaseCopia.nome = "Pepe Viyuela"
print("Nome: \(miClase.nome)") //Imprime: Nome: Pepe Viyuela
print("Nome: \(miClaseCopia.nome)") //Imprime: Nome: Pepe Viyuela
Como podemos observar:
- Estruturas → pasan por valor: Na
liña 26
copiamosmiEstrutura
enmiEstruturaCopia
. Como son estruturas, a copia é por valor (créase unha copia demiEstrutura
), polo que ao modificar unha propiedade enmiEstruturaCopia
(liña 27
) non afecta á estrutura orixinal (miEstrutura
) que mantén o seu valor. Isto o vemos nasliñas 28 e 29
- Clases → pasan por referencia: Na
liña 34
copiamosmiClase
enmiClaseCopia
. Como son clases, non hai copia, o paso é por referencia, polo que ao modificar unha propiedade enmiClaseCopia
(liña 35
) afecta á clase orixinal (miClase
). Isto o vemos naliña 36
, en lugar de imprimir o valor inicial demiClase.nome
(«Aitor Tilla», tal e como imprime naliña 33
) imprime «Pepe Viyuela» (valor cambiado enmiClaseCopia.nome
)
Empregar estruturas ou clases? #
- En xeral, as estruturas recoméndanse sempre que sexa posible porque son máis eficientes que as clases e máis seguras. Por defecto, emprega sempre que podas
Struct
- As clases deben usarse cando:
- Necesitamos herdanza,
- Só requírese unha instancia dos datos encapsulados
- Se requirimos paso por referencia
- Se queremos tomar medidas adicionais para liberar recursos mediante
deinit{ }
.