A shortcut for weakly referencing functions


(Radek Pietruszewski) #1

Here’s a pattern I find myself doing quite often:

  1> class Child {
  2. var onSomeEvent: () -> Void = { }
  3. }
  4> class Parent {
  5. let child = Child()
  6. init() {
  7. child.onSomeEvent = doSomething
  8. }
  9. func doSomething() {
10. }
11. }

I have some ownership hierarchy of classes (often controllers — view controllers — views), where I pass information from children up to parents using closures set by the parent.

I like this pattern because children classes don’t have to be tied to knowledge about their parents, and I don’t have to define delegate protocols. It’s very clean, and also very easy to set up.

The only problem is that there’s a strong danger of introducing reference cycles.

With class properties, you can quite easily see the potential for a reference cycle and mark references to parents with weak/unowned. And with `self` captures in closures, you’re reminded of memory management by having to be explicit about `self`. But when passing references from parents to children, there’s no way to mark the closure property as `weak` (and there’s no reminder of the danger).

* * *

Right now, every time I pass a closure down to children, I have to wrap my references like so:

  { [unowned self] self.doSomething($0) }

instead of a neat and simple function reference:

  doSomething

I think it would be useful to have a shortcut syntax for creating weak and unowned references to functions, like so:

  @unowned(doSomething)

or perhaps:

  #unowned(self.doSomething)

* * *

An alternative would be the ability to mark closure properties as weak or unowned. Then I could, at the *child* level, say:

  unowned let onSomeEvent: () -> Void

* * *

Does this make sense? What do you think?

— Radek


(Joe Groff) #2

As we briefly discussed on Twitter, I feel like Swift already has too much closure syntax as it is. { [unowned self] self.doSomething($0) } is definitely more text than self.doSomething, but it's clear what it's doing. "self.doSomething" method applications are also the only place where we directly capture an object into a closure without explicit closure syntax, which is where much of the surprise about memory leaks comes from. Even when strong references are desired, it's arguable that { self.doSomething($0) } is still preferable, since it's more obviously capturing `self`, and that the 'self.doSomething' shorthand is a misfeature.

-Joe

···

On Apr 1, 2016, at 8:09 AM, Radosław Pietruszewski via swift-evolution <swift-evolution@swift.org> wrote:

Here’s a pattern I find myself doing quite often:

1> class Child {
2. var onSomeEvent: () -> Void = { }
3. }
4> class Parent {
5. let child = Child()
6. init() {
7. child.onSomeEvent = doSomething
8. }
9. func doSomething() {
10. }
11. }

I have some ownership hierarchy of classes (often controllers — view controllers — views), where I pass information from children up to parents using closures set by the parent.

I like this pattern because children classes don’t have to be tied to knowledge about their parents, and I don’t have to define delegate protocols. It’s very clean, and also very easy to set up.

The only problem is that there’s a strong danger of introducing reference cycles.

With class properties, you can quite easily see the potential for a reference cycle and mark references to parents with weak/unowned. And with `self` captures in closures, you’re reminded of memory management by having to be explicit about `self`. But when passing references from parents to children, there’s no way to mark the closure property as `weak` (and there’s no reminder of the danger).

* * *

Right now, every time I pass a closure down to children, I have to wrap my references like so:

  { [unowned self] self.doSomething($0) }

instead of a neat and simple function reference:

  doSomething

I think it would be useful to have a shortcut syntax for creating weak and unowned references to functions, like so:

  @unowned(doSomething)

or perhaps:

  #unowned(self.doSomething)

* * *

An alternative would be the ability to mark closure properties as weak or unowned. Then I could, at the *child* level, say:

  unowned let onSomeEvent: () -> Void

* * *

Does this make sense? What do you think?

— Radek

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


(Zachary Waldowski) #3

Responses inline!

Sincerely,
Zachary Waldowski
zach@waldowski.me

Here’s a pattern I find myself doing quite often:

  1> class Child {
  2. var onSomeEvent: () -> Void = { }
  3. }
  4> class Parent {
  5. let child = Child()
  6. init() {
  7. child.onSomeEvent = doSomething
  8. }
  9. func doSomething() {
10. }
11. }

I have some ownership hierarchy of classes (often controllers — view
controllers — views), where I pass information from children up to
parents using closures set by the parent.

I like this pattern because children classes don’t have to be tied to
knowledge about their parents, and I don’t have to define delegate
protocols. It’s very clean, and also very easy to set up.

The only problem is that there’s a strong danger of introducing reference
cycles.

With class properties, you can quite easily see the potential for a
reference cycle and mark references to parents with weak/unowned. And
with `self` captures in closures, you’re reminded of memory management by
having to be explicit about `self`. But when passing references from
parents to children, there’s no way to mark the closure property as
`weak` (and there’s no reminder of the danger).

I will go ahead and note, at least in terms of writing the proposal, I
don't find this argument very strong. The proposed syntax still requires
you to know about the cycles involved.

* * *

Right now, every time I pass a closure down to children, I have to wrap
my references like so:

  { [unowned self] self.doSomething($0) }

instead of a neat and simple function reference:

  doSomething

I think it would be useful to have a shortcut syntax for creating weak
and unowned references to functions, like so:

  @unowned(doSomething)

or perhaps:

  #unowned(self.doSomething)

* * *

I really like the #unowned syntax! It's expressive and in line with
other pieces of Swift sugar. Would +1 if it came to a proposal!

The @ version doesn't make as much sense as it's not an attribute.

An alternative would be the ability to mark closure properties as weak or
unowned. Then I could, at the *child* level, say:

  unowned let onSomeEvent: () -> Void

I don't particularly think it makes sense, because it is prescriptive.
If some other user in your codebase wanted the cycle, they couldn't get
it. Plus, what would it mean when the closure isn't capturing anything?

···

On Fri, Apr 1, 2016, at 11:09 AM, Radosław Pietruszewski via swift-evolution wrote:

* * *

Does this make sense? What do you think?

— Radek

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


(Radek Pietruszewski) #4

Here’s a pattern I find myself doing quite often:

1> class Child {
2. var onSomeEvent: () -> Void = { }
3. }
4> class Parent {
5. let child = Child()
6. init() {
7. child.onSomeEvent = doSomething
8. }
9. func doSomething() {
10. }
11. }

I have some ownership hierarchy of classes (often controllers — view
controllers — views), where I pass information from children up to
parents using closures set by the parent.

I like this pattern because children classes don’t have to be tied to
knowledge about their parents, and I don’t have to define delegate
protocols. It’s very clean, and also very easy to set up.

The only problem is that there’s a strong danger of introducing reference
cycles.

With class properties, you can quite easily see the potential for a
reference cycle and mark references to parents with weak/unowned. And
with `self` captures in closures, you’re reminded of memory management by
having to be explicit about `self`. But when passing references from
parents to children, there’s no way to mark the closure property as
`weak` (and there’s no reminder of the danger).

I will go ahead and note, at least in terms of writing the proposal, I
don't find this argument very strong. The proposed syntax still requires
you to know about the cycles involved.

Oh, I agree. The proposal doesn’t fix the relative undiscoverability (is that a word?) of the potential for reference cycles when passing function references.

Still — I think, maybe, when you can say in tutorials and programming guides that you avoid reference cycles when passing functions down the ownership hierarchy by wrapping those function references in #weak(…) / #unowned(…), it might be easier to grasp and remember than `{ [unowned self] in self.foo($0) }`.

* * *

Right now, every time I pass a closure down to children, I have to wrap
my references like so:

  { [unowned self] self.doSomething($0) }

instead of a neat and simple function reference:

  doSomething

I think it would be useful to have a shortcut syntax for creating weak
and unowned references to functions, like so:

  @unowned(doSomething)

or perhaps:

  #unowned(self.doSomething)

* * *

I really like the #unowned syntax! It's expressive and in line with
other pieces of Swift sugar. Would +1 if it came to a proposal!

The @ version doesn't make as much sense as it's not an attribute.

Thank you. You’re probably right about #unowned(…) vs @unowned(…). I thought of at-syntax because it feels like applying an attribute to the function reference. (IIRC, weak and unowned were @weak and @unowned in Swift β1)

An alternative would be the ability to mark closure properties as weak or
unowned. Then I could, at the *child* level, say:

  unowned let onSomeEvent: () -> Void

I don't particularly think it makes sense, because it is prescriptive.
If some other user in your codebase wanted the cycle, they couldn't get
it. Plus, what would it mean when the closure isn't capturing anything?

Rright. Because it’s not really about assigning literal closures to such a parameter, rather assigning method references, where the method’s receiver (self in my example) is implicitly captured… So it seems like it should be a responsibility of the code assigning the reference.

— Radek


(Radek Pietruszewski) #5

Thanks for chiming in, Joe, and apologies for not replying promptly!

"self.doSomething" method applications are also the only place where we directly capture an object into a closure without explicit closure syntax, which is where much of the surprise about memory leaks comes from.

Ohhh. So that’s interesting. Makes total sense, but from a programmer’s perspective I just thought of `self.doSomething` as nothing more than a function reference. But, if I understand correctly, it’s actually equivalent to `{ [self] in Foo.doSomething(self)($0) }`, correct?

Even when strong references are desired, it's arguable that { self.doSomething($0) } is still preferable, since it's more obviously capturing `self`, and that the 'self.doSomething' shorthand is a misfeature.

Hm. Not having the shortcut would suck a bit, but it’s hard to argue with the fact that it’s much clearer you’re doing something potentially dangerous.

What about this, then: rip out the `self.doSomething` method applications, but have a shortcut, like the ones I proposed, that’s more explicit about the method capture:

#strong(self.doSomething) // <— currently just self.doSomething
#weak(self.doSomething)
#unowned(self.doSomething)

The balance of closure syntaxes is zero :wink: but the Misfeature Shorthand is now clearer, and forming weak/unowned-self is easier and less noisy.

— Radek

···

On 01 Apr 2016, at 19:35, Joe Groff <jgroff@apple.com> wrote:

As we briefly discussed on Twitter, I feel like Swift already has too much closure syntax as it is. { [unowned self] self.doSomething($0) } is definitely more text than self.doSomething, but it's clear what it's doing. "self.doSomething" method applications are also the only place where we directly capture an object into a closure without explicit closure syntax, which is where much of the surprise about memory leaks comes from. Even when strong references are desired, it's arguable that { self.doSomething($0) } is still preferable, since it's more obviously capturing `self`, and that the 'self.doSomething' shorthand is a misfeature.

-Joe

On Apr 1, 2016, at 8:09 AM, Radosław Pietruszewski via swift-evolution <swift-evolution@swift.org> wrote:

Here’s a pattern I find myself doing quite often:

1> class Child {
2. var onSomeEvent: () -> Void = { }
3. }
4> class Parent {
5. let child = Child()
6. init() {
7. child.onSomeEvent = doSomething
8. }
9. func doSomething() {
10. }
11. }

I have some ownership hierarchy of classes (often controllers — view controllers — views), where I pass information from children up to parents using closures set by the parent.

I like this pattern because children classes don’t have to be tied to knowledge about their parents, and I don’t have to define delegate protocols. It’s very clean, and also very easy to set up.

The only problem is that there’s a strong danger of introducing reference cycles.

With class properties, you can quite easily see the potential for a reference cycle and mark references to parents with weak/unowned. And with `self` captures in closures, you’re reminded of memory management by having to be explicit about `self`. But when passing references from parents to children, there’s no way to mark the closure property as `weak` (and there’s no reminder of the danger).

* * *

Right now, every time I pass a closure down to children, I have to wrap my references like so:

  { [unowned self] self.doSomething($0) }

instead of a neat and simple function reference:

  doSomething

I think it would be useful to have a shortcut syntax for creating weak and unowned references to functions, like so:

  @unowned(doSomething)

or perhaps:

  #unowned(self.doSomething)

* * *

An alternative would be the ability to mark closure properties as weak or unowned. Then I could, at the *child* level, say:

  unowned let onSomeEvent: () -> Void

* * *

Does this make sense? What do you think?

— Radek

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


(James Richard) #6

I run into this a lot and love this idea.

···

On Apr 1, 2016, at 8:09 AM, Radosław Pietruszewski via swift-evolution <swift-evolution@swift.org> wrote:

Here’s a pattern I find myself doing quite often:

1> class Child {
2. var onSomeEvent: () -> Void = { }
3. }
4> class Parent {
5. let child = Child()
6. init() {
7. child.onSomeEvent = doSomething
8. }
9. func doSomething() {
10. }
11. }

I have some ownership hierarchy of classes (often controllers — view controllers — views), where I pass information from children up to parents using closures set by the parent.

I like this pattern because children classes don’t have to be tied to knowledge about their parents, and I don’t have to define delegate protocols. It’s very clean, and also very easy to set up.

The only problem is that there’s a strong danger of introducing reference cycles.

With class properties, you can quite easily see the potential for a reference cycle and mark references to parents with weak/unowned. And with `self` captures in closures, you’re reminded of memory management by having to be explicit about `self`. But when passing references from parents to children, there’s no way to mark the closure property as `weak` (and there’s no reminder of the danger).

* * *

Right now, every time I pass a closure down to children, I have to wrap my references like so:

  { [unowned self] self.doSomething($0) }

instead of a neat and simple function reference:

  doSomething

I think it would be useful to have a shortcut syntax for creating weak and unowned references to functions, like so:

  @unowned(doSomething)

or perhaps:

  #unowned(self.doSomething)

* * *

An alternative would be the ability to mark closure properties as weak or unowned. Then I could, at the *child* level, say:

  unowned let onSomeEvent: () -> Void

* * *

Does this make sense? What do you think?

— Radek

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