Updated with more examples on Mar 6th, 2021.

One of the most often asked questions in the Combine and RxSwift slack channels is something along the lines of:

Should I use self, weak, or unowned with my reactive code?

Given that most operators take closures, it’s a fair question. In this post I’ll go over common scenarios for using weak, unowned or simply self and include links with more information at the bottom.

Important: Combine code is still plain Swift - memory management works exactly as in any other of your Swift apps. This brief post only aims to give you few examples to refer to if you are unsure of the details.

Use [weak] when an object might get released

Tip: Use weak only if you’re capturing a class instance.

Use weak when you use a class instance in an escaping closure in one of your operators. When you call a method on your view controller, change a property on a view, or access another class instance, you likely need to capture it weakly.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class MyViewController {
  func bind(_ publisher: AnyPublisher<Int, Never>)
    -> AnyCancellable? {
    
    return publisher
      .sink(receiveValue: { [weak self] in
        self?.displayValue($0)
      })
  }
}

The sink closure above is called asynchronously. It might happen that when the closure is (finally) executed MyViewController is already dismissed by the user, and released from memory.

In such case self is nil - you need to safely access it as self? or alternatively unwrap it with a guard:

1
2
3
4
.sink(receiveValue: { [weak self] in
  guard let self = self else { return }
  self.displayValue($0)
})

Tip: Capturing objects weakly prevents memory retain cycles.

Use [unowned] for objects guaranteed to exist

Tip: Use unowned only if you’re capturing a class instance.

Use unowned in an escaping closure to access an object that is guaranteed to exist and avoid memory retain cycles by not retaining that object from the closure.

Such objects are your app delegate (which usually lives as long as your app is running), the app’s root view controller, a shared cache object, or any other object you know for certain will be present in memory for as long as your closure might get called.

1
2
3
4
5
6
7
8
9
class AppController {
  func events() -> AnyCancellable {

    return eventPublisher
      .sink(receiveValue: { [unowned self] e in
        print("Event \(e) from \(self)")
      })
  }
}

Warning: If self is released from memory and your subscription is invoked - accessing self will crash your app.

Use self to explicitly capture an object or a value

If you’re capturing a struct or another value type like an Int or a String you don’t need to be concerned about memory management. Reference self or another value and Swift will figure the memory management automatically:

1
2
3
4
5
6
7
8
9
struct ViewModel {
  func bind() -> AnyPublisher<Int, Never> {
    return myPublisher
      .map {
        return self.convertValue($0)
      }
      .eraseToAnyPublisher()
  }
}

Implicitly capture strongly an object by not specifying weak or unowned. This will ensure the object is retained in memory for as long as your closure might get called.

Here’s an example how to weakly capture self but also strongly another object which you need and no-one else might be retaining:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class ViewController {
    var subscription: AnyCancellable?
    
    func bind(viewModel: MyViewModel) {
        subscription = [1].publisher
            .map { [viewModel] in
                return viewModel.convert($0)
            }
            .sink { [weak self] value in
                self?.displayValue(value)
            }
    }
}

assign(to:on:) captures strongly

Use assign(to:on:) to capture the subscriber of a subcription, as long as you’re not creating a cyclic reference.

The subscription below captures self because you use it with assign(to:on:) and MyViewController leaks (aka is never released from memory).

1
2
3
4
5
6
7
class MyViewController: ViewController {
    
  func bind(viewModel: MyViewModel) {
    viewModel.eventPublisher
      .assign(to: \.currentEvent, on: self)
  }
}

Instead, use assign(to:on:) safely when you don’t capture self; for example to bind updates from the network to a data model:

1
2
3
4
5
6
7
class APIController {
    
  func bind(viewModel: MyViewModel) {
    myAPIEndpoint.updatesPublisher
      .assign(to: \.latestUpdates, on: viewModel)
  }
}

What happens if I capture self strongly?

Try using self directly like so inside a sink(...):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Model {
  func printDate(_ date: Date) {
    print(date)
  }
    
  var sub: AnyCancellable?
    
  init() {
    sub = Timer
      .publish(every: 1, on: RunLoop.main, in: .default)
      .autoconnect()
      .sink {
        self.printDate($0)
      }
  }
}

You retain the escaping closure used in sink via retaining the subscription in the class’ sub property. At the same time you retain self back from within the same escaping closure. Each of the two retains the other one in a cyclic reference.

See the effect of this cyclic retain by running this code:

1
2
var model: Model? = Model()
model = nil

Creating a new Model initializes the timer subscription and the app starts printing dates every second. Unfortunately setting model to nil doesn’t release it from memory as the closure used in sink keeps it alive.

Note: After you do model = nil there is no way to access the model that is just hanging around in memory anymore. That’s why we call this a “memory leak”.

Now let’s try not capturing self by using [weak]- all of the code stays the same except the code inside sink:

1
2
3
4
5
6
7
class Model {
  ...
      .sink { [weak self] in
        self?.printDate($0)
      }
  ...
}

Now you retain the escaping closure via retaining the subscription from Model, but you don’t retain the model back from the closure. As soon as you set the local variable model to nil that releases the subscription and the closure too.

1
2
var model: Model? = Model()
model = nil

This time around the code doesn’t print anything anymore as the model and the timer subscription are released immediately after they are created.

Where to go from here?

If you haven’t read it, the Swift book’s “Automatic Reference Counting” chapter is a great read and covers in detail the concepts we touched upon in here and more.

Hit me up with your ideas on Twitter at https://twitter.com/icanzilb.


To learn about all Combine check Combine: Asynchronous programming with Swift - this is where you can see all updates, discuss in the website forums, and more.