Shouldn't be [weak self] the default?

Hi,

I recently had a bug where I should have been using [weak self] instead of the default strong self (writing nothing).

I know this topic is being discussed a lot lately. But I haven't read anything about the changing the default behaviour in swift. When thinking about all the times where I really needed a strong self and the times where using a weak self would be ok too I came to the point that the majority of closures would work fine with weak selfs. This might be because of the domain I developing for or not. I'm not sure.

Wouldn't it be "better" to change the default behaviour to [weak self] and introduce a strong / guard / whatever the community decides in these topics?

What do you think?

3 Likes

there’s also [unowned self] and capture variable separately [foo = self.foo]

How did you miss the strong self capture?
The default is actually not capture, you need to explicitly do self.something to get strong capture.

2 Likes
  1. It wouldn't match the default behavior for all the other variables
  2. It would be very source breaking

Also, the explicit strong capture exists already, and is spelled [self], so you don't have to introduce that one

2 Likes

There are many valid cases to omit capturing self at all with no risk of memory leaks. For instance if the closure is not retained by anybody. Treating the omission of capture as [weak self] would be a strange choice IMO since it's not the only way to capture self (we also have unowned). Even though I apply weak self to most of my closures, I don't think I would want it as a default.

1 Like

Oh, I didn't know that. In that case I missed it and had an explicit call. I just missed it by human error.

I totally agree on your first point. It would be source breaking but it could be done by a script. So I guess that would be kind of ok?

I just thought that since it's the (at least for me) most used case it would make a sense having it as a default case. For example in languages like Java where you have to write final in front of every variable people tend to not do it, because it's "more work" I guess. Even if there is no reason to have it non-final. I guess a lot of people like to have the "better" choice as a default.

As I said I'm not sure if it is really a better choice. I use it a lot, but of cause there are reasons to have it non-weak too.

So far, I haven't seen any argument that could convince me that the chosen default is better than capturing everything weak when no setting is given - weak is the right choice in many situations, and yet it's the most tedious option because of the weak/strong dance which is very annoying for many developers.
But apparently the hight priests of Swifthood ;-) had I different opinion, and as changing it now would break compatibility, it's very unlikely that the default will ever be changed.

It might be possible to infer weak capturing when the variable in question is used as an Optional - but this has it's own share of problems, so I think we are stuck with what we have.

3 Likes

I'm not a person that decides if that's ok, but I feel like it was okay before swift 4, and not okay now because there was a big push towards stabilising the language

I think I pretty profoundly disagree with this point. There are fundamentally two cases in which you capture variables:

  1. Where you are providing a closure that is going to be invoked multiple times as part of a peer object (basically the delegate pattern).
  2. Where you are providing a closure that is going to be invoked once, as a callback. Think of things like DispatchQueue.async.

Consider the failure modes of changing the default capture strength on each case. The status quo is that each case captures strongly. In the case of (1) this can build reference cycles that require manual breaking or will cause object leaks. In the case of (2), this guarantees that all objects that need to be in memory for your DispatchQueue.async invocation will still be in memory at the time of the call.

What if we flipped the default to capture weakly by default instead? The failure mode of (1) goes away: you can't leak memory any more. However, we've introduced a failure mode for (2): as everything captured in an async block is captured weakly, if nothing else keeps those objects alive they will deallocate and vanish. This will lead to fairly rough-to-debug issues in your code too, where you have to try to discover why you're taking an early exit from an enqueued block.

Neither of these is perfect, but in general I take the view that the default behaviour is pretty clear: if you pass an object into a closure, the language guarantees it will be present unless you say otherwise. Explaining to programmers unfamiliar with the language why all the objects in their closures became Optional for no clear reason would definitely be a source of confusion.

11 Likes

Regardless of containing instance (if there's any) the closures capture local scope variables strongly by default. Replacing this behaviour with the weak capture will introduce unnecessary complexities for accessing everything through optionals and also will be very inconvenient for escaping closures, i.e. those that will be executed after exit from the containing scope.

I don't know why Swift closures are designed that way, however this design echoes how closures are behaving in other languages, so new programmers feel more at home.

Personally, I have different gripe with closures: I want to see non-capturing closure modifier, i.e. when the closure won't allow me to accidentally capture anything from the containing scope. That way closures can be used as local pure anonymous functions, where the purity is enforced.

1 Like

As I mentioned in another thread, If this is occurring often in your code, I think you're probably doing something wrong (?)

This would be nice. I'd also like the possibility to mark instance methods as pure, basically making them not instance methods, but just pure functions where visibility is tied and namespaced to a type.

Kinda like mutating func, I'd like pure func. And the compiler would guarantee that only pure functions can be called from another pure function. self would be inacessible from such a function.

That’d be what @convention(c) does right now.

Almost. But a pure function/closure could still take types not expressible in C as input and/or return values.

1 Like

What you're looking for is @convention(thin), but AFAIK it's not officially supported yet.

I know it's quite off topic, but I would love to know what the status of @convention(thin) is and when it might become supported.

Whether a function captures values has nothing to do with "purity" as people normally use the word.

7 Likes

The difference is that this failure not only easier to detect, but also only happens as a result of a conscious decision of a developer in the majority of cases: I have to add those question marks, guards or capture lists, or the compiler will complain - those objects aren't deallocated unless that behavior is acknowledged.

The default in this scenario would be "you have to think", whereas the current default is "you leak memory". Looking at how much RAM is wasted by common application nowadays, leaks may seem like something you can just ignore - but I still prefer code with proper memory management.

Alas, as I said: We are stuck with what we have, and so we'll see probably one or two requests for [guard self] every year forever... ;-)

There's a lot of related discussion in Pitch: Syntactic sugar for circumventing closure capture lists which I don't want to rehash.

For my own development I would prefer the default capture to be [weak self] rather than [strong self] because I feel it's safer, but there are pros and cons to each being the global default.

Regardless, at this point I doubt we will see a change to the default. The more likely scenario is some new syntactic sugar or feature that makes the current syntax irrelevant and/or deprecated.

I think the most interesting thing here would be a proposal to call out long-lived "delegate" closures for special treatment.

My experience in reading other people's code is that they vastly over-use weak self; I see people frequently using weak self in e.g. completion handlers, which ought to be totally unnecessary.

14 Likes
Terms of Service

Privacy Policy

Cookie Policy