Last active
January 25, 2017 21:21
-
-
Save jhanzo/9f2beb71b4263b5e7571352ae58dbb5d to your computer and use it in GitHub Desktop.
Quick overview about how ARC works in Swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
//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