Weak Reference Collections

(Maik) #1

Hey there,

since there are some collection related discussions active, it’s probably a good time to bring weak reference collections up.

Sometimes weak collections are needed / better to avoid reference cycles. At the moment this can be only done using Objective-C frameworks or creating a wrapper object. In my opinion the standard library should provide a way to use weak reference collections. This could also be done using a third party library, but I feel like it should be added to the standard library.

I could imagine:

let weakArray = [Weak<SomeObject>]()
let weakArray = [weak SomeObject]()
let weakArray = Array<SomeObject>.weakReferences()

The implementation should be done on base type, so that it can be used for arrays, sets, dictionaries and so on.

What do you think?

Evolution of Swift Evolution?
(Charlie Monroe) #2

I’ve written myself a wrapper for this and a WeakArray struct that automatically did something like this. The challenge IMHO is to decide whether to do automatic cleanup of nulled values or not. I.e. if you have an array of 3 objects, one deallocs, nulling one of the items in the array. What is the array’s count? Still 3? Or 2? Should it automatically shrink and removed nulled values?

(Maik) #3

I had that in mind too. There could be a parameter (with a default value) passed to the constructor.

(Jeffrey Macko) #4

objc.io guys take a stab at this a couple month ago https://www.objc.io/blog/2017/12/28/weak-arrays/

(Jon Hull) #5

I think it should be written [weak SomeObject?], where it has to be an optional like other weak variables. The array should not automatically shrink (since that would lead to hard to reproduce bugs, especially when multi-threading), but instead have the value become nil. Thus the count would remain the same.

Removing nil values can be easily done with compact() as desired.

(Jessy) #6

I don't think you can do automatic cleanup; otherwise you can't declare lets.

+1 from me on this. I've been using WeakReferencer and UnownedReferencer boxes since June 2014.

(Charlie Monroe) #7

In my implementation, the automatic cleanup happens when the an item is added or removed, which are mutating functions. The iteration also goes only over nonnil values, which prevents you from doing:

for let observer in self.observers {
    guard let observer = observer else { continue }

Yes, you can do the for loop over filtered/compacted values, but IMHO you are more interested in nonnil values... At least in my applications I was, so the automatic cleanup during mutating methods made more sense, as well as looping over nonnil values only.

(John McCall) #8

FWIW, the weak/unowned design originally suggested adding wrapper structs, but we never got around to adding them because we weren't sure enough about the use cases.

(Joe Groff) #9

Furthermore, putting a wrapper struct in a vanilla collection is often not really what you want from a collection of weak references. Weak collections in practice are usually more specialized. For instance, a number of people in this thread raised the question of automatic removal of dead references, which is a common desire for caches, and a reasonable thing to expect in general from a collection of weakly-held references. As such, having wrapper structs only to enable the formation of [Weak<T>] types might also be misleading or suboptimal for the most common use cases. Having standardized WeakArray and WeakDictionary collections may be more useful.

(Maik) #10

Declaring a let reference will create a strong pointer to the embedded object. So it can‘t be released by an automatic clean up. Or did I misunderstand you?

(Charlie Monroe) #11

You have a let for the array, but the array is most unlikely to keep the references on stack (assuming it's a struct), but will alloc a block of memory to keep the references - and the weak references are automatically nulled by the runtime - which will work with a let, even though it kind of violates the value-type semantics a little bit. My guess is that the WeakArray would ideally be a class instead of a struct.

(David Harris) #12

Weirdly enough, I can't get this to compile anymore in a framework with Incremental compilation. Whole Module works fine, but then if you try to compile it in isolation with whole module it breaks all of the sudden. Xcode doesn't even report an error, but at the command line the output is:

WeakBox.swift:24:25: error: 'WeakBox' requires that 'WeakArray<Element>.Element' (aka 'Optional<Element>') be a class type
    private var items: [WeakBox<Element>] = []
WeakBox.swift:15:13: note: requirement specified as 'A' : 'AnyObject' [with A = WeakArray<Element>.Element]
final class WeakBox<A: AnyObject> {

Can investigate more, but this just started happening.