[swift-evolution] Retain cycles in closures
John McCall
rjmccall at apple.com
Wed Aug 30 18:25:43 CDT 2017
> On Aug 30, 2017, at 7:02 PM, Yvo van Beek <yvo at yvo.net> wrote:
>
> 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?
You're correct that there's a temporary cycle which lasts until the queue executes the closure. However, temporary cycles like this rarely cause memory leaks for the same reason that local variables rarely cause memory leaks: eventually the closure will be executed or the function will return. The only way that a closure on a dispatch queue can cause a memory leak is if it constantly enqueues new closures that recreate the same cycle.
If you're doing a dispatch with a very substantial delay (in the hundreds of milliseconds), it can still be a good idea to use a weak reference just so objects can be collected faster. But it's not strictly necessary just to avoid leaks.
John.
>
> - Yvo
>
>
> On Wed, Aug 30, 2017 at 6:48 PM, John McCall <rjmccall at apple.com <mailto:rjmccall at apple.com>> wrote:
>
>> On Aug 30, 2017, at 6:45 PM, Yvo van Beek via swift-evolution <swift-evolution at swift.org <mailto: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 <mailto:swift-evolution at swift.org>
>> https://lists.swift.org/mailman/listinfo/swift-evolution <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/78f08749/attachment.html>
More information about the swift-evolution
mailing list