[swift-evolution] Proposal: Allow `[strong self]` capture in closures and remove the `self` requirement therein

Marc Knaup marc at knaup.koeln
Tue Dec 15 19:37:08 CST 2015


No leak here:

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)
        }
    }
}

do {
    let p = Photo()
    p.save()
}

let sema = dispatch_semaphore_create(0)
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)



On Wed, Dec 16, 2015 at 2:32 AM, Marc Knaup <marc at knaup.koeln> wrote:

> dispatch_semaphore_wait never returns and thus the current scope is never
> left. This is likely what keeps the Photo instance alive.
>
> On Wed, Dec 16, 2015 at 2:24 AM, Marc Knaup <marc at knaup.koeln> wrote:
>
>> Waiiiit. That looks more like a bug in Swift than expected behavior which
>> would us prevent [strong self] from being used.
>>
>>    - metadata is a value type and thus should be copied. There's should
>>    be no implicit reference back to the Photo instance.
>>    - Capturing "self" in the closure because a property is accessed is
>>    fine and intentional. The reference to self would normally be destroyed as
>>    soon as the closure is gone.
>>
>> So something must erroneously keep either the closure alive or hold a
>> reference to self which it shouldn't.
>>
>> On Wed, Dec 16, 2015 at 2:12 AM, Drew Crawford via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>> 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.
>>>
>>> _______________________________________________
>>> 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/20151216/2d7b81ad/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/20151216/2d7b81ad/attachment.jpg>


More information about the swift-evolution mailing list