Weak references - RaduG/swift_learning GitHub Wiki

Overview

As Swift uses ARC to clear unreachable objects, reference cycles are problematic. To solve this, it is possible to declare weak references. A weak reference must always be of an optional type as ARC will set it to nil on cleanup. Im

In this example, nothing gets printed, as person1 has a strong reference of school1 and vice-versa:

class Person {
  let name: String
  var school: School?

  init(_ name: String) {
    self.name = name
  }

  deinit {
    print("Cleaning Person \(name)")
  }
}

class School {
  let name: String
  var pupils = [Person?]()

  init(_ name: String) {
    self.name = name
  }

  func addPupil(_ pupil: Person?) {
    pupils.append(pupil)
  }

  deinit {
    print("Cleaning School \(name)")
  }
}

var person1: Person? = Person("Radu")
var school1: School? = School("Some School")
person1!.school = school1!
school1!.addPupil(person1!)
person1 = nil
school1 = nil

However, if Person's reference to a school is weak, then the memory can be cleaned:

class Person {
  let name: String
  weak var school: School?

  init(_ name: String) {
    self.name = name
  }

  deinit {
    print("Cleaning Person \(name)")
  }
}

class School {
  let name: String
  var pupils = [Person?]()

  init(_ name: String) {
    self.name = name
  }

  func addPupil(_ pupil: Person?) {
    pupils.append(pupil)
  }

  deinit {
    print("Cleaning School \(name)_")
  }
}

var person1: Person? = Person("Radu")
var school1: School? = School("Some School")
person1!.school = school1!
school1!.addPupil(person1!)
person1 = nil
school1 = nil

Both deinitialisers are called.

Unowned references

Similarly to weak, unowned references do not keep a strong hold of the instance they refer to. However, an unowned relationship should only be used when the lifetime of the target object extends beyond the lifetime of the current one. Therefore, unowned variables are not optional types as ARC does not assign nil to them.

class Person {
  let name: String
  var card: Card?

  init(_ name: String) {
    self.name = name
  }

  deinit {
    print("Removing \(name)")
  }
}

class Card {
  let number: String
  let pin: String
  unowned let owner: Person

  init(number: String, pin: String, owner: Person) {
    self.number = number
    self.pin = pin
    self.owner = owner
  }

  deinit {
    print("Removing \(number)")
  }
}

var person1: Person? = Person("Radu")
var card: Card? = Card(number: "55", pin: "1234", owner: person1!)
person1!.card = card

person1 = nil
card = nil

In this case, a person may have a credit card, but a credit card will always belong to a person.

Closures

In the example below, the greeting closure holds a strong reference to self and the instance holds a strong reference to the closure:

class Person {
  let firstName: String
  let lastName: String
  let age: Int

  lazy var greeting = {
    "Hello, my name is \(self.firstName) \(self.lastName) and I am \(self.age) years old"
  }

  init(_ firstName: String, _ lastName: String, _ age: Int) {
    self.firstName = firstName
    self.lastName = lastName
    self.age = age
  }

  deinit {
    print("Cleaning \(firstName) \(lastName)")
  }
}

var radu: Person? = Person("Radu", "Ghitescu", 100)
print(radu!.greeting())
radu = nil

To fix this, we need to declare a capture list in the closure to hold a weak reference to self instead:

class Person {
  let firstName: String
  let lastName: String
  let age: Int

  lazy var greeting = {
    [unowned self] in
    "Hello, my name is \(self.firstName) \(self.lastName) and I am \(self.age) years old"
  }

  init(_ firstName: String, _ lastName: String, _ age: Int) {
    self.firstName = firstName
    self.lastName = lastName
    self.age = age
  }

  deinit {
    print("Cleaning \(firstName) \(lastName)")
  }
}

var radu: Person? = Person("Radu", "Ghitescu", 100)
print(radu!.greeting())
radu = nil