[swift-evolution] Proposal: Allow `[strong self]` capture in closures and remove the `self` requirement therein
Drew Crawford
drew at sealedabstract.com
Tue Dec 15 19:12:51 CST 2015
> Can you explain what is so evil about func evil() when it is called from an asynchronously-executed closure?
Apologies for earlier brevity, perhaps it would be helpful for me to present a more complete, realistic example. Paste the following into main.swift:
import Foundation
print("Hello, World!")
typealias evilArg = [String:String]
var strongReference: evilArg! = nil
func evil(foo:evilArg ) {
strongReference = foo
}
final class Photo {
var data = [UInt8](count: 100000000, repeatedValue: 0) //a large amount of data
let metadata: [String: String] = [:] //a small amount of data
func save() {
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {
evil(self.metadata)
}
}
}
let p = Photo()
p.save()
//leaks Photo, data, and metadata
let sema = dispatch_semaphore_create(0)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)
In this example, the memory usage at the end of the program is 100MB:
This is because Photo, data, and metadata are all leaked by the evil function.
This is the (surprising?) behavior specified in the Swift Book:
> [A capture occurs when] the closure’s body accesses a property of the instance, such as self.someProperty, or because the closure calls a method on the instance, such as self.someMethod(). In either case, these accesses cause the closure to “capture” self, creating a strong reference cycle.
Even though evil may seem (to the casual programmer who does not read language specifications for funsies) like the closure captures only the evil argument `metadata`, it *actually* captures Photo (and therefore data)
The capture of Photo (data) is somewhat clear when we write
evil(self.metadata)
But it is hidden when we write
evil(metadata)
As you propose.
I think that if we are going to have semantics that capture Photo (data), it had better look like it in a cursory inspection. The existing syntax is not as great as it could be, but it provides a clue. I think even that the existing syntax isn't good enough, because I expect that many people are unaware of this particular dark corner. As you say:
> Can you explain what is so evil about func evil() when it is called from an asynchronously-executed closure? I don't see an obvious bug here.
It may very well be that there is no *obvious* bug, and so what we may actually need is a proposal to make the programmer even more explicit when referencing self, not less.
But in any case. For `strong self` to make sense, we would need to do one of two things:
1. We could change Swift semantics to only capture metadata in this example, however this is a breaking change, and a very very nontrivial one. I do not know why it is specified this way in the first place, perhaps a designer can weigh in on that. I do know that ObjC is very similar, so there may be compatibility implications.
2. We could introduce additional syntax to provide compiler diagnostics to guard in this case, e.g.
func lessEvil(@noescape foo:evilArg ) { //@noescape currently not supported for non-closure parameters
self.strongReference = foo //should generate diagnostic about escaping a @noescape parameter
}
and then
dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { [strong self] in
evil(metadata) //should generate diagnostic that self is required for functions without @noescape
lessEvil(metadata) //no diagnostic since parameter is @noescape
}
I think both of these fixes to the proposal are fairly impractical, but they would weaken my vote to -0.5.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151215/ef1f91f8/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: Screen Shot 2015-12-15 at 6.26.52 PM.jpg
Type: image/jpeg
Size: 23371 bytes
Desc: not available
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20151215/ef1f91f8/attachment.jpg>
More information about the swift-evolution
mailing list