I don't think we need to allow silly things in Swift just to allow precise control of the unnecessary annotations produced in the exported Objective-C interface.
Wait, is this an argument for or against?
I don't think we should allow
weak on Swift properties that don't have their own storage.
I initially bristled at this, because memory semantics of entity relationships are definitely not an implementation detail. For example, the delegate pattern in Cocoa depends heavily on objects not owning their delegates. The zeroing-ness of zeroing weak references also has API implications here.
On the other hand, there are many entity relationships that aren’t exposed as properties. In our code base, we have a widespread
removeObserver pattern – essentially multicast delegates – where observers are held weakly through a collection, and this has to be expressed through documentation and convention rather than annotations, just as the delegate pattern used to be.
Being able to express memory semantics of entity relationships in general wouldn’t necessarily be silly, but it would require something far more expressive than property annotations. As such keeping the latter around because the former might be nice is probably a bad idea.
Are there any resiliency concerns here related to library evolution, as a property may change between being stored and computed in different versions?
I think this is overly pedantic hair-splitting. Not only is "weak reference" a perfectly reasonable term for the existence of a reference that does not keep the object alive (it even has a runtime representation in Swift!), but the API contract of strong vs. weak (Cocoa terminology that predates ARC or even ObjC GC) is important in reasoning about your object graph.
This is pushing me towards keeping
weak on computed properties in Swift.
No; weakness is not part of the interface to the property as far as the language is concerned.
I don't think the language should allow you to write things that have no semantic effect. It will only lead to confusion.
That also implies
weak should not be visible in generated interfaces.
There's something to be said for presentation of an interface being part of defining a library, even if it's unenforceable. But I agree with Michel otherwise: if we take this away, we shouldn't print it at all, or else we've made going from stored to computed property observable in the generated interface.
I'm shifting my position a little bit based on the feedback in this thread.
In the case of (say) a "
delegate" property, it is of course vital to know whether the object takes ownership or not. It's typical (in Apple frameworks) that it's zeroing weak, but some are not, so it's important to "mark" the exceptions.
In the case of a stored property in Swift (and a synthesized @property in ObjC), it's a very natural alignment for
weak to be both a storage annotation and an API contract.
In the case of a computed property in Swift (and an ObjC @property with a custom implementation), there is a great possibility of error if the
weak annotation is "only" an API contract, and not something that the compiler somehow enforces or validates.
This is not a theoretical problem in ObjC, but an actual source of memory management bugs.
weak on computed properties works as API documentation, but easily becomes a source of bugs. Outlawing it is safer, but forces the documentation to be separate from the API.
Both seem viable, but opinion seems to be split on which is better.
Inside the setter of a
weak computed property, could we issue a warning on assigning
newValue to a non-weak stored property of
self? That'd make the annotation useful, even though it'd be easy to evade.
which says it makes sense to have weak on computed properties. is it still true?
weak is not part of the type in the first place? Being able to write
var delegates: [weak Delegate] would be handy. And this would make contact clear. And if weak reference would be less aggressive about dropping zombie side tables, they would be trivially hashable.
It is now possible to have a struct with a weak reference as a single field. Such struct can be used as a return type and type of arguments, it can be used as a value for generic type parameters. But also, being a single field struct it has a memory layout identical to a weak reference. So, de-facto we can use weak references as types.
This is probably a mistake in the Swift type system, and I'm not sure if this could be reasonably fixed at this point. You can work around this by defining a
Weak<T> type that has a weak property internally.
That's pretty much what I ended up doing. But my current solution relies on associated objects, rather than side table, which limits it to Apple platforms. And using custom token instead of side table requires an extra allocation.
And also, currently I cannot express constraint for
T that would enable using both classes and existential types. According to https://github.com/apple/swift/pull/28741, it would be
Weak<T: any AnyObject>. BTW, could someone approve the MR, or at least jump in to continue discussion. @codafi?
Is this really a mistake? If it was part of the type,
weak would implicitly propagate everywhere you copy the variable to, just like implicitly unwrapped optionals did before SE-0054. IUOs were changed because a proliferation of IUOs was considered undesirable. Wouldn't it be even less desirable for
I don’t think that analogy between
weak and IUO is correct. IUO is a transitional technology, and so probably is
unowned(unsafe), but I’m pretty sure
unowned are here to stay. Some mechanism to support weak references is essential for any reference-counting-based system and Swift is not an exception. Even if two will be merged into one for simplicity in the future (hypothetically), at least one of them will remain for sure.
@michelf, do you see any problems with using weak references as types in these or any other cases?
- using weak reference as a type of element/key/value of the container
- using weak reference as a type of function argument/result
- returning weak reference type when inspecting object using Mirror
Should we... um, spawn a new thread?
Moving that discussion to a new thread: Should weak be a type?