Skip to content

Instantly share code, notes, and snippets.

@jhanzo
Last active January 25, 2017 21:21
Show Gist options
  • Save jhanzo/9f2beb71b4263b5e7571352ae58dbb5d to your computer and use it in GitHub Desktop.
Save jhanzo/9f2beb71b4263b5e7571352ae58dbb5d to your computer and use it in GitHub Desktop.
Quick overview about how ARC works in Swift
//Jessy HANZO
//Swift 2.2
//
//Since iOS 6, ARC brings the power to have an easier use of memory management
// **ARC**
//It provides you an intelligent management of memory
//But developper has to be watchful about how ARC works ...
//... because (Winter) Retain Cycles are coming...
//... and it is very very bad 😱
//
//It could be responsible for a lot of unexcepted behaviours on your app
//High memory consumption, app crashes, low reactivity...
//But fortunately, XCode cames with an instrument for seeing leaks
//
//A good article for ARC with screenshots and examples :
//https://digitalleaves.com/blog/2015/05/demystifying-retain-cycles-in-arc/
import UIKit
//---------------------//
//------EXAMPLE 1------//
//---------------------//
//let suppose a person can only have only one child and vice versa
class Child {
let name: String
init(name: String) {
self.name = name
print("Child '\(name)' is being initialized")
}
deinit {
print("Child '\(name)' is being deinitialized")
}
}
class Person {
let name: String
init(name: String) {
self.name = name
print("Person '\(name)' is being initialized")
}
deinit {
print("Person '\(name)' is being deinitialized")
}
}
//For every instances of a class, ARC allocates memory
//In some cases, ARC frees up the memory used by this instance
//But ARC will not deallocate an instance if a reference
//(properties, constants, variables) still exists
//---------------------//
//------EXAMPLE 2------//
//---------------------//
//1 - The following code creates multiple references to one new Person
//2 - These variables are optionals and are init with nil value and not a reference to class
var dad_me: Person?
var dad_you: Person?
var dad_him: Person?
//a strong reference is created between dad_me and new Person
//until this reference existing, ARC will not deallocated it
dad_me = Person(name: "Daddy")
dad_you = dad_me
dad_him = dad_me
//"wha... what !? we have the same dad ?!"
//YES ! and 2 more strong references !!
dad_me = nil//NO printing "Person 'Daddy' is being deinitialized" :(
dad_him = nil//still NO printing
dad_you = nil//GG ! printing "Person Daddy' is being deinitialized" !!
//"Are you kidding me ?? ARC works very well 💪 !! ❤️ ARC rocks ❤️"
//Almost right : ARC can track easily how many references there are and when to deallocate instance
//BUT it's possible to get ARC down if two instances hold strong references to each other
//and ... this ... is ... incredibly ... wonderfully ... named ... ⭐️ Retain Cycles ⭐️ !!
//---------------------//
//------EXAMPLE 3------//
//---------------------//
//And a good example for pointing out :
class Baby {
let name: String
var mother: Woman//a baby always have a mother
init(name: String, mother: Woman) {
self.name = name
self.mother = mother
}
deinit { print("Baby '\(name)' is being deinitialized") }
}
class Woman {
let name: String
var baby: Baby?//a woman does not always have a baby
init(name: String) { self.name = name }
deinit { print("Woman '\(name)' is being deinitialized") }
}
var mummy: Woman? = Woman(name: "Mummy")//1 strong ref to Woman
var baby: Baby? = Baby(name: "", mother: mummy!)//1 strong ref to Baby
mummy!.baby = baby// 2 others strong ref = 4 strong refs ...
//"Wooooow what are u doing man !? R u serious, there is already a ref to mummy line 105 !! 😱"
//Don't be so surprised, this case often happens in real life !!
//and this is a RETAIN CYCLE, a little bit uggly presented like that, isn't it ?!
//Sometimes it is more hidden... or not ...
//"Please do something !! Pleeeeeease !!"
mummy = nil
baby = nil
//... useless !!! 2 strong ref left : ones between Woman and Baby
//but mummy and baby variables cannot be used anymore...
//and two instances still exist
//Note that this case would be even happen if there would be only one strong ref between Woman and Baby
//" :'( My app is dead... swift >> 💩 >> 🚽"
//Be nice and patient, wait and see
//----------------------//
//-------SOLUTION-------//
//----------------------//
//⭐️ Weak ⭐️ references
//These refs are not holdly strong ref on the instance it refers to
//example :
class Man {
let name: String
weak var wife: Woman?//a man has not always a wife
init(name: String, wife: Woman) {
self.name = name
self.wife = wife
}
}
//note 'weak' implies using an optional value (no explanation, it is logical 😉)
//in other words, weak keyword can only be linked to var for indicating it could be changed at runtime
//FYI, on deallocation, weak references will be automatically set to nil by ARC
//if reference always has a value please use ⭐️ unowned ⭐️ references instead
//in other words unowned cannot be optional, NEVER !
class GrandMother {
let name: String
unowned var child: Child//a grand mother (or father) always has a child (and more)
init(name: String, child: Child) {
self.name = name
self.child = child
}
}
//Now if you set an instance class to nil, weak ref will be automatically released for you
//Awesome, right ?
//"But you tell me ARC frees up memory by setting to nil my variable ?"
// "So, how an unowned reference can be free up since it cannot be nil?"
//Very good question, indeed ARC does not set nil but removes the reference to instance
//And so if you try to use this variable after deallocation, app will always crash
//NB 1 : you can imagine a class initializer which initiating an another class instance :
class Orphan {
let name: String
var father: FosterFather!//NB2 : try to remove '!', what's happen ? ...
//Answer : father has a default value of nil but can be accessed without unwrapping its value
//So you can initiate this class without instantiate father, you get it ?
init(name: String, nameFather: String) {
self.name = name
self.father = FosterFather(name: nameFather, child: self)
}
}
class FosterFather {
let name: String
unowned let child: Orphan
init(name: String, child: Orphan) {
self.name = name
self.child = child
}
}
//So please never forget Retain Cycles when you are using variable between different view controllers ;)
//----------------------//
//-------CLOSURES-------//
//----------------------//
//I don't tell you the whole truth about retain cycles...
//Indeed, you have to care about retain cycles in closures statement
//As you may already know it's required to prefix a variable with self in closure
//in order to indicate to which instance to point
//but using self in a closure creates a strong ref because closures (as classes) are reference types...
//even if there are multiple use of self, only one strong ref is captured
//So if you assign a variable with a closure you create another strong ref
//And these two strong ref are keeping themselves alive
class Dog {
let master: Person?//unfortunately some dogs does not have master :(
let name: String?//and if dog has no master dog may not have a name
//now a closure is defined for giving more readable information about dog
lazy var description: () -> String = {
if let name = self.name {
return "Dog's name is \(name)"
} else {
return "Dog does not have a name yet"
}
}
init(name: String, master: Person? = nil) {
self.name = name
self.master = master
}
}
//NB 1: lazy is used for telling variable is only needed if and when element has to be rendered as String
//so in this case you can access to self because self is supposed to be already initialized
//Anyway, now imagine you want to update 'description' property behaviour :
//NB 2: brutus and medor are set optionals for setting them to nil to demonstrate retain cycles
var brutus: Dog? = Dog(name: "Brutus", master: dad_me)
brutus!.description = {
return "My name is Brutus, take that !!"
}
print(brutus!.description())
//but try to do create a new instance
var medor: Dog? = Dog(name: "Medor")
print(medor!.description())
//You see what : first declaration of description is used, no overwritten by brutus description
//BUT there is retain cycle between Dog instance and the closure used for description
medor = nil//nope not sufficient !! deinit is not called :(
//solution for this is ⭐️ Capture List ⭐️
class Cat {
let master: Person?//unfortunately some dogs does not have master :(
let name: String?//and if dog has no master dog may not have a name
//now a closure is defined for giving more readable information about dog
lazy var description: (String) -> String = { [unowned self](surname: String) -> String in
if let name = self.name {
return "\(name) cat has a pretty surname \(surname), right ?"
} else {
return "Cat does not have a name nor surname :("
}
}
init(name: String, master: Person? = nil) {
self.name = name
self.master = master
}
}
//no retain cycle anymore, hip hip hip hourray !!
//and you can set to nil as you want, deinit will be called and ARC will do its job perfectly
//----------------------//
//--------SUM UP--------//
//----------------------//
//Keep in mind :
//1 - how ARC works
//2 - Use weak references for avoiding retain cycles (even in closures)
//3 - Use unowned references when value of property cannot be nil
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment