[swift-evolution] Retain cycles in closures
Yvo van Beek
yvo at yvo.net
Wed Aug 30 18:02:00 CDT 2017
Hi John,
I see that I've used DispatchQueue.main in my example which is probably a
poor choice to demonstrate the issue:
class MyClass {
let queue = DispatchQueue(label: "MyApp.MyQueue")
func start() {
queue.async {
otherClass.doSomethingThatTakesAWhile()
}
...
queue.async {
self.doSomething()
}
}
}
This would create a retain cycle wouldn't it?
- Yvo
On Wed, Aug 30, 2017 at 6:48 PM, John McCall <rjmccall at apple.com> wrote:
>
> On Aug 30, 2017, at 6:45 PM, Yvo van Beek via swift-evolution <
> swift-evolution at swift.org> wrote:
>
> 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()
> }
>
>
> Closures handed off to dispatch queues will not cause retain cycles.
>
> John.
>
> 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
> _______________________________________________
> swift-evolution mailing list
> swift-evolution at swift.org
> https://lists.swift.org/mailman/listinfo/swift-evolution
>
>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20170830/12ac8f23/attachment.html>
More information about the swift-evolution
mailing list