Connect.Tech- Swift Memory Management

Post on 16-Apr-2017

101 views 0 download

transcript

Swift Memory ManagementBy: Marcus Smith Software Engineer stable|kernelmarcus.smith@stablekernel.com

@stablekernel

Why should you care?

@stablekernel

Who am I?

• Marcus Smith • iOS Developer at stable|kernel • Struggled learning memory management • Previously worked for BMW and Frozen Fire Studios • Have a game in the app store

stable|kernel is an Atlanta-based mobile development company to craft smartly-designed mobile applications that connect brands directly with their users. Our team of engineers and designers takes clients from strategy through design, development and deployment, ensuring timely delivery of the highest quality applications.

Memory Management

@stablekernel

• Value & Reference Types • Reference Counting & ARC • Avoiding memory issues

Value & Reference Types

@stablekernel

• Value types contain data • Reference types contain the location in memory where data is

Value Types

@stablekernel

• Structs, Enums and Tuples • Copied on assignment or when passing to a function

struct Point { var x, y: Int }

let aPoint = Point(x: 0, y: 0) var anotherPoint = aPoint anotherPoint.x = 5 print(aPoint.x) // prints 0

Reference Types

@stablekernel

• Classes and Closures • Creates another reference when assigned to a variable or passed to a function • Implicit sharing

@stablekernel

class Person { var name: String var age: Int init(name: String, age: Int) {…} }

let bob = Person(name: “Bob”, age: 30) let myNeighbor = bob myNeighbor.age = 50 print(bob.age) // prints 50

func increaseAge(person: Person) { person.age += 1 }

increaseAge(person: bob) print(myNeighbor.age) // prints 51

Reference Types

@stablekernel

• More power, more responsibility • Can have multiple owners • Inheritance • Require some memory management on the part of the programmer

• blog.stablekernel.com

Reference Counting

@stablekernel

• Type of garbage collection • Objects keep a count of references • When an object’s reference count gets down to 0, it is destroyed and its

memory is freed • Used to be done manually with alloc, retain, copy, release

@stablekernel

https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/MemoryMgmt.html

Automatic Reference Counting (ARC)

@stablekernel

• Compiler makes retain and release calls • Has a problem with circular references creating retain cycles (memory leaks)

@stablekernel

class Person { var name: String var age: Int init(name: String, age: Int) {…} var apartment: Apartment? }

class Apartment { let unit: String init(unit: String) {…} var tenant: Person? }

class SomeViewController: UIViewController { var person: Person!

override func viewDidLoad() { super.viewDidLoad()

person = Person(name: “Bob”, age: 30) let unit4A = Apartment(unit: “4A”) person.apartment = unit4A unit4A.tenant = person } }

@stablekernel

UINavigationController SomeViewController

Person

Apartment

Push1

1

1

2

0Pop

ARC - Strong and Weak References

@stablekernel

• ARC deals with circular references by having strong and weak references • Strong references increase an object’s reference count • Weak references do not increase an object’s reference count • In Swift, references are strong by default

class Apartment { let unit: String init(unit: String) {…} var tenant: Person? }

ARC - Strong and Weak References

@stablekernel

• ARC deals with circular references by having strong and weak references • Strong references increase an object’s reference count • Weak references do not increase an object’s reference count • In Swift, references are strong by default

class Apartment { let unit: String init(unit: String) {…} weak var tenant: Person? }

@stablekernel

UINavigationController SomeViewController

Person

Apartment

Push1

1

1

0Pop

0

0

ARC - Weak References

@stablekernel

• Weak references help prevent retain cycles • Can go nil at any time, which was problematic in Objective-C • In Swift, weak references must be optional vars

weak var tenant: Person

weak let tenant: Person? ‘weak’ must be a mutable variable, because it may change at runtime

‘weak’ variable should have optional type ‘Person?’

ARC - Unowned References

@stablekernel

• Like weak references, but non-optional • Should only be used if you can guarantee the object won’t become nil • Objective-C name “Unsafe Unretained” • In Objective-C dangling pointers could cause crashes or data corruption

“Swift guarantees your app will crash if you try to access an unowned reference after the instance it references is deallocated. You will never encounter unexpected behavior in this situation. Your app will always crash reliably, although you should, of course, prevent it from doing so.”

unowned let tenant: Person

Avoiding Retain Cycles

@stablekernel

• Delegates • Parent - Child relationships • IBOutlets • CoreData • Closures • Lazy properties

Delegates

@stablekernel

• Delegates should be weak open class UITableView { weak open var dataSource: UITableViewDataSource? weak open var delegate: UITableViewDelegate? ... }

protocol SomeDelegate {}

class SomeObject { weak var delegate: SomeDelegate? } ‘weak’ may only be applied to class and class-bound

protocol types, not ‘SomeDelegate’

Delegates

@stablekernel

• Delegates should be weak open class UITableView { weak open var dataSource: UITableViewDataSource? weak open var delegate: UITableViewDelegate? ... }

protocol SomeDelegate: class {}

class SomeObject { weak var delegate: SomeDelegate? } ‘weak’ may only be applied to class and class-bound

protocol types, not ‘SomeDelegate’

Parent - Child Relationships

@stablekernel

• Parents typically should hold a strong reference to their children • Children should hold a weak reference to their parent

extension UIView { open var superview: UIView? { get } open var subviews: [UIView] { get } ... }

open class UIViewController { weak open var parent: UIViewController? { get } open var childViewControllers: [UIViewController] { get } ... }

IBOutlets

@stablekernel

• Weak by default (except root view) • Retained by superviews • Need to be declared as strong if you will be removing and re-adding • Same is true for NSLayoutConstraint

@stablekernel

class ViewController: UIViewController { @IBOutlet weak var someView: UIView! /// A subview of `someView` @IBOutlet weak var someSubView: UIView! override func viewDidLoad() { super.viewDidLoad() someView.removeFromSuperview() }

override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) someSubView.backgroundColor = .red }}

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

fatal error: unexpectedly found nil while unwrapping an Optional value

@stablekernel

class ViewController: UIViewController { @IBOutlet var someView: UIView! /// A subview of `someView` @IBOutlet weak var someSubView: UIView! override func viewDidLoad() { super.viewDidLoad() someView.removeFromSuperview() }

override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) someSubView.backgroundColor = .red }}

Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)

fatal error: unexpectedly found nil while unwrapping an Optional value

Core Data

@stablekernel

• CoreData relationships on NSManagedObjects create retain cycles • To break these retain cycles call refresh on NSManagedObjectContext:

context.refresh(someManagedObject, mergeChanges: false)

Closures

@stablekernel

• Blocks of code that can be passed around

let sayHello: (String) -> Void = { name in print("Hello \(name)") } sayHello("World") // prints "Hello World”

let add: (Int, Int) -> Int = { x, y in return x + y } let total = add(3, 5) // returns 8

let numbers = [1, 2, 3, 4, 5] let odds = numbers.filter { number in return number % 2 == 1 } print(odds) // prints [1, 3, 5]

Closures

@stablekernel

• Reference type • Have to capture and store references to the variables they need to run

let increaseBobsAgeBy: (Int) -> Void = { (value) in bob.age += value }

class Executable { let bob: Person init(bob: Person) {…} func execute(value: Int) { bob.age += value } }

increaseBobsAgeBy(2)

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomething() {…} func makeNetworkRequest() {…} func update() { completion = { doSomething() } makeNetworkRequest() } }

Call to method ‘doSomething’ in closure requires explicit ‘self.’ to make capture semantics explicit

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomething() {…} func makeNetworkRequest() {…} func update() { completion = { self.doSomething() } makeNetworkRequest() } }

Call to method ‘doSomething’ in closure requires explicit ‘self.’ to make capture semantics explicit

• Closures require an explicit self • Can cause retain cycles if an object owns a closure that captures itself

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomething() {…} func update() { completion = { self.doSomething() } } } class Executable { let someClass: SomeClass init(someClass: SomeClass) {…} func execute() { someClass.doSomething() } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomething() {…} func update() { completion = { [weak self] in self.doSomething() } } } class Executable { let someClass: SomeClass init(someClass: SomeClass) {…} func execute() { someClass.doSomething() } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomething() {…} func update() { completion = { [weak self] in self.doSomething() } } } class Executable { weak var someClass: SomeClass? init(someClass: SomeClass) {…} func execute() { someClass.doSomething() } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomething() {…} func update() { completion = { [weak self] in self?.doSomething() } } } class Executable { weak var someClass: SomeClass? init(someClass: SomeClass) {…} func execute() { someClass?.doSomething() } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomethingWithNonOptional(_ someClass: SomeClass) {…} func update() { completion = { [weak self] in } } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomethingWithNonOptional(_ someClass: SomeClass) {…} func update() { completion = { [unowned self] in doSomethingWithNonOptional(self) } } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomethingWithNonOptional(_ someClass: SomeClass) {…} func update() { completion = { [weak self] in if let strongSelf = self { doSomethingWithNonOptional(strongSelf) } } } }

Closures - Capturing Self

@stablekernel

class SomeClass { var completion: () -> Void func doSomethingWithNonOptional(_ someClass: SomeClass) {…} func update() { completion = { [weak self] in guard let strongSelf = self else { return } doSomethingWithNonOptional(strongSelf) } } }

Closures - Capturing Self

@stablekernel

• Self does not always need to be captured weakly • Depends on what objects may own the closure with self

DispatchQueue.main.async { self.doSomething() // Not a retain cycle }

UIView.animate(withDuration: 1) { self.doSomething() // Not a retain cycle }

DispatchQueue.main.asyncAfter(deadline: .now() + 30) { self.doSomething() // Not a retain cycle, but... }

Lazy Properties

@stablekernel

• Lazy closure properties need to capture self weakly to prevent retain cycles

class Person { lazy var greeting: () -> String = { [unowned self] in return “Hi, I’m \(self.name)” } }

class Person { lazy var fullName: String = { return “\(self.firstName) \(self.lastName)” }() }

• Other lazy properties do not

Avoiding Retain Cycles

@stablekernel

• Delegates - delegate should be weak • Parent - Child relationships - parent property should be weak • IBOutlets - weak or strong is fine, but strong if removing and re-adding • CoreData - refresh changes to turn back into faults • Closures - be careful what you capture and how • Lazy properties - only need to capture self weakly for closure properties

Questions?

Business Inquiries: Sarah Woodward Director of Business Development sarah.woodward@stablekernel.com

Marcus Smith Software Engineer marcus.smith@stablekernel.com blog.stablekernel.com