[swift-evolution] Retain cycles in closures

Yvo van Beek yvo at yvo.net
Wed Aug 30 17:45:11 CDT 2017


When I'm writing code I like it to be free from any distractions that
aren't really relevant to the problem that I'm trying to solve. One of
these distractions is having to pay a lot of attention to retain cycles. As
my code grows, I start making extensions to simplify my code.

I've created the following helper for DispatchQueues:

  extension DispatchQueue {
    func async<T: AnyObject>(weak arg: T, execute: @escaping (T) -> Void) {
      async { [weak arg] in
        if let argRef = arg { execute(argRef) }
      }
    }
  }

It allows you to do this:

   DispatchQueue.main.async(weak: self) { me in
    me.updateSomePartOfUI()
  }

When functions are passed as a closure, the compiler won't warn about a
possible retain cycle (there is no need to prefix with self). That's why
I've also created helpers for calling instance functions:

    func blockFor<Target: AnyObject>(_ target: Target, method: @escaping
(Target) -> () -> Void) -> () -> Void {
    return { [weak target] in
      if let targetRef = target { method(targetRef)() }
    }
  }

  func blockFor<Target: AnyObject, Args>(_ target: Target, method:
@escaping (Target) -> (Args) -> Void, args: Args) -> () -> Void {
    return { [weak target] in
      if let targetRef = target { method(targetRef)(args) }
    }
  }

Calls look like this:

  class MyClass {
    func start() {
      performAction(completion: blockFor(self, method: MyClass.done))
    }

    func done() {
      ...
    }
  }

When you look at code samples online or when I'm reviewing code of
colleagues this seems a real issue. A lot of people probably aren't aware
of the vast amounts of memory that will never be released (until their apps
start crashing). I see people just adding self. to silence the complier :(

I'm wondering what can be done to make this easier for developers. Maybe
introduce a 'guard' keyword for closures which skips the whole closure if
the instances aren't around anymore. Since guard is a new keyword in this
context it shouldn't break any code?

   DispatchQueue.main.async { [guard self] in
    self.updateSomePartOfUI()
  }

I don't have any ideas yet for a better way to pass functions as closures.

- Yvo
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170830/3ec877a9/attachment.html>


More information about the swift-evolution mailing list