[swift-evolution] [Draft] @selfsafe: a new way to avoid reference cycles

Brent Royal-Gordon brent at architechies.com
Sun Feb 19 18:30:32 CST 2017


> On Feb 19, 2017, at 6:45 AM, Matthew Johnson <matthew at anandabits.com> wrote:
> 
> 1. Swift *already* acknowledges that it is far easier to create a reference cycle through captured strong references to `self` than any other way.  This is why you have to explicitly say `self.` in escaping closures.

The reason you have to explicitly say `self` is that `self` is the only variable you can implicitly reference. That's disabled in closures, so it's on the same footing as other variables.

Other than that, the only reason `self` is more likely to cause a loop is that, in common Cocoa patterns, you're more likely to be handing the closure to something owned by `self`. But that's not *necessarily* the case, it just happens to be true in many cases.

> 2. There are *already* APIs which are designed to capture an object weak and then call a method on it if it's still around when an event occurs.  In fact this has been a trend in Apple's newer APIs.  Users are able to learn the semantics of these APIs without a problem.  In fact, users like the, because they solve a real problem by ensuring that3.  object lifetime is not extended when using them.
> 
> 3. Swift libraries don't tend to design APIs with weak callback semantics, probably because they are currently syntactically heavy for both users and libraries.  This is true even when weak callback semantics would be beneficial for users and help prevent leaks (which can lead to crashes and other badly behaved apps).
> 
> 4. There have been several ideas proposed to make weak capture easier to do on the call side but they haven't gone anywhere.  The syntactic savings aren't that significant for callers and the burden is still on callers to get it right.

I definitely agree this is a problem, but again, that doesn't mean `self` is the issue here. I think this is mainly because (a) the pattern isn't taught, and (b) there are some minor speed bumps in current Swift (like the inability to shadow `self` in a closure parameter list).

> If users actually need a strong reference there are ways to handle that.  First, if it is likely that a user might want self to be captured strong the API might choose to not use `@selfsafe`.

This doesn't work. The API designer doesn't know what's at the call site, and the user can't modify the API to suit the use.

> If they *do* choose to use it the could also add an overload that does not use it and is disambiguated using some other means (base name or argument label).

That seems like an ugly way to design APIs.

> Finally, users can always add an extra closure wrapper {{ self.myMethod() }} to bypass the API's weak callback semantics.  Here, `self` is captured by the inner closure rather than the the outer closure and the API only converts strong `self` references in the outer closure.

That wouldn't work. The outer closure captures `self` from the context, and the inner closure captures `self` from the outer closure. The outer closure's capture would still be weak.

I really think this focus on `self` is counterproductive. I'm thinking we might be able to address this in a different way.

Let's look at your code sample:

	addAction(takesInt)

With the implicit `self`s made explicit, that would instead be:

	self.addAction(
		self.takesInt
	)

Now let's look at mine:

	alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in
		…
		viewController.performSegue(withIdentifier: "Cancel", sender: self)
	})
	…
		
	viewController.present(alert)

These have a similar pattern: The closure ends up being passed to a method on one of the variables it captures. Your example:

	self.addAction(
	^ Closure gets passed to method on `self`
		self.takesInt
		^ Closure captures `self`
	)

Mine:

	alert.addAction(UIAlertAction(title: "Delete", style: .destructive) { _ in
	^ Closure passed to method on `alert`
		…
		viewController.performSegue(withIdentifier: "Cancel", sender: self)
		^ Closure captures `viewController`
	})
	…
		
	viewController.present(alert)
	^ `alert` passed to `viewController`

This seems like something the compiler—or perhaps a static analyzer?—could warn about. And it wouldn't target `self` specifically, or silently change the meaning of your code.

-- 
Brent Royal-Gordon
Architechies



More information about the swift-evolution mailing list