Is there a weak Set type?

I have some UIViews that I want to associate with a boolean value. I could use a set...

 var isFoo: Set<UIView>

So for any given UIView, my bool value is "true" if the view is in the set. But I don't want the Set retaining views that would otherwise be deallocated. Is there a standard way to do something like this?

There are no weak collections in the standard library (hopefully just yet).

You might want to try implementing such set yourself by wrapping the standard one by declaring a weak box like that:

struct WeakBox<T: AnyObject & Hashable> {
    weak var item: T?

    func hash(into hasher: inout Hasher) {
        hasher.combine(item)
    }
}

and use a Set<WeakBox<UIView>>.

The issue is that these boxes stay in the set even when the boxed object becomes deallocated. This might not seem like a huge problem at first, but this violates the fundamental property of sets: they normally promise to be a collection of unique elements. Let's see what happens if you naively use this implementation:

var set = Set<WeakBox<MyClass>>()
var mc1: MyClass? = MyClass()
var mc2: MyClass? = MyClass()
set.insert(WeakBox(item: mc1))
set.insert(WeakBox(item: mc2))
print(set.count) // prints "2" — so far, the elements are unique
mc1 = nil
mc2 = nil
print(set.count) // prints "2" — two non-unique elements!

I believe that's one of the reasons why the standard library doesn't provide an obvious solution yet. So, you should be careful using weak sets and maybe try finding a different solution for your case.

Maybe this WeakBox could hold a second field for the raw pointer, and use that for implementing Hashable and Equatable, so at least it wouldn't change while in the Set.

The Set would need to be periodically cleaned of zombie values.

Well, you can't guarantee that the same pointer won't be taken by a totally new object before the set is accessed again. I could imagine another implementation based on timestamps, but that would mean that any modification to the set both needs a syscall (to ask the OS for a new timestamp — which is slow) and also to very carefully specify its assumptions about the timestamp type since it will overflow eventually.

Yep, that would be the ideal implementation — on the one hand. But this imposes that all reads will need to be mutating (virtually making this set impossible to declare as a let constant) and take O(n) time instead of the expected O(1) time (since it ought to scan for released weak pointers) — which breaks the common semantics for Set in Swift at least.

May I ask what your actual goal is? Perhaps I could suggest a solution that's not based on set semantics..?

I was trying to attach some properties to UIViews, sort of like "extension properties". I may end up creating a subclass of UIView that wraps one other UIView along with the properties. Or use some old Objective-C API that might be able to attach key/value pairs to UIViews.

Subclassing would be perfect for arranging some new properties/storage per instance — that's one of the primary goals of subclassing.

I understand that it might seem inconvenient in some cases, but any other solution is even less convenient — it would essentially boil down to tracking the lifetime of each of these objects (i.e. overriding initializers and deinitilizers, which still requires subclassing) and manually adding/removing this additional context information. Again: especially that you can't be sure that a pointer won't get occupied by a completely new instance before you get to realize that.

As mentioned, there is no standard way. I made something a long time ago for my use case. It is not exactly what you need, but you might find it useful. I have not tested it with the current version of Swift, but I think it should probably work as is. You can just paste it in a playground and see what happens.

As Nikita says, a subclass may be your best bet.

Maybe the (untested) solution I suggested for Hashing Weak
Variables

will help?

Dave

Terms of Service

Privacy Policy

Cookie Policy