<div dir="ltr">Waiiiit. That looks more like a bug in Swift than expected behavior which would us prevent [strong self] from being used.<div><ul><li>metadata is a value type and thus should be copied. There's should be no implicit reference back to the Photo instance.<br></li><li>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.</li></ul><div>So something must erroneously keep either the closure alive or hold a reference to self which it shouldn't.</div></div></div><div class="gmail_extra"><br><div class="gmail_quote">On Wed, Dec 16, 2015 at 2:12 AM, Drew Crawford via swift-evolution <span dir="ltr"><<a href="mailto:swift-evolution@swift.org" target="_blank">swift-evolution@swift.org</a>></span> wrote:<br><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"><div style="word-wrap:break-word"><span class=""><div style="font-family:HelveticaNeue"><blockquote type="cite">Can you explain what is so evil about func evil() when it is called from an asynchronously-executed closure?</blockquote></div><div style="font-family:HelveticaNeue"><br></div></span><div style="font-family:HelveticaNeue">Apologies for earlier brevity, perhaps it would be helpful for me to present a more complete, realistic example. Paste the following into main.swift:</div><div style="font-family:HelveticaNeue"><br></div><blockquote style="font-family:HelveticaNeue;margin:0px 0px 0px 40px;border:none;padding:0px"><div>import Foundation</div><div><br></div><div>print("Hello, World!")</div><div><br></div><div>typealias evilArg = [String:String]</div><div>var strongReference: evilArg! = nil</div><div>func evil(foo:evilArg ) {</div><div> strongReference = foo</div><div>}</div><div><br></div><div>final class Photo {</div><div> var data = [UInt8](count: 100000000, repeatedValue: 0) //a large amount of data</div><div> let metadata: [String: String] = [:] //a small amount of data</div><div> func save() {</div><div> dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) {</div><div> evil(self.metadata)</div><div> }</div><div> }</div><div>}</div><div>let p = Photo()</div><div>p.save()</div><div>//leaks Photo, data, and metadata</div><div><br></div><div>let sema = dispatch_semaphore_create(0)</div><div>dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER)</div></blockquote><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">In this example, the memory usage at the end of the program is 100MB:</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue"><img height="210" width="258" src="cid:F82AC3D2-D8A5-4366-8545-4EEE3ADA7BD9@austin.rr.com"></div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">This is because Photo, data, and metadata are all leaked by the evil function.</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">This is the (surprising?) behavior specified in the Swift Book:</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue"><blockquote type="cite">[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.<br></blockquote></div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">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 *<b>actually</b>* captures <b>Photo (and therefore data)</b></div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">The capture of Photo (data) is somewhat clear when we write</div><div style="font-family:HelveticaNeue"><br></div><blockquote style="font-family:HelveticaNeue;margin:0px 0px 0px 40px;border:none;padding:0px"><div>evil(self.metadata)</div><div><br></div></blockquote><span style="font-family:HelveticaNeue">But it is hidden when we write</span><div style="font-family:HelveticaNeue"><br></div><blockquote style="font-family:HelveticaNeue;margin:0px 0px 0px 40px;border:none;padding:0px"><div>evil(metadata)</div></blockquote><br style="font-family:HelveticaNeue"><div style="font-family:HelveticaNeue">As you propose.</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">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:</div><span class=""><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue"><blockquote type="cite">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.</blockquote><br></div></span><div style="font-family:HelveticaNeue">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.</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">But in any case. For `strong self` to make sense, we would need to do one of two things:</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">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.</div><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">2. We could introduce additional syntax to provide compiler diagnostics to guard in this case, e.g.</div><div style="font-family:HelveticaNeue"><br></div><blockquote style="font-family:HelveticaNeue;margin:0px 0px 0px 40px;border:none;padding:0px"><div>func lessEvil(@noescape foo:evilArg ) { //@noescape currently not supported for non-closure parameters</div><div> self.strongReference = foo //should generate diagnostic about escaping a @noescape parameter</div><div>}</div><div><br></div></blockquote><span style="font-family:HelveticaNeue">and then</span><br style="font-family:HelveticaNeue"><blockquote style="font-family:HelveticaNeue;margin:0px 0px 0px 40px;border:none;padding:0px"><div><br></div><div><div>dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0)) { [strong self] in</div><div> evil(metadata) //should generate diagnostic that self is required for functions without @noescape</div><div> lessEvil(metadata) //no diagnostic since parameter is @noescape</div><div>}</div></div></blockquote><div style="font-family:HelveticaNeue"><br></div><div style="font-family:HelveticaNeue">I think both of these fixes to the proposal are fairly impractical, but they would weaken my vote to -0.5.</div>
<img src="https://u2002410.ct.sendgrid.net/wf/open?upn=6ZGE61OxINd5lLe2xYh9Ku-2BXbixWNr2nvfzp2IB1sZi1VWd-2BIRIt8-2BRz62cmvYEv7pvXkzZIoIRGIomZLI1XdxnchbDvo86Ak3mtbjHzm5IJrEfFoO1yzbINg9fAW4mRdusw5aoz9wlWq3uAHpDPYOOYT-2F0GpuwrQIlsIinUaKP8kVakkrN6NJfKeqvtlSnRJso618pW5Umgl3Ioa4F3Uiu8MQVLnPloZXZ-2BzHnYa7Y-3D" alt="" width="1" height="1" border="0" style="min-height:1px!important;width:1px!important;border-width:0!important;margin-top:0!important;margin-bottom:0!important;margin-right:0!important;margin-left:0!important;padding-top:0!important;padding-bottom:0!important;padding-right:0!important;padding-left:0!important">
</div>
<br>_______________________________________________<br>
swift-evolution mailing list<br>
<a href="mailto:swift-evolution@swift.org">swift-evolution@swift.org</a><br>
<a href="https://lists.swift.org/mailman/listinfo/swift-evolution" rel="noreferrer" target="_blank">https://lists.swift.org/mailman/listinfo/swift-evolution</a><br>
<br></blockquote></div><br></div>