A practical guide to Weak Referencing in Swift

Serge Mata M
14 min readOct 9, 2019

--

A weak reference is a reference that does not keep a stronghold on the instance it refers to, and so does not stop ARC from disposing of the referenced instance.

This behavior prevents the reference from becoming part of a strong reference cycle. Practically, you will indicate that a property or a variable is to be stored with a weak reference by marking it with the weak keyword before its declaration; unowned is another keyword that can also be used to indicate the weak nature of a reference to a property or a variable. The difference between unowned and weak will be explained in this article.

1. Weak referencing in asynchronous callbacks

Swift allows for a block of code to be passed to a function for future execution. The time it takes for the execution to start would usually depend on some conditions and results of the aforementioned function. The execution of the passed block of code is what we refer to as a callback. The block executed during the callback can beescaping or not, it can also be anautoclosure.

Assume, you triggered a long-lasting task, and you are expecting a callback on completion of that task. The latter might be a service call, a hardware task, a call to an SDK, etc.

As mentioned earlier, you cannot foretell the instant when the callback will take places. Therefore you cannot also guarantee the state of your application or the originating view at that instance. So the application can be in the background due to a call that came through or to an erroneous screen lock, the screen might be dismissed due to OOB authentication screen that popped-in, the view controller might even be deallocated because you waited too long as you ended navigating to another screen.

In short, the possibilities are multiple and unpredictable. It arises then the need to ensure that irrespective of the state of the application of the screen, the callback does not lead to a fatal crash. We need to weak-reference all the properties or variables for which we cannot guarantee the existence at the instant of the callback.

1. Given a MediumArticleLoader protocol which represents the requirements of aHTTP client that is meant to load medium articles. The request to load articles can complete with a list of fetched articles or an error. Assume that the said request is triggered by a tap on a UIButton and that the fetched articles are to be displayed on a tableview.

struct Article { // model for medium article   let authorFullname: String   let publicationDate: Date   var title: String   var content: String   var rating: Int}enum ServiceError: Error { //possible errors that might occur when fetching articles   case describedBy(String)   case `default`}protocol MediumArticleLoader { //  requirements of a HTTP client    func loadMediumArticles(completion:@escaping (([Article]?, ServiceError?) -> Swift.Void))}class MediumArticleTableViewController: UITableViewController {    var mediumArticleLoader: MediumArticleLoader? // to be set 
func displayError(_ error: Error?) { // Implementation goes here } func updateDisplayedArticles(_ articles: [Article]) { // Implementation goes here } @IBAction func didTapLoadArticleButton(_ sender: UIButton) { mediumArticleLoader?.loadMediumArticles { [weak self]
(fetchedArticles, error) in
if error==nil { self?.displayError(error) } else if let loadedArticles = fetchedArticles { self?.updateDisplayedArticles(loadedArticles) } else { self?.displayError(ServiceError.default) } } }}

Notes and recommendations about the callback block:

  • Minimize the number of calls to an optional self inside the callback. The important to keep in mind is that self is optional and there is no benefit to having a lot of call to self? that might be noop if self is nil. My suggestion is that you extract the code that is part of your callback into an instance method or nested ruction and that you pass the adequate parameters to that method.
  • Avoid calling functions or methods with a parameter list that includes instance properties. For example, in the code below, the line self?.navigateToMediumHomePage(username: (self?.usernameTextField.text)!, token: session.token) should be avoided for many reasons including the following:

** (self?.usernameTextField.text)! is almost a logical contraction in all strictness. I have seen similar lines of code so many times, and I repeat to the writer that there is a fundamental flow in lines contains both a ? and a !. Indeed, at one point we hypothesize that the value might be nil with the use of ? and at the other point we assert with the use of ! , that we should always have a value.

** All the variables used in self?.navigateToMediumHomePage(username: (self?.usernameTextField.text)!, token: session.token)are accessible within the self realm which alleviates the need to pass them through. I recommend extracting the call into an instance method that can be defined as func navigateToMediumHomePageForUserWithToken(_ token: String). The created function must have the particularity of not taking any of the instance properties as one of its parameters.

struct Session {  let token: String}protocol MediumAuthenticator {   func authenticate(username: String?, password: String?, completion:@escaping ((Session?, ServiceError?)  -> Swift.Void))}class MediumLoginViewController: UIViewController {   var mediumAuthenticator: MediumAuthenticator? // to be set   @IBOutlet weak var usernameTextField: UITextField!   @IBOutlet weak var passwordTextField: UITextField!   func displayError(_ error: Error?) {     // Your implementation   } func navigateToMediumHomePage(username: String, token: String) {     // Your implementation } func navigateToMediumHomePage(userSessionToken token: String) {    // Your implementation
// Compute user name here from usernameTextField
} @IBAction func didTapLoginButton(_ sender: UIButton) { mediumAuthenticator?.authenticate(username: usernameTextField.text, password: passwordTextField.text) {
[weak self] (session, error) in
if let error = error { self?.displayError(error) } else if let session = session { self?.navigateToMediumHomePage(username: (self?.usernameTextField.text)!, token: session.token) // BAD // self?.navigateToMediumHomePage(userSessionToken: session.token) // GOOD (use this call instead of the previous) } else { self?.displayError(ServiceError.default) } } }}

2. Weak referencing in asynchronous dispatches

Swift defined a number of dispatch queues which all offer asynchronous and synchronous execution of blocks of codes. When using them to perform tasks asynchronously we need to pay particular attention to weak referencing.

DispatchQueue.global(qos: .userInteractive).async { }DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 5) {}DispatchQueue.main.asyncAfter(deadline: .now() + 5) {}

When a block code is called for asynchronous execution in a dispatch queue, it is the OS that decides when to perform your block of code based on various parameters: the requested queue, the quality of service if applicable, the number of operations in the queue, etc. We recall that we mentioned earlier the issues that originate from not controlling the time of the execution of your block code. The issues will even be more pronounced if you wish to delay the execution of the block by the use ofasyncAfter. All the preceding imposes ipso facto that you must access variables weakly where needed.

extension  NSNotification.Name  {    struct CustomNotificationName {      static let kAccountRequiresRefreshNotificationName = 
"accountRequiresRefresh"
} static let accountRequiresRefresh =
NSNotification.Name(CustomNotificationName.kAccountRequiresRefreshNotificationName)
}class MyClass: NSObject { override init() { super.init() NotificationCenter.default.addObserver(forName: .accountRequiresRefresh, object: nil, queue: .current, using: { [weak self] _ in // prevent strong capture of self, which might prevent deallocation // perform you update or call self here, self is optional --> use self?. }) } func downloadArticleImage() { DispatchQueue.global(qos: .userInteractive).asyncAfter(deadline: .now() + 5) { [weak self] in// call to the download API } } deinit { NotificationCenter.default.removeObserver(self) }
}

3. The forbidden “unowned”

Unowned and weak keywords both offer the same means of storing properties/variables with a weak reference i.e without keeping a stronghold to them. For the sake of differentiating, we will refer tounowned keyword as unowned reference and to weak keyword a weak reference. Let’s highlight one of the key differences between unowned and weak references.

An object stored with an unowned reference always expects to have a nonnull value, whereas both null and nonnull values, are permissible into a variable property referenced with a weak reference.

  • A corollary of the above is that since a weak-referenced variable is inherently capable of holding null as a value, it shall always, therefore, be declared as an optional property.

Some practical cases for the use of unowned

i. Completion blocks where the responder shall always exist. In the snippet below storeProductDelegate for whom the app store page was requested the shall always exist if the callback is to be triggered and because there are no states to persist when attempting to load the app store page, there is no need to store the storeProductDelegate. Though we cannot guarantee the existence of self at the instant of the call-back, we must guarantee however guarantee that the storeProductDelegate will always exist if the current self exist withouth however having a stronghold to it.


private func launchStoreProductPage(id productId: UInt64,
storeProductDelegate: SKStoreProductViewControllerDelegate) {
class StoreProductInteractor: NSObject { private func launchStoreProductPage(id productId: UInt64, storeProductDelegate: SKStoreProductViewControllerDelegate) { let product = [SKStoreProductParameterITunesItemIdentifier: NSNumber(value: productId)] let storeController = SKStoreProductViewController() storeController.delegate = storeProductDelegate storeController.loadProduct(withParameters: product) {[unowned storeProductDelegate, weak self](success, error) in if success { // self is optional --> use self?. // storeProductDelegate is not optional --> use storeProductDelegate } else { // self is optional --> use self?. // storeProductDelegate is not optional --> use storeProductDelegate } }}}

ii. Storing of a hardware resource that must always exist on a non-faulty device and that should not be strongly referenced by multiple objects. There are cases where an instance of hardware is shared by objects which on they turn have circular dependencies between each other. It is crucial to limit the strong reference to the shared hardware instance to prevent retain cycles. Let’s consider an instance of aCameraPermissionManager which should not be owned or by an instance of a PhotoLoaderViewController for aforementioned reasons.

class CameraPermissionManager { }class PhotoInteractor {      unowned var cameraPermissionManager: CameraPermissionManager // we     cannot own[have a strong reference] cameraPermissionManager, but cameraPermissionManager must always exist    init(cameraPermissionManager:  CameraPermissionManager) {        self.cameraPermissionManager = cameraPermissionManager    }}

iii. Asynchronous UI in updates. When a UI update is being performed in a dispatch block or in a callback, access to properties or variables must be done with caution. However, it can be guaranteed that the variable will exist, unowned is the best option.

class MyClassWithAnimation: NSObject {   func performTypeAAnimation() {    UIView.animate(withDuration: 0.5, delay: 0.5, options: .curveEaseOut, animations: { [unowned self] in    // self should never be nil, unless something terrible had happened         })     }
func performTypeBAnimation() { DispatchQueue.main.asyncAfter(deadline: .now() + 0.25) {// self should never be nil, unless something terrible had happened } }}

4. Weak referencing when storing delegates

A delegate object is an object that responds or handles the results of certain events or operations that have taken place in a given object called the delegating object. The latter must have knowledge ofthe delegate , usually by having it as one of its property, in order to forward calls to it when needed. In general, the delegate is a responder or slave to the delegating object but it is a commander or driver of the UI or the logic of another object. It is, in the context of this latter that the delegate should be owned i.e strongly held, and not in the context of the delegating class.

  • The delegating class must exist and function even if the delegate does not exist. The delegate is merely a responder, the engine is the delegating class, a good analogy is that the engine of a vehicle must be able to function even if there are no wheels that transform the output into motion. Therefore, it is recommended to store delegate as an optional property in general.
  • The actions of the delegate should be idempotent to the states of the delegating class. Do not make delegate actions change any of the stored states of the delegating class. In general, it should be safe to make the access level of the delegate property as public.
  • The delegate constraints should be defined in by protocol that has to be class-bound in order to allow for weak referencing.
protocol ScannerDelegate: class { func scanner(_ scanner: Scanner, didCompletedScanningWithData scannedData: Data)}class BarCodeViewController: UIViewController {  weak var scannerDelegate: ScannerDelegate?
}
  • There are cases where the delegate can be strongly stored, especially if it is the delegating class that is creating the delegate. Do not fall into the trap of assuming that delegate = weak. Suppose you are creating a custom text view with autoformatting and the behavior is fixed. You might need to set an internal delegate to update/format the content accordingly, in all practicality that delegate would have to be stored strongly. Note that in this case the idempotent nature the delegate actions, mentioned earlier may be broken.

5. Weak referencing for two way ownership

Let’s consider two objects a and b. A two-way relationship is one where a has b as a property and a has b as a property. A common case of two-way ownership is delegation. A UITableviewController for instance, will have the tableview as a property of type UITableView and the tableview might have the very same UITableviewController as a property of type stored as UITableViewDataSource or UITableViewDelegate or both.

class UITableViewController {
var tableView: UITableView! { get set } // owns a UITableView with strong reference
}
class UITableView {
weak var delegate: UITableViewDelegate? { get set } // owns a UITableViewDelegate view with weak reference
weak var dataSource: UITableViewDataSource? { get set } // owns a UITableViewDataSource view with weak reference}

In the case of a UITableViewController, the tableview property is strongly owned, however in the case of a UIViewController which has an tableview outlet added via the nib, the tableview outlet property will have to be held with a weak reference. Let’s attempt to explain why IB Outlets are generally weakly referenced. An outlet is declared as a weak reference (weak) to prevent strong reference cycles. The outlet is owned by both its view controller as an outlet and by its view as a subview, when the outlet points upstream in the view hierarchy, a reference cycle is likely to be created. Since in general, one is not entirely certain that an outlet will never point upstream in the view hierarchy, it is recommended to use a weak reference to ensure that a retain cycle is never created.

class MyUIViewController: UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak private(set) var tableView: UITableView! { get set } // owns a UITableView with weak reference, linked to the a tableview added in the nib for MyUIViewController
// MARK:- UITableViewDataSource// required methods for UITableViewDataSource go here// MARK:- UITableViewDelegate// required methods for UITableViewDelegate go here}class UITableView {
weak var delegate: UITableViewDelegate? { get set } // owns a UITableViewDelegate view with weak reference
weak var dataSource: UITableViewDataSource? { get set } // owns a UITableViewDataSource view with weak reference}

There are a plethora of other cases where such bidirectional ownership might be used, and in all those cases we should remember to weak reference one of the objects in the other. Here are a few cases: Let’s assume an operation manager that tracks and controls a given operation which depends on a given set of conditions. Let’s say that based on those conditions, the operation can be canceled, stopped or restarted. We need some means of informing the operation manager of any change of state in the operation. If we find ourselves working in a performance-critical system, using a polling approach will be inefficient, a suitable solution will require using a pushing approach. In other terms, the operation has to the one notifying its manager of any of its change of state, it would, therefore, need to have knowledge (own a copy) of its manager. Inversely, it is also primordial for the operation manager to have knowledge (own a copy) of the operation in order to command it. If we already established that weak referencing must be used in such cases, a crucial question to answer before setting the weak and strong references adequately, is: What really owns what? A systematic approach to finding the answer to this question is to identify the object with the longer lifecycle, the latter is usually the true owner and must, therefore, be the one holding a strong reference to the other.

class Operation {
weak var opertionManager: OperationManager? { get set } // owns a OperationManager with wealreference
}
class OperationManager {
let operation: Operation? { get set } // owns a OperationManager with wealreference
}

6. Weak referencing when storing a list of responders or observers

In previous sections, we have discussed various cases where a single object was to be held with a reference, in this last section we will discuss the case where we need to store a collection of objects by a weak reference.

Let’s get back to our operation manager and let’s modify its behavior. Instead of commanding a single operation, let’s make it command multiple operations that might depend on each other. As previously established, each one of the operations will hold a weak reference to the operation manager. In turn, the operation manager has to hold a weak reference to each one of the operations it controls. In general, we would have fixed a number of managed operations, this imposes the use of collection.

The collection that will help achieve weak referencing is NSPointerArray. I suggest you now read about NSPointerArray before continue reading this section. In summary, the NSPointerArray array class is modeled after theNSArray, but can also hold nil values. You can insert or remove nil values which contribute to the array's count.

A pointer array can be initialized to maintain strong or weak references to objects, or according to any of the memory or personality options defined by NSPointerFunctions.Options.

class OperationManager: NSObject {     private var weakOperations =   
NSPointerArray.weakObjects() // NSPointer array with
// weak referenced objects
public func addOperation(_ operation: AbstractOperation) { objc_sync_enter(self) // safe to synchronize unowned var weakOperation = operation // because at this point
// we guarantee the existence of
// operation but we do not want to own it
let pointer =
Unmanaged.passUnretained(weakOperation).toOpaque()
weakOperations.compact() // remove operations that
// might have been deallocated
weakOperations.addPointer(pointer) // insert weak reference objc_sync_exit(self) }}

Before we define aremoveOperation, let’s explain the addOperation in the snippet above.

Firstly, we have to define a weakOperations property of type NSPointerArray, we have to use the designated constructor for weakObjects to ensure that our object will be weakly referenced.

Secondly, we define a function to help add objects into the weakOperations property func addOperation(_ operation: AbstractOperation) . We have synchronized our function since the insertion of objects might occur in various threads and given that we are inserting pointers, there are just so many issues, that might arise from a lack of synchronization. The discussion of these issues is beyond the scope of this article. The line unowned var weakOperation = operation ensures that we are only adding non null objects and that we are at no point creating a stronghold to any object inserted. This important to prevent the addition of nulls and to assert the hypothesis indicated by the non optional AbstractOperation parameter of this method. You might omit unowned if you wish to add an object that can be null at the time of insertion.

Thirdly, in the line let pointer =
 Unmanaged.passUnretained(weakOperation).toOpaque()
, we create a weak pointer indicated by the passUnretained , clean up all deallocated objects weakOperations.compact()and insert the weak pointer weakOperations.addPointer(pointer) . Of course, we end by exiting our synchronization block.

In general, you would not need an explicit remove command since the objects are weakly stored. Every time that compact() is called, all deallocated operations will no longer be part of the weakOperations . However, let’s assume that for some reasons you needed an explicit removal.

// Inside OperationManager class@objc public func removeOperation(_ operation: Operation) {  objc_sync_enter(self) // safe to synchronize, especially                 // if your operation addition                 // might occur in various thread  weakOperations.compact() // remove operations that                          // might have been deallocated  var validOperations: [AnyObject]? = 
weakOperations.allObjects as [AnyObject]? // obtain
// remaining operations
var operationToRemove = operation as? AnyObject // convert operation// to remove into any since we store// pointers and type specific objects if let index = validOperations?.firstIndex(where: { $0 === operationToRemove }) { validOperations = nil // remove array having strong holds weakOperations.removePointer(at: index) } objc_sync_exit(self)}

The line let validOperations = weakOperations.allObjects as [AnyObject]? helps us create an array storing objects of type AnyObject . We need this array to compute the index of pointer corresponding to the object to remove. Since we store the object without any specified type, we had to use the AnyObject which will represent any possible object.

All we need before removing the pointer is to compute the index of the object to be removed. After we found that index, we will need to destroy the array since it has strong references to the objects it contains. To find the index, we use validOperations?.firstIndex(where: { $0 === operationToRemove }) . Note that the usage of the identity operator === is crucial. Using the equality operator ==might erroneously lead to the removal of a different object. It is important to mention that equal objects in Swift, are not necessarily the same object. The result of the equality check operation between two objects of a given type depends solely on how the given type adheres to Equatable protocol , whereas the result of the identity check operation depends on whether the objects share the very same memory address and have same content in all aspects.

A playground containing all snipped and code of this article can be found in this git repo.

--

--