Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoiding using self in the DispatchQueue

I wonder how to eliminate of using self inside the DispatchQueue. As a good practice, we are supposed to use self only in the init()

func loadAllClasses() {
    DispatchQueue.global(qos: .background).async {
      self.classVM.fetchAllClasses(id: id, completion: { (classes, error) in
        DispatchQueue.main.async {
          if error != nil {
            self.showAlert(message: "try again", title: "Error")
          }
          if let classes = classes {
            self.classesList = classes
            self.classesCollectionView.reloadData()
          }
        }
      })
    }
  }
like image 210
konya Avatar asked Oct 16 '25 11:10

konya


2 Answers

Don't worry! DispatchQueue closures don't cause retain cycles.

like image 90
vadian Avatar answered Oct 18 '25 11:10

vadian


You asked:

I wonder how to eliminate of using self inside the DispatchQueue.

In SE-0269, adopted in Swift 5.3, they introduced a pattern where if you include self in the capture list, it eliminates the need for all the self references in the closure:

func loadAllClasses() {
    DispatchQueue.global(qos: .background).async { [self] in  // note [self] capture list
        classVM.fetchAllClasses(id: id) { (classes, error) in
            DispatchQueue.main.async {
                if error != nil {
                    showAlert(message: "try again", title: "Error")
                }
                if let classes = classes {
                    classesList = classes
                    classesCollectionView.reloadData()
                }
            }
        }
    }
}

As an aside, fetchAllClasses would appear to already be asynchronous, so the dispatch to the global queue is unnecessary:

func loadAllClasses() {
    classVM.fetchAllClasses(id: id) { [self] (classes, error) in  // note [self] capture list
        DispatchQueue.main.async {
            if error != nil {
                showAlert(message: "try again", title: "Error")
            }
            if let classes = classes {
                classesList = classes
                classesCollectionView.reloadData()
            }
        }
    }
}

As a good practice, we are supposed to use self only in the init()?

No, you use self wherever it eliminates ambiguity or where you need to make potential strong reference cycles clear. Do not shy away from using self references.

But the intuition, to eliminate unnecessary self references is good, because where not needed, it ends up being syntactic noise. And the whole point of requiring self references inside a closure is undermined if your broader codebase sprinkles self references all over the place.


As an aside, above I illustrate that where you are willing to capture self, you can use [self] in syntax to make one’s code more succinct, eliminate lots of unnecessary self references inside the closure.

That having been said, we would generally want to use [weak self] reference in this case. Sure, as vadian suggested, there might not be any strong reference cycle risk. But it is a question of the desired lifecycle of the object in question. For example, let us assume for a second that fetchAllClasses could conceivably be very slow. Let us further assume that it is possible that the user might want to dismiss the view controller in question.

In that scenario, do you really want to keep the view controller alive for the sake of completing fetchAllClasses, whose sole purpose is to update a collection view that has been dismissed?!? Probably not.

Many of us would use [weak self] when calling a potentially slow, asynchronous process:

func loadAllClasses() {
    classVM.fetchAllClasses(id: id) { [weak self] (classes, error) in  // note `[weak self]` so we don't keep strong reference to VC longer than we need
        DispatchQueue.main.async {
            guard let self = self else { return }                      // if view controller has been dismissed, no further action is needed

            guard error == nil, let classes = classes else {           // handle error and unwrap classes in one step
                self.showAlert(message: "try again", title: "Error")
                return
            }

            self.classesList = classes                                 // otherwise, proceed as normal
            self.classesCollectionView.reloadData()
        }
    }
}

Yes, this has re-introduced the self references that we removed above, but this is a good pattern for asynchronous requests, in general: We never let some network request designed to update a view controller prevent that view controller from being deallocated when the user dismisses it.

A further refinement of this above pattern would be to design fetchAllClasses to be cancelable, and then in the view controller’s deinit, cancel any pending network requests, if any. That is beyond the scope of this question, but the idea is that, not only should we not retain the view controller longer than necessary, but we should cancel pending requests, too. This deinit cancelation pattern, though, only works if you used a weak reference in the closure.

like image 42
Rob Avatar answered Oct 18 '25 09:10

Rob



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!