[swift-evolution] Retain cycles in closures

Jon Shier jon at jonshier.com
Wed Aug 30 17:50:04 CDT 2017


	Just because self is used in a a closure doesn’t mean a retain cycle has been created. I don’t worry about retain cycles usually, but always make sure to run my app through the Memory instrument before release, since that should detect any cycles that are present.
	That said, it would be very nice if Swift had an elegant solution here.


Jon


> 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()
>   }
> 
> 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/b976e0a8/attachment.html>


More information about the swift-evolution mailing list