retain cycle non-obvious when assigning method to closure


(Joshua Scott Emmons) #1

There's a test case demonstrating what I'm about to talking about: https://gist.github.com/jemmons/6f668006fa2712a84807

But here's the nut of it. Given a class like:

class Foo {
  var handler: (()->Void)?
  func noop(){}
  ...
}

If somewhere in this class I do something like:

handler = { self.noop() }

I've created a retain cycle because handler holds a strong reference to a closure which holds a strong reference to self which holds a strong reference to a closure that…

In fact, my understanding is Swift's requirement that self be called out here explicitly is to highlight this danger.

Swift makes it easy to fix the problem with a capture list:

handler = { [unowned self] in self.noop() }

So far, so good. But what if I do something daft like assign noop directly:

handler = noop

This causes a retain loop for all the same reasons as our original example, but there's no self call-out to warn us of impending doom, and no support for some kind of capture list to fix the issue.

Is there any possibility of requiring "self" here? As in: "handler = self.noop" for example? And is there any way of specifying how we want self to be retained here?

Cheers,
-jemmons


(Jacob Bandes-Storch) #2

I also noticed this problem with nested functions recently:

    class Foo {

        var handler: (() -> Void)?

        func noop() {}

        func setup() {
            // *error:*
            handler = { noop() }

            // *no error:*
            handler = noop

            // *no error:*
            func innerFunc() { noop() }
            handler = { innerFunc() }
            handler = innerFunc
        }
    }

Jacob

···

On Sun, Jan 17, 2016 at 11:06 AM, Joshua Scott Emmons via swift-users < swift-users@swift.org> wrote:

There's a test case demonstrating what I'm about to talking about:
https://gist.github.com/jemmons/6f668006fa2712a84807

But here's the nut of it. Given a class like:

class Foo {
var handler: (()->Void)?
func noop(){}
...
}

If somewhere in this class I do something like:

handler = { self.noop() }

I've created a retain cycle because handler holds a strong reference to a
closure which holds a strong reference to self which holds a strong
reference to a closure that…

In fact, my understanding is Swift's requirement that self be called out
here explicitly is to highlight this danger.

Swift makes it easy to fix the problem with a capture list:

handler = { [unowned self] in self.noop() }

So far, so good. But what if I do something daft like assign noop
directly:

handler = noop

This causes a retain loop for all the same reasons as our original
example, but there's no self call-out to warn us of impending doom, and
no support for some kind of capture list to fix the issue.

Is there any possibility of requiring "self" here? As in: "handler = self.noop"
for example? And is there any way of specifying how we want self to be
retained here?

Cheers,
-jemmons

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users


(David Turnbull) #3

Why isn't self unowned by default? Seems like that would fix all kinds of
problems including being a novice.

Here's an example of a case where you need a strong self. Imagine that
[unowned self] is the default and the programmer made the mistake of
forgetting to use [strong self] .

class ClosureFactory {

    let s:String

    init(_ s:String) { self.s = s }

    func writeln() -> () -> Void {

        return {[unowned self] in print(self.s)}

    }

}

let writer = ClosureFactory("hi").writeln()

writer()

The program crashes. Wouldn't this be better than having a default that
makes it easy to write memory leaks? I'd much rather be fixing a bug in
code I wrote 5 minutes ago than 5 months ago. Especially a memory leak.

-david https://github.com/AE9RB/SwiftGL

···

On Sun, Jan 17, 2016 at 11:57 AM, Jacob Bandes-Storch via swift-users < swift-users@swift.org> wrote:

I also noticed this problem with nested functions recently:

    class Foo {

        var handler: (() -> Void)?

        func noop() {}

        func setup() {
            // *error:*
            handler = { noop() }

            // *no error:*
            handler = noop

            // *no error:*
            func innerFunc() { noop() }
            handler = { innerFunc() }
            handler = innerFunc
        }
    }

Jacob

On Sun, Jan 17, 2016 at 11:06 AM, Joshua Scott Emmons via swift-users < > swift-users@swift.org> wrote:

There's a test case demonstrating what I'm about to talking about:
https://gist.github.com/jemmons/6f668006fa2712a84807

But here's the nut of it. Given a class like:

class Foo {
var handler: (()->Void)?
func noop(){}
...
}

If somewhere in this class I do something like:

handler = { self.noop() }

I've created a retain cycle because handler holds a strong reference to
a closure which holds a strong reference to self which holds a strong
reference to a closure that…

In fact, my understanding is Swift's requirement that self be called out
here explicitly is to highlight this danger.

Swift makes it easy to fix the problem with a capture list:

handler = { [unowned self] in self.noop() }

So far, so good. But what if I do something daft like assign noop
directly:

handler = noop

This causes a retain loop for all the same reasons as our original
example, but there's no self call-out to warn us of impending doom, and
no support for some kind of capture list to fix the issue.

Is there any possibility of requiring "self" here? As in: "handler = self
.noop" for example? And is there any way of specifying how we want self
to be retained here?

Cheers,
-jemmons


(Jordan Rose) #4

I agree that there are things we could do to improve this situation. My personal preference would be for "implicit closures" like this to be formed as @noescape, and require you to explicitly write a closure expression if the closure might escape. But any of these suggestions would count as language changes and should thus be discussed on swift-evolution.

Jordan

···

On Jan 17, 2016, at 13:31, David Turnbull via swift-users <swift-users@swift.org> wrote:

Why isn't self unowned by default? Seems like that would fix all kinds of problems including being a novice.

Here's an example of a case where you need a strong self. Imagine that [unowned self] is the default and the programmer made the mistake of forgetting to use [strong self] .

class ClosureFactory {
    let s:String
    init(_ s:String) { self.s = s }
    func writeln() -> () -> Void {
        return {[unowned self] in print(self.s)}
    }
}
let writer = ClosureFactory("hi").writeln()
writer()

The program crashes. Wouldn't this be better than having a default that makes it easy to write memory leaks? I'd much rather be fixing a bug in code I wrote 5 minutes ago than 5 months ago. Especially a memory leak.

-david https://github.com/AE9RB/SwiftGL

On Sun, Jan 17, 2016 at 11:57 AM, Jacob Bandes-Storch via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
I also noticed this problem with nested functions recently:

    class Foo {
        
        var handler: (() -> Void)?
        
        func noop() {}
        
        func setup() {
            // error:
            handler = { noop() }
            
            // no error:
            handler = noop
            
            // no error:
            func innerFunc() { noop() }
            handler = { innerFunc() }
            handler = innerFunc
        }
    }

Jacob

On Sun, Jan 17, 2016 at 11:06 AM, Joshua Scott Emmons via swift-users <swift-users@swift.org <mailto:swift-users@swift.org>> wrote:
There's a test case demonstrating what I'm about to talking about: https://gist.github.com/jemmons/6f668006fa2712a84807

But here's the nut of it. Given a class like:

class Foo {
  var handler: (()->Void)?
  func noop(){}
  ...
}

If somewhere in this class I do something like:

handler = { self.noop() }

I've created a retain cycle because handler holds a strong reference to a closure which holds a strong reference to self which holds a strong reference to a closure that…

In fact, my understanding is Swift's requirement that self be called out here explicitly is to highlight this danger.

Swift makes it easy to fix the problem with a capture list:

handler = { [unowned self] in self.noop() }

So far, so good. But what if I do something daft like assign noop directly:

handler = noop

This causes a retain loop for all the same reasons as our original example, but there's no self call-out to warn us of impending doom, and no support for some kind of capture list to fix the issue.

Is there any possibility of requiring "self" here? As in: "handler = self.noop" for example? And is there any way of specifying how we want self to be retained here?

Cheers,
-jemmons

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users