Table of Contents
Apple define una fuga de memoria como: “Memoria que estaba localizada en un punto pero, al no publicarla, tu aplicación ya no tiene referencia de ella. Por lo que, si no hay ninguna referencia de esta, no puedes volver a publicarla ni utilizarla”. Para entender al 100% fuga de memorias, primero tenemos que desarrollar la gestión de la memoria en el sistema operativo iOS.
Automatic reference counting (ARC)
ARC mantiene la cuenta de las veces que una referencia aparece.
Puede que sean weak o strong. En el caso de que se aparezca una referencia strong, el contador incrementa en 1 punto. Cuando se libera esa referencia, la cuenta decrece 1 punto. Por lo que si esa cuenta llega a 0, el sistema puede eliminarlo por completo de la memoria heap ya que no hay uso de ello. Las referencias weak no tienen valor en la cuenta de referencias por lo que se liberan la cuenta llega a 0.
Retain cycles
Como developer, este es el principal problema con el que nos enfrentamos cuando lidiamos con ARC. Aparece cuando dos objetos presentan fuertes referencias entre ellos. La cuenta de la referencia del primer objeto no puede ser 0 hasta que no haya release del segundo objeto y a la misma vez, la cuenta de la referencia del segundo objeto no puede ser 0 hasta que haya release del primer objeto.
Aquí tenéis un ejemplo de retención de ciclos:
class Dog {
var owner: Person?
}
class Person {
var dog: Dog?
}
let mark = Person()
let pancho = Dog()
mark.dog = pancho
pancho.owner = mark
Dog y Person tiene referencias fuertes entre ellas y nunca se eliminarán de la memoria.
Rompiendo los retain cycle
Como hemos explicado antes, las referencias pueden ser strong o weak. A diferencia que las referencias strong, las weak no aumentan la cuenta de referencia por lo que son opcionales. No garantizan que la referencia tenga valor en el runtime. Hay dos tipos de referencias weak:
Weak
Este tipo de referencia es weak y tenemos que fijarnos si la referencia de ese objeto sigue siendo válido cuando accedemos a él. Este tipo de referencia se suele utilizar cuando no estamos seguros si el tiempo de vida de la referencia es menor o mayor que el objeto haciéndole referencia.
Unowned
Esta referencia no es opcional por lo que sólo podemos utilizarla cuando estamos seguros de que el tiempo de vida de la referencia es mayor (o igual) que el objeto haciéndole referencia. Si intentamos acceder a una referencia unowned que ya no está en la memoria, tendremos una runtime exception
Refactorizando el ejemplo de arriba para break the retain cycle y evitar fuga de memorias sería así:
class Dog {
weak var owner: Person?
}
class Person {
var dog: Dog?
}
let mark = Person()
let pancho = Dog()
mark.dog = pancho
pancho.owner = mark
Ahora pancho tiene una referencia weak hacia el owner, mientras que mark sigue teniendo una referencia strong hacia Dog. Cuando se libere mark, ARC se liberará automaticamente a pancho.
Retain cycles en closures
Los Closures son una de las características más conocidas de Swift, sin embargo, es fácil crear un retain cycle si no se usa de manera correcta. Los Closures son un tipo de referencia, por lo que pueden provocar una memory leak si haces referencia a un objeto con una referencia a los closure. Échale un vistazo al ejemplo de abajo, el cuerpo de la closure hace referencia a la class.
class TestClass {
let constant = "Hello"
lazy var closure: (() -> ())? = {
print(self.constant)
}
}
Esto se puede resolver mediante un capture list. Definiendo el tipo de referencia que utilizamos dentro del cuerpo de la closure; si marcas la self reference como débil, retain cycle se rompe.
lazy var closure: (() -> ())? = { [weak self] in
print(self?.constant)
}
Detectando memory leaks
El caso de arriba es simple y posiblemente lo podamos detectar únicamente observando el código, pero en memory leaks de aplicaciones de verdad puede que no sea tan obvio.
Por lo que nos harán falta herramientas y técnicas. Apple presentó el Memory graph debugger en Xcode 9 que permitía detener la aplicación y conseguir una imagen del actual estado de la memoria. Podemos obtener una lista de los objetos en la memory heap si seleccionamos un objeto; seleccionandolo podemos ver todos los objetos que le hacen referencia a la vez que todo su backtrace.
Para detectar memory leaks ha de ejecutarse la aplicación y navegar a través de todos los flows posibles y abrir varias veces los mismos view controllers.Luego abrir el memory graph debugger y echar un vistazo a la memory heap. Busca objetos que no deberían estar en la memory heap, por ejemplo:
- Un ViewController que ya no está en la aplicación.
- Un número distinto de instancias de un mismo objeto.
El memory graph debugger muestra una exclamación púrpura entre objetos que puede que estén sufriendo una leak. Aún así, deberás comprobar si es una memory leak o un falso positivo.
Testing fuga de memorias
Es fácil y simple realizar test unitarios de fugas de memoria. Comprobando que el sistema bajo test (SUT) es nulo después de que la ejecución del test haya terminado. Para ello, asignamos el SUT a una referencia débil y nos fijamos si la referencia sigue siendo nulo en el método teardown.
class MemoryLeakTestTests: XCTestCase {
weak var weakSUT: Person?
func testPersonLeak() {
let mark = Person()
weakSUT = mark
let pancho = Dog()
mark.dog = pancho
pancho.owner = mark
}
override func tearDown() {
XCTAssertNil(weakSUT, “SUT is leaking”)
}
}
Utilizando el ejemplo de Person – Dog podemos ejecutar un test y ver si hay alguna memory leak. Si rompemos el retain cycle cambiando el tipo de referencia del owner del Dog a weak y ejecutamos el test otra vez, vemos que ahora sí pasa el test. WeakSUT es nulo por lo que esta referencia ha sido liberada.
Suscríbete a nuestro newsletter para estar al día de desarrollo de aplicaciónes moviles e fuga de memorias en iOS en concreto!
Si este artículo sobre fuga de memorias en iOS te gustó, te puede interesar:
La capa de datos con MoyaRx y Codable
Mejorar la testabilidad de CLLocationManager
Tendencias en aplicaciónes móviles
Debugging con Charles Proxy en Android emulator
Integración Continua en iOS usando Fastlane y Jenkins
Cornerjob – iOS objective-C app un caso de exito
Author
-
Graduate in Audiovisual Systems Engineering focused on iOS Development. Constantly learning new technologies and work methodologies, good time management and problem solving skills.
Ver todas las entradas