Weak closure reference


#1

I requested a new feature

Implement weak var callback: (() -> ())?

What does the community think about this?

https://bugs.swift.org/browse/SR-8205


(Slava Pestov) #2

What would the semantics be?


(Matthew Johnson) #3

This topic has come up before in one of the discussions lined to here: Guarded closures. You may find those threads worth reading for background on the topic.


(David Beck) #4

I agree. On a related note, I think structs (and protocols that could be structs) should be allowed to be weak too since they may contain reference counted objects. Why should I have to make my delegate protocol require a class implementation just so I can mark it as weak in case it is?


(Slava Pestov) #5

What would it mean to have a weak struct though? When would the weak "reference" become nil?


(David Beck) #6

Assuming it has plain contents, nothing. It would effectively be a no-op. But in the case that it has object properties, each of those would be weakly referenced instead of strongly.


(Alexander Momchilov) #7

Yet another problem that would so simply be solved if weak was just a Weak<T> struct, but people keep shitting on that idea whenever I see it brought up, lol.


(Slava Pestov) #8

I'm not sure what problem that would solve. It looks like a change to the spelling that would be trivial to implement but not really have a semantic effect.


(Slava Pestov) #9

Array<T> is a struct. Would this that a weak Array<T>? would become nil if the strong reference count of any element of the array dropped to 0? What if the element type T is itself a struct?


(Daniel Resnick) #10

I agree. I think something that would solve that particular problem would be something like Box<T> that purely is an object wrapper for a value type T and conforms to all protocols that T conforms to by delegating to it.


(Alexander Momchilov) #11

Oops, I'm really tired and I was very unclear. Let me try again.

weak as a language attribute is given a lot of special treatment that it just doesn't need. The ability to make weak closure is yet another undeserved special case. The other being the lack of ability to make weak types nested within other types.


(Goffredo Marocchi) #12

What would it mean if the storage type of a strict variable were weak though? Strings are value types and the weak/strong modifier is reference counting territory really.


#13

Closure is a reference type.
I'm not sure what does namely 'semantics' mean.
But in my example

class Container {

    weak var callback: (() -> ())? // this line

    func some() { }

    init() { }

    deinit {

        print("deinit() Container")

    }

}

var container: Container? = Container()

container?.callback = container?.some 

container = nil

should help to resolve the retain cycle.

It is possible to use nil closure. I don't understand why this was not implemented.

Closures are reference types.

https://docs.swift.org/swift-book/LanguageGuide/Closures.html

weak , imho, have to be implemented.


(Adrian Zubarev) #14

It's not the value that should be weak but the captured refences. If any of the reference becomes unavailable, the whole optional will invalidate and return nil. You may also want unowned closures where you known what you're doing.

I pitched this once myself: [Pitch] Introduction of weak/unowned closures


#15

guarded closures, imho, contain difficult context and restrictions to understand.


(David Beck) #16

If I understand how Array is implemented, it would effect the reference count of the underlying storage class, not the individual elements. So if I have a reference to a and a weak reference to [a], the array would immediately be released because there are no other references to the storage.

That being said, Array (and other collections) are a good example of the problems this would introduce. Many users would think that weak var foo: Array<T>? would weakly reference the elements instead of the array itself.

The biggest area I find myself wanting this is with delegates. For instance:

protocol FooDelegate {}

class Foo {
    weak var delegate: FooDelegate?
}

That won't compile because in this case, FooDelegate could be a struct. But as Foo I don't really care what implements my delegate protocol, I just don't want to create a retain cycle if it's a class that might reference me. But for that matter, I wouldn't want a struct that references me to retain me either. So you have to restrict FooDelegate to be a class or risk a retain cycle.


#17

The problem with this is that it's effectively unusable.

A weak variable is incapable of keeping the referenced object alive. Therefore, in order for such a variable to be non-nil ever, there must be an owning variable somewhere else.

But that's just not how closures are typically used. A stored callback is typically set from a passed parameter that comes from a closure literal ({ … }), which is stored strongly nowhere else.

A delegate property is typically a different scenario. Typically, an object A sets itself as a delegate of an object B, and stores an owning reference to object B, which means B's weak var delegate: … property is non-nil for as long as A is alive.

Or, if object A creates a new object C to set as the delegate of object B, then A is responsible for keeping C alive.

But this isn't a thing for closures. Object A typically has no reason to store an owning reference to a closure that it passes to B (or sets as a property of B).


#18

Weak closures have to be used when you try to use a function as a closure. When you feed a function of a current class into another class function with, for instance, completion handler result the only choice is to use inside the second class weak closure to prevent reference cycle.


#19

My point was that storing a weak reference to the closure doesn't help.

What is need in the case you describe is a strong reference to a closure that captures self weakly. In a different context, I suggested new syntax along this line:

    self.callback = [weak self in] self.someMethod

However, once the closure that captures self strongly is created, you can't retrospectively make the self capture weak.


#20

It doesn't help when you save function as a closure.

Please, understand, capturing using [ some_to_do ] is another context.