Al ser un lenguaje moderno de alto nivel,
Swift básicamente se encarga de la administración de la memoria en sus aplicaciones, asignando y liberando memoria. Esto se debe a un mecanismo llamado
Conteo de referencia automático , o
ARC para abreviar. En esta guía, aprenderá cómo funciona ARC y cómo administrar adecuadamente la memoria en Swift. Al comprender este mecanismo, puede influir en la vida útil de los objetos ubicados en el montón (
montón ).
En esta guía, desarrollará su conocimiento de Swift y ARC al aprender lo siguiente:
- cómo funciona ARC
- ¿Qué son los ciclos de referencia y cómo solucionarlos correctamente?
- cómo crear un bucle de enlace de ejemplo
- Cómo encontrar bucles de enlace usando las herramientas visuales que ofrece Xcode
- cómo manejar tipos de referencia y tipos de valor
Empezando
Descargue los
materiales de origen. Abra el proyecto en la carpeta
Ciclos / Inicio . En la primera parte de nuestra guía, que comprende los conceptos clave, trataremos exclusivamente con el archivo
MainViewController.swif t.
Agregue esta clase en la parte inferior de MainViewController.swift:
class User { let name: String init(name: String) { self.name = name print("User \(name) was initialized") } deinit { print("Deallocating user named: \(name)") } }
La clase de
usuario se define aquí, que, con la ayuda de
las declaraciones de
impresión , nos indica acerca de la inicialización y liberación de la instancia de clase.
Ahora cree una instancia de la clase Usuario en la parte superior de MainViewController.
Coloque este código antes del método
viewDidLoad () :
let user = User(name: "John")
Inicia la aplicación. Haga que la consola Xcode sea visible con
Command-Shift-Y para ver el resultado de las declaraciones de impresión.
Observe que el
Usuario John se inicializó apareció en la consola, pero la declaración de impresión dentro de
deinit no se ejecutó. Esto significa que este objeto no se lanzó, ya que no salió del
alcance .
En otras palabras, hasta que el controlador de vista que contiene este objeto se salga del alcance, el objeto nunca se liberará.
¿Está en alcance?
Al envolver una instancia de la clase Usuario en un método, permitiremos que salga del alcance, permitiendo así que ARC la libere.
Vamos a crear el método
runScenario () dentro de la clase MainViewController y mover la inicialización de la instancia de la clase Usuario dentro de ella.
func runScenario() { let user = User(name: "John") }
runScenario () define el alcance de la instancia de usuario. Al salir de esta zona, el
usuario debe ser liberado.
Ahora llame a runScenario () agregando esto al final de viewDidLoad ():
runScenario()
Inicia la aplicación. La salida de la consola ahora se ve así:
El usuario John fue inicializado
Usuario desasignante llamado: John
Esto significa que ha liberado un objeto que ha abandonado el campo de visión.
Vida útil del objeto
La existencia del objeto se divide en cinco etapas:
- asignación de memoria: de la pila o del montón
- Inicialización: el código se ejecuta dentro de init
- uso de
- Desinicialización: el código se ejecuta dentro de Deinit
- memoria libre: la memoria asignada se devuelve a la pila o al montón
No hay una forma directa de rastrear los pasos de asignación y liberación de memoria, pero puede usar el código dentro de init y deinit.
Los recuentos de referencia , también conocidos como
recuentos de uso , determinan cuándo ya no se necesita un objeto. Este contador muestra el número de quienes "usan" este objeto. Un objeto se vuelve innecesario cuando el contador de uso es cero. Luego, el objeto se inicializa y se libera.

Cuando se inicializa el objeto Usuario, su recuento de referencia es 1, porque la constante del
usuario se refiere a este objeto.
Al final de runScenario (), el usuario sale del alcance y el recuento de referencias se reduce a 0. Como resultado, el usuario no se inicializa y luego se libera.
Ciclos de referencia
En la mayoría de los casos, ARC funciona como debería. El desarrollador generalmente no necesita preocuparse por las pérdidas de memoria cuando los objetos no utilizados permanecen sin asignar indefinidamente.
¡Pero no siempre! Posibles fugas de memoria.
¿Cómo puede pasar esto? Imagine una situación en la que dos objetos ya no se usan, pero cada uno de ellos se refiere al otro. Como cada recuento de referencia no es 0, ninguno de ellos se liberará.

Este es un
ciclo de referencia fuerte . Esta situación confunde el ARC y no le permite borrar la memoria.
Como puede ver, el recuento de referencias al final no es 0, y aunque ya no se necesitan objetos, object1 y object2 no se liberarán.
Mira nuestros enlaces
Para probar todo esto en acción, agregue este código después de la clase Usuario en MainViewController.swift:
class Phone { let model: String var owner: User? init(model: String) { self.model = model print("Phone \(model) was initialized") } deinit { print("Deallocating phone named: \(model)") } }
Este código agrega una nueva clase de
teléfono con dos propiedades, una para el modelo y otra para el propietario, así como los métodos init y deinit. La propiedad del propietario es opcional, ya que el teléfono puede no tener un propietario.
Ahora agregue esta línea a runScenario ():
let iPhone = Phone(model: "iPhone Xs")
Esto creará una instancia de la clase Phone.
Sostener el móvil
Ahora agregue este código a la clase Usuario, inmediatamente después de la propiedad de nombre:
private(set) var phones: [Phone] = [] func add(phone: Phone) { phones.append(phone) phone.owner = self }
Agregue una variedad de teléfonos propiedad del usuario. El setter está marcado como privado, por lo que se debe agregar add (phone :).
Inicia la aplicación. Como puede ver, las instancias de las clases de objetos Teléfono y Usuario se liberan según sea necesario.
El usuario John fue inicializado
El teléfono iPhone XS se inicializó
Teléfono deslocalizador llamado: iPhone Xs
Usuario desasignante llamado: John
Ahora agregue esto al final de runScenario ():
user.add(phone: iPhone)
Aquí agregamos nuestro iPhone a la lista de teléfonos propiedad del
usuario , y también establecemos la propiedad del
propietario del teléfono como '
usuario '.
Ejecute la aplicación nuevamente. Verá que los objetos de usuario y iPhone no se liberan. El ciclo de fuertes vínculos entre ellos impide que el ARC los libere.

Enlaces débiles
Para romper el ciclo de enlaces fuertes, puede designar la relación entre objetos como débil.
Por defecto, todos los enlaces son fuertes y la asignación lleva a un aumento en el recuento de referencias. Cuando se usan referencias débiles, el recuento de referencias no aumenta.
En otras palabras, los
enlaces débiles no afectan la gestión de la vida de un objeto . Los enlaces débiles siempre se declaran
opcionales . De esa manera, cuando el recuento de enlaces se convierte en 0, el enlace se puede establecer en nulo.

En esta ilustración, las líneas discontinuas indican enlaces débiles. Tenga en cuenta que el recuento de referencia de object1 es 1, ya que variable1 se refiere a él. El recuento de referencia de object2 es 2, porque es referenciado por variable2 y object1.
object2 también hace referencia a object1, pero
WEAK , lo que significa que no afecta el recuento de referencias de object1.
Cuando variable1 y variable2 se liberan, object1 tiene un recuento de referencia de 0, que lo libera. Esto, a su vez, libera una fuerte referencia a object2, que ya conduce a su lanzamiento.
En la clase Teléfono, cambie la declaración de propiedad del propietario de la siguiente manera:
weak var owner: User?
Al declarar la referencia de propiedad del propietario como 'débil', rompemos el ciclo de enlaces fuertes entre las clases Usuario y Teléfono.

Inicia la aplicación. Ahora el usuario y el teléfono están liberados correctamente.
Enlaces no propios
También hay otro modificador de enlace que no aumenta el recuento de referencias:
sin propietario .
¿Cuál es la diferencia entre
no propietario y
débil ? Una referencia débil siempre es opcional y automáticamente se vuelve nula cuando se libera el objeto referenciado.
Es por eso que debemos declarar propiedades débiles como una variable de tipo opcional: esta propiedad debe cambiar.
Los enlaces no propios, en contraste, nunca son opcionales. Si intenta acceder a una propiedad no propietaria que se refiere a un objeto liberado, obtendrá un error que parece un desenvolvimiento forzado que contiene una variable nula (desenvolvimiento forzado).

Intentemos aplicar
sin propietario .
Agregue una nueva clase
CarrierSubscription al final de MainViewController.swift:
class CarrierSubscription { let name: String let countryCode: String let number: String let user: User init(name: String, countryCode: String, number: String, user: User) { self.name = name self.countryCode = countryCode self.number = number self.user = user print("CarrierSubscription \(name) is initialized") } deinit { print("Deallocating CarrierSubscription named: \(name)") } }
CarrierSubscription tiene cuatro propiedades:
Nombre: nombre del proveedor.
CountryCode: código de país.
Número: número de teléfono.
Usuario: enlace al usuario.
¿Quién es tu proveedor?
Ahora agregue esto a la clase Usuario después de la propiedad de nombre:
var subscriptions: [CarrierSubscription] = []
Aquí mantenemos una variedad de proveedores de usuarios.
Ahora agregue esto a la clase Phone, después de la propiedad del propietario:
var carrierSubscription: CarrierSubscription? func provision(carrierSubscription: CarrierSubscription) { self.carrierSubscription = carrierSubscription } func decommission() { carrierSubscription = nil }
Esto agrega la propiedad opcional CarrierSubscription y dos métodos para registrar y cancelar el registro del teléfono con el proveedor.
Ahora agregue la clase CarrierSubscription dentro del método init, justo antes de la declaración de impresión:
user.subscriptions.append(self)
Agregamos CarrierSubscription a la gama de proveedores de usuarios.
Finalmente, agregue esto al final del método runScenario ():
let subscription = CarrierSubscription( name: "TelBel", countryCode: "0032", number: "31415926", user: user) iPhone.provision(carrierSubscription: subscription)
Creamos una suscripción al proveedor para el usuario y le conectamos el teléfono.
Inicia la aplicación. En la consola verás:
El usuario John fue inicializado
El teléfono iPhone Xs se inicializó
CarrierSubscription TelBel se inicializa
Y de nuevo un ciclo de enlaces! usuario, iPhone y suscripción no se liberaron al final.
¿Puedes encontrar un problema?

Rompiendo la cadena
O bien el enlace del usuario a la suscripción o el enlace de la suscripción al usuario no deben ser propietarios para romper el ciclo. La pregunta es qué opción elegir. Veamos las estructuras.
Un usuario posee una suscripción a un proveedor, pero viceversa, no, una suscripción a un proveedor no posee un usuario.
Además, no tiene sentido la existencia de CarrierSubscription sin referencia al usuario que lo posee.
Por lo tanto, el enlace del usuario no debe ser propietario.
Cambie la declaración del usuario en CarrierSubscription:
unowned let user: User
Ahora el usuario no posee, lo que rompe el ciclo de enlaces y le permite liberar todos los objetos.

Enlaces de bucle en cierres
Los ciclos de enlace para los objetos ocurren cuando los objetos tienen propiedades que se refieren entre sí. Al igual que los objetos, los cierres son un tipo de referencia y pueden conducir a bucles de referencia. Los cierres capturan los objetos que usan.
Por ejemplo, si asigna un cierre a una propiedad de una clase, y este cierre utiliza propiedades de la misma clase, obtenemos un bucle de enlaces. En otras palabras, el objeto mantiene un enlace al cierre a través de la propiedad. El cierre contiene una referencia al objeto a través del valor capturado de uno mismo.

Agregue este código a CarrierSubscription inmediatamente después de la propiedad del usuario:
lazy var completePhoneNumber: () -> String = { self.countryCode + " " + self.number }
Este cierre calcula y devuelve el número de teléfono completo. La propiedad se declara
perezosa , se asignará al primer uso.
Esto es necesario porque usa self.countryCode y self.number, que no estará disponible hasta que se ejecute el código inicializador.
Agregue runScenario () al final:
print(subscription.completePhoneNumber())
Llamar a completePhoneNumber () ejecutará el cierre.
Inicie la aplicación y verá que el usuario y el iPhone se liberan, pero CarrierSubscription no lo es, debido a un ciclo de fuertes vínculos entre el objeto y el cierre.

Listas de captura
Swift proporciona una manera simple y elegante de romper el lazo de los enlaces fuertes en los cierres. Declara una lista de captura en la que define la relación entre el cierre y los objetos que captura.
Para demostrar la lista de captura, considere el siguiente código:
var x = 5 var y = 5 let someClosure = { [x] in print("\(x), \(y)") } x = 6 y = 6 someClosure()
x está en la lista de captura de cierre, por lo que el valor de x se copia a la definición de cierre. Se captura por valor.
y no está en la lista de captura, se captura por referencia. Esto significa que el valor de y será lo que era en el momento en que se llamó al circuito.
Las listas de bloqueo ayudan a identificar interacciones débiles o no propias con respecto a los objetos capturados dentro del bucle. En nuestro caso, la elección adecuada no es propiedad, ya que no puede existir un cierre si se libera la instancia de CarrierSubscription.
Aprovechate
Reemplace la definición completePhoneNumber con CarrierSubscription ::
lazy var completePhoneNumber: () -> String = { [unowned self] in return self.countryCode + " " + self.number }
Agregamos [self propio] a la lista de captura de cierre. Esto significa que nos capturamos a nosotros
mismos como un enlace no
propiedad en lugar de uno fuerte.
Inicie la aplicación y verá que CarrierSubscription ya está disponible.
De hecho, la sintaxis anterior es una forma corta de una más larga y más completa en la que aparece una nueva variable:
var closure = { [unowned newID = self] in
Aquí newID es una copia de uno mismo sin dueño. Más allá del cierre, el yo permanece como sí mismo. En la forma corta dada anteriormente,
creamos una nueva auto variable que oscurece el yo existente dentro del cierre.
Usar sin propiedad con cuidado
En su código, la relación entre self y completePhoneNumber se designa como no propietaria.
Si está seguro de que el objeto utilizado en el cierre no se liberará, puede usarlo sin propietario. Si lo hace, ¡estás en problemas!
Agregue este código al final de MainViewController.swift:
class WWDCGreeting { let who: String init(who: String) { self.who = who } lazy var greetingMaker: () -> String = { [unowned self] in return "Hello \(self.who)." } }
Ahora aquí está el final de runScenario ():
let greetingMaker: () -> String do { let mermaid = WWDCGreeting(who: "caffeinated mermaid") greetingMaker = mermaid.greetingMaker } print(greetingMaker())
Inicie la aplicación y verá un bloqueo y algo así en la consola:
El usuario John fue inicializado
El teléfono iPhone XS se inicializó
CarrierSubscription TelBel se inicializa
0032 31415926
Error fatal: intento de leer una referencia no propiedad pero el objeto 0x600000f0de30 ya fue desasignado2019-02-24 12: 29: 40.744248-0600 Ciclos [33489: 5926466] Error fatal: intento de leer una referencia no propiedad pero el objeto 0x600000f0de30 ya fue desasignado
Se produjo una excepción porque el cierre espera a que exista quien existe, pero se liberó tan pronto como la sirena salió del alcance al final del bloque do.
Este ejemplo puede parecer succionado por un dedo, pero tales cosas suceden. Por ejemplo, cuando usamos cierres para comenzar algo mucho más tarde, digamos, después de que la llamada asincrónica en la red ha finalizado.
Desactiva la trampa
Reemplace greetingMaker en la clase WWDCGreeting con esto:
lazy var greetingMaker: () -> String = { [weak self] in return "Hello \(self?.who)." }
Hicimos dos cosas: primero, reemplazamos sin dueño por débil. En segundo lugar, dado que el yo se ha debilitado, accedemos a la propiedad de quién a través del yo. Ignore la advertencia de Xcode, lo arreglaremos pronto.
La aplicación ya no se bloquea, pero si la ejecuta, obtenemos un resultado divertido: "Hola, nada".
Quizás el resultado sea bastante aceptable, pero a menudo necesitamos hacer algo si el objeto fue liberado. Esto se puede hacer usando la declaración de guardia.
Reemplace el texto de cierre con esto:
lazy var greetingMaker: () -> String = { [weak self] in guard let self = self else { return "No greeting available." } return "Hello \(self.who)." }
La declaración de guardia se asigna a uno mismo tomado del ser débil. Si self es nulo, el cierre devuelve "No hay saludo disponible". De lo contrario, self se convierte en una referencia fuerte, por lo que se garantiza que el objeto vivirá hasta el final del cierre.
Buscando bucles de enlace en Xcode 10
Ahora que comprende cómo funciona ARC, qué son los bucles de enlace y cómo romperlos, es hora de ver un ejemplo de una aplicación real.
Abra el proyecto Starter ubicado en la carpeta Contactos.
Inicia la aplicación.

Este es el administrador de contactos más simple. Intente hacer clic en un contacto, agregue un par de nuevos.
Asignación de archivo:
ContactsTableViewController: muestra todos los contactos.
DetailViewController: muestra la información detallada del contacto seleccionado.
NewContactViewController: le permite agregar un nuevo contacto.
ContactTableViewCell: celda de la tabla que muestra los detalles de contacto.
Contacto: modelo de contacto.
Número: modelo de número de teléfono.
Sin embargo, con este proyecto, todo está mal: hay un ciclo de enlaces. Al principio, los usuarios no notarán problemas debido al pequeño tamaño de la pérdida de memoria, por la misma razón es difícil encontrar la pérdida.
Afortunadamente, Xcode 10 tiene herramientas integradas para encontrar la pérdida de memoria más pequeña.
Inicie la aplicación nuevamente. Elimine 3-4 contactos usando el deslizamiento hacia la izquierda y el botón Eliminar. Parece que desaparecen por completo, ¿verdad?

Donde fluye
Cuando la aplicación se esté ejecutando, haga clic en el botón Debug Memory Graph:

Observe los problemas de tiempo de ejecución en el navegador de depuración. Están marcados con cuadrados morados con un signo de exclamación blanco dentro:

Seleccione uno de los objetos de contacto problemáticos en el navegador. El ciclo es claramente visible: los objetos Contacto y Número, que se refieren entre sí, se mantienen.

Parece que deberías mirar el código. Tenga en cuenta que un contacto puede existir sin un número, pero no al revés.
¿Cómo resolverías este bucle? ¿Enlace de contacto a número o de número a contacto? débil o sin dueño? Pruébalo tú mismo primero!
Si necesitabas ayuda ...Hay 2 posibles soluciones: hacer un enlace de Contacto a Número débil, o de Número a Contacto sin propiedad.
La documentación de Apple recomienda que el objeto padre tenga una fuerte referencia a "hijo", y no al revés. Esto significa que le damos a Contact una fuerte referencia a Number, y Number, un enlace no propietario a Contact:
class Number { unowned var contact: Contact
Bonificación: bucles con tipos de referencia y tipos de valor.
Swift tiene tipos de referencia (clases y cierres) y tipos de valores (estructuras, enumeraciones). El tipo de valor se copia cuando se pasa, y los tipos de referencia comparten el mismo valor utilizando el enlace.
Esto significa que en el caso de los tipos de valor, no puede haber ciclos. Para que se produzca un bucle, necesitamos al menos 2 tipos de referencia.
Volvamos al proyecto Cycles y agreguemos este código al final de MainViewController.swift:
struct Node {
No va a funcionar! La estructura es un tipo de valor y no puede tener recursividad en una instancia de sí misma. De lo contrario, dicha estructura tendría un tamaño infinito.
Cambia la estructura a una clase.
class Node { var payload = 0 var next: Node? }
La referencia a sí mismo es bastante aceptable para las clases (tipo de referencia), por lo que el compilador no tiene problemas.
Ahora agregue esto al final de MainViewController.swift:
class Person { var name: String var friends: [Person] = [] init(name: String) { self.name = name print("New person instance: \(name)") } deinit { print("Person instance \(name) is being deallocated") } }
Y esto está al final de runScenario ():
do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(bert)
Inicia la aplicación. Tenga en cuenta: ni ernie ni bert son liberados.
Enlace y significado
Este es un ejemplo de una combinación de un tipo de referencia y un tipo de valor que ha llevado a un bucle de enlaces.
ernie y bert permanecen inéditos, manteniéndose mutuamente en las matrices de sus amigos, aunque las matrices mismas son tipos de valor.
Intente hacer que los amigos archiven como no propiedad, y Xcode mostrará un error: no propiedad solo se aplica a las clases.
Para arreglar este bucle, tenemos que crear un objeto contenedor y usarlo para agregar instancias a la matriz.
Agregue la siguiente definición antes de la clase Persona:
class Unowned<T: AnyObject> { unowned var value: T init (_ value: T) { self.value = value } }
Luego cambie la definición de amigos en la clase Persona:
var friends: [Unowned<Person>] = []
Finalmente, reemplace el contenido del bloque do en runScenario ():
do { let ernie = Person(name: "Ernie") let bert = Person(name: "Bert") ernie.friends.append(Unowned(bert)) bert.friends.append(Unowned(ernie)) }
Inicie la aplicación, ahora ernie y bert se han lanzado correctamente.
La matriz de amigos ya no es una colección de objetos Persona. Esta es ahora una
colección de objetos sin propietario que sirven como envoltorios para instancias de Persona.
Para obtener objetos Person de Unowned, use la propiedad value:
let firstFriend = bert.friends.first?.value
Conclusión
Ahora tiene una buena comprensión de la administración de memoria en Swift y sabe cómo funciona ARC. Espero que la publicación te haya sido útil.
Apple: conteo automático de referencias