How do I declare an array of weak references in Swift?

52,269

Solution 1

Create a generic wrapper as:

class Weak<T: AnyObject> {
  weak var value : T?
  init (value: T) {
    self.value = value
  }
}

Add instances of this class to your array.

class Stuff {}
var weakly : [Weak<Stuff>] = [Weak(value: Stuff()), Weak(value: Stuff())]

When defining Weak you can use either struct or class.

Also, to help with reaping array contents, you could do something along the lines of:

extension Array where Element:Weak<AnyObject> {
  mutating func reap () {
    self = self.filter { nil != $0.value }
  }
}

The use of AnyObject above should be replaced with T - but I don't think the current Swift language allows an extension defined as such.

Solution 2

You can use the NSHashTable with weakObjectsHashTable. NSHashTable<ObjectType>.weakObjectsHashTable()

For Swift 3: NSHashTable<ObjectType>.weakObjects()

NSHashTable Class Reference

Available in OS X v10.5 and later.

Available in iOS 6.0 and later.

Solution 3

A functional programming approach

No extra class needed.

Simply define an array of closures () -> Foo? and capture the foo instance as weak using [weak foo].

let foo = Foo()

var foos = [() -> Foo?]()
foos.append({ [weak foo] in return foo })

foos.forEach { $0()?.doSomething() }

Solution 4

It's kind of late for party, but try mine. I implemented as a Set not an Array.

WeakObjectSet

class WeakObject<T: AnyObject>: Equatable, Hashable {
    weak var object: T?
    init(object: T) {
        self.object = object
    }

    var hashValue: Int {
        if let object = self.object { return unsafeAddressOf(object).hashValue }
        else { return 0 }
    }
}

func == <T> (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
    return lhs.object === rhs.object
}


class WeakObjectSet<T: AnyObject> {
    var objects: Set<WeakObject<T>>

    init() {
        self.objects = Set<WeakObject<T>>([])
    }

    init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    var allObjects: [T] {
        return objects.flatMap { $0.object }
    }

    func contains(object: T) -> Bool {
        return self.objects.contains(WeakObject(object: object))
    }

    func addObject(object: T) {
        self.objects.unionInPlace([WeakObject(object: object)])
    }

    func addObjects(objects: [T]) {
        self.objects.unionInPlace(objects.map { WeakObject(object: $0) })
    }
}

Usage

var alice: NSString? = "Alice"
var bob: NSString? = "Bob"
var cathline: NSString? = "Cathline"

var persons = WeakObjectSet<NSString>()
persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]

alice = nil
print(persons.allObjects) // [Cathline, Bob]

bob = nil
print(persons.allObjects) // [Cathline]

Beware that WeakObjectSet won't take String type but NSString. Because, String type is not an AnyType. My swift version is Apple Swift version 2.2 (swiftlang-703.0.18.1 clang-703.0.29).

Code can be grabbed from Gist. https://gist.github.com/codelynx/30d3c42a833321f17d39

** ADDED IN NOV.2017

I updated the code to Swift 4

// Swift 4, Xcode Version 9.1 (9B55)

class WeakObject<T: AnyObject>: Equatable, Hashable {
    weak var object: T?
    init(object: T) {
        self.object = object
    }

    var hashValue: Int {
        if var object = object { return UnsafeMutablePointer<T>(&object).hashValue }
        return 0
    }

    static func == (lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool {
        return lhs.object === rhs.object
    }
}

class WeakObjectSet<T: AnyObject> {
    var objects: Set<WeakObject<T>>

    init() {
        self.objects = Set<WeakObject<T>>([])
    }

    init(objects: [T]) {
        self.objects = Set<WeakObject<T>>(objects.map { WeakObject(object: $0) })
    }

    var allObjects: [T] {
        return objects.flatMap { $0.object }
    }

    func contains(_ object: T) -> Bool {
        return self.objects.contains(WeakObject(object: object))
    }

    func addObject(_ object: T) {
        self.objects.formUnion([WeakObject(object: object)])
    }

    func addObjects(_ objects: [T]) {
        self.objects.formUnion(objects.map { WeakObject(object: $0) })
    }
}

As gokeji mentioned, I figured out NSString won't get deallocated based on the code in usage. I scratched my head and I wrote MyString class as follows.

// typealias MyString = NSString
class MyString: CustomStringConvertible {
    var string: String
    init(string: String) {
        self.string = string
    }
    deinit {
        print("relasing: \(string)")
    }
    var description: String {
        return self.string
    }
}

Then replace NSString with MyString like this. Then strange to say it works.

var alice: MyString? = MyString(string: "Alice")
var bob: MyString? = MyString(string: "Bob")
var cathline: MyString? = MyString(string: "Cathline")

var persons = WeakObjectSet<MyString>()

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObject(bob!)
print(persons.allObjects) // [Bob]

persons.addObjects([alice!, cathline!])
print(persons.allObjects) // [Alice, Cathline, Bob]

alice = nil
print(persons.allObjects) // [Cathline, Bob]

bob = nil
print(persons.allObjects) // [Cathline]

Then I found a strange page may be related to this issue.

Weak reference retains deallocated NSString (XC9 + iOS Sim only)

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

It says the issue is RESOLVED but I am wondering if this is still related to this issue. Anyway, Behavior differences between MyString or NSString are beyond this context, but I would appreciate if someone figured this issue out.

Solution 5

This is not my solution. I found it on the Apple Developer Forums.

@GoZoner has a good answer, but it crashes the Swift compiler.

Here's a version of a weak-object container doesn't crash the current released compiler.

struct WeakContainer<T where T: AnyObject> {
    weak var _value : T?

    init (value: T) {
        _value = value
    }

    func get() -> T? {
        return _value
    }
}

You can then create an array of these containers:

let myArray: Array<WeakContainer<MyClass>> = [myObject1, myObject2]
Share:
52,269

Related videos on Youtube

Bill
Author by

Bill

Updated on July 14, 2022

Comments

  • Bill
    Bill almost 2 years

    I'd like to store an array of weak references in Swift. The array itself should not be a weak reference - its elements should be. I think Cocoa NSPointerArray offers a non-typesafe version of this.

    • nielsbot
      nielsbot about 10 years
      What about making a container object that weakly references another object, then making an array of those? (If you don't get a better answer)
    • Bastian
      Bastian about 10 years
      why don't you use an NSPointerArray ?
    • Sulthan
      Sulthan about 10 years
      @nielsbot That's an old obj-c solution :) To make it Swifty, it should be a generic object! :) However, the real problem is how to get objects removed from the array when the referenced object is deallocated.
    • Bill
      Bill about 10 years
      Right, I'd prefer something with parameterized types. I guess I could make a parameterized wrapper around NSPointerArray, but wanted to see if there were any alternatives.
    • Cy-4AH
      Cy-4AH about 10 years
      Have you tried Array<weak AnyObject>?
    • Mick MacCallum
      Mick MacCallum about 10 years
      Just as another option, NSHashTable exists. It's basically an NSSet that allows you to specify how it should reference the objects it contains.
    • nielsbot
      nielsbot about 10 years
      @Sulthan yes, you will have to use an indirect access... like, if (nil) { remove handle from array }
    • Chris Conover
      Chris Conover over 9 years
      This has to be a language bug as all the solutions proposed achieve the immediate result through encapsulation and break the semantics of the type. For example, an array of protocol references lose their protocol identity, meaning that they cannot be exposed to Interface Builder via IBOutlet.
  • Sulthan
    Sulthan about 10 years
    How do you remove the wrapper objects from the array when their value is deallocated?
  • Bill
    Bill about 10 years
    I actually just hit on this same idea. Unfortunately, it seems to crash the compiler. I can only get it to work if I eliminate the generic parameter. I'll investigate more and then perhaps submit a bug.
  • GoZoner
    GoZoner about 10 years
    Yes, it crashed the compiler.
  • Bill
    Bill about 10 years
    Has to be a class to use weak vars
  • Bill
    Bill about 10 years
    In my experience, it's trivial to crash the compiler when you start using weak vars.
  • GoZoner
    GoZoner about 10 years
    @Sulthan you don't. They just hang around. You'd check for a 'reclaimed value' with if let value = array[index].value? { ... }. Or define class WeakArray : Array { ... }
  • Joshua Weinberg
    Joshua Weinberg about 10 years
    Says who? The code above works fine for me. The only requirement is that the object becoming weak needs to be a class, not the object holding the weak reference
  • Bill
    Bill about 10 years
    Sorry. I could have sworn I just got a compiler message that said "Cannot use weak variables in structs". You're correct - that compiles.
  • Sulthan
    Sulthan about 10 years
    @GoZoner What if you need count?
  • Bill
    Bill about 10 years
    @GoZoner I'm going to award you the accepted answer, because I think this is the correct solution and the compiler has simply not caught up. :-)
  • John Estropia
    John Estropia about 10 years
    This is correct. It only crashes in Playground but works fine in a normal project
  • jasamer
    jasamer over 9 years
    In my Swift project (i.e. not a playground) in Xcode 6.1.1, adding this code crashes the compiler ("Segmentation Fault 11"). It doesn't crash when I create a new project though, so... dunno. Just keep in mind that this may cause Xcode to act up.
  • mente
    mente over 9 years
    strange, but doesn't work with structs anymore. Says EXC_BAD_ACCESS for me. With class works just fine
  • David H
    David H about 9 years
    The question is how to create an array (or say Set) of weak objects.
  • Bartłomiej Semańczyk
    Bartłomiej Semańczyk almost 9 years
    It doesn't work. The controller is still not deinitialised because of this.
  • GoZoner
    GoZoner almost 9 years
    Please post your problem code in a new question; no reason to ding my answer when it might be your code!
  • onmyway133
    onmyway133 over 8 years
    @JoshuaWeinberg what if Foo is a protocol?
  • Lukáš Kubánek
    Lukáš Kubánek over 8 years
    What about using flatMap instead of filter & map ?
  • Ramis
    Ramis over 8 years
    Best answer and do not waist time for wrappers!
  • Aaron Brager
    Aaron Brager over 8 years
    This is clever, but like GoZoner's answer, this doesn't work with types that are Any but not AnyObject, such as protocols.
  • Brian Stewart
    Brian Stewart over 8 years
    In Xcode 7.2.1 / Swift 2 this causes a compiler error: Using as a concrete type conforming to protocol AnyObject is not supported.
  • GoZoner
    GoZoner over 8 years
    More info please. In a 7.2.1 Playground, the provided code compiles/executes as expected.
  • olejnjak
    olejnjak about 8 years
    @onmyway133 AFAIK if protocol is declared to be implemented only by classes it would work. protocol Protocol : class { ... }
  • Theo
    Theo about 8 years
    @EdGamble The provided code works as it is, but fails if you replace the class Stuff by a protocol; see this related question
  • Theo
    Theo about 8 years
    @onmyway133 It should work if Foo is a protocol. If you use Swift 2, a bug leads to compiler errors for some protocols, however. See this question for details and a workaround.
  • GoZoner
    GoZoner about 8 years
    Weak-ness only really applies to reference types - hence AnyObject or :class types. You can observe this trying to declare weak var value : <type> with different types - the Swift compiler will limit your choices to 'class types'.
  • Aaron Brager
    Aaron Brager almost 8 years
    @SteveWilford But a protocol can be implemented by a class, which would make it a reference type
  • David Goodine
    David Goodine almost 8 years
    Structs are value types, it shouldn't work with them. The fact that it crashed at runtime rather than being a compile-time error is a compiler bug.
  • schirrmacher
    schirrmacher almost 8 years
    your example crashes with swift 3.0 beta :(
  • jboi
    jboi almost 8 years
    With this solution, you can even create an array with multiple values like var array: [(x: Int, y: () -> T?)]. Exactly, what I was looking for.
  • Yasmin Tiomkin
    Yasmin Tiomkin almost 8 years
    a protocol can extend class and then you can use it as weak (e.g. protocol MyProtocol: class)
  • eonil
    eonil almost 8 years
    @DavidH I updated my answer to answer the question. I hope this helps.
  • gokeji
    gokeji over 6 years
    I've adopted this solution for my project. Great job! Just one suggestion, this solution doesn't seem to remove nil values from the internal Set. So I've added a reap() function mentioned in the top answer, and made sure to call reap() every time the WeakObjectSet is accessed.
  • gokeji
    gokeji over 6 years
    Hmm wait, for some reason this doesn't work in Swift 4/iOS 11. Seems like the weak reference doesn't get deallocated right away when the value becomes nil anymore
  • Kaz Yoshikawa
    Kaz Yoshikawa over 6 years
    I updated code to Swift4, see the second half of the answer. I seems NSString has some deallocation issues, but It should still work on your custom class objects.
  • gokeji
    gokeji over 6 years
    Thanks so much for looking into it @KazYoshikawa, and updating the answer! I also realized later that a custom class works, whereas NSString doesn't.
  • Guy Daher
    Guy Daher over 6 years
    Thank you so much for this answer. I tried to use this and after running the memory leak instrument, I see some leaks that come from the WeakObjectSet class. The way I use it is I have weak references to UIKit components such as UITableViews, UICollectionViews. These components implement a class protocol A, and then in my class I declare a weak set like this: var set = WeakObjectSet<A>(). When the ViewController dismisses along with its views, set behaves correctly (the views are gone from the set) but the memory instrument shows a leak at specialized WeakObject.init(object:).
  • quantumpotato
    quantumpotato over 6 years
    @Bill sometimes Xcode errors pop up briefly, then disappear and actually compile fine even with the red mark. (This happened to me as recently as yesterday)
  • KPM
    KPM over 6 years
    A struct would be better, as it would be kept on the stack instead of needing a heap fetch.
  • simonseyer
    simonseyer over 6 years
    I've made the experience that the pointer that is returned by UnsafeMutablePointer<T>(&object) can change randomly (same with withUnsafePointer). I now use a version backed by a NSHashTable: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d.
  • Ale Ravasio
    Ale Ravasio almost 6 years
    I loved this approach, and I think it's super clever. I made a class implementation using this strategy. Thank you!
  • Greg
    Greg over 5 years
    I get a compiler error with MyProtocol: class and NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' requires that 'MyProtocol' be a class type.
  • XmasRights
    XmasRights over 4 years
    Only thing to consider: the .count method for NSHashTable has a few issues with caching, such that making an object nil, and immediately calling count gives you the wrong value. Generally not a problem in the codebase, but unit tests do fail as a result.
  • meaning-matters
    meaning-matters over 4 years
    @simonseyer Please add this as an answer describing why you made it and how to use it; I'll vote for it!
  • simonseyer
    simonseyer over 4 years
    @meaning-matters I guess you can just upvote this reply stackoverflow.com/a/27108747. My code only provides a very thing wrapper around NSHashTable. You could even argue that just using it directly is better because it doesn't shadow what is used underneath.
  • Kaz Yoshikawa
    Kaz Yoshikawa over 4 years
    I realized that hashValue will change its value after deallocation. I updated WeakObjectSet code for recent Xcode and Swift. WeakObjectSet.swift - Swift 5 gist.github.com/codelynx/73919293f4166edd767f77a4cd178274
  • Dan Selig
    Dan Selig over 4 years
    Very nice. How do you remove an object from the weakset?
  • Dan Selig
    Dan Selig over 4 years
    let subs = persons.allobjects.filter { $0 !== subscriber } persons = WeakObjectSet<AnyObject>() persons?.addObjects(subs)
  • Kaz Yoshikawa
    Kaz Yoshikawa over 4 years
    There are some removing methods if you are referring following gist. gist.github.com/codelynx/73919293f4166edd767f77a4cd178274
  • Matic Oblak
    Matic Oblak about 4 years
    Not too sure about the let values = Array(array1.map({ $0() })) part. Since this is no longer an array of closures with weak references, values will be retained until this array is deallocated. If I am correct then it is important to note that you should never retain this array like self.items = Array(array1.map({ $0() })) as this beats the purpose.
  • Matic Oblak
    Matic Oblak about 4 years
    My problem with both options (and many other) is that these types of array are not usable with protocols. For instance this will not compile: protocol TP: class { } class TC { var a = WeakArray<TP>() var b = WeakObjectsArray<TP>() }
  • Vasily  Bodnarchuk
    Vasily Bodnarchuk about 4 years
    @MaticOblak what about using generics? protocol TP: class { } class TC<TYPE> where TYPE: TP { var a = WeakObjectsArray<TYPE>() // Use like regular array. With any objects var weakObjectsArray = [TYPE?]() }
  • Matic Oblak
    Matic Oblak about 4 years
    The idea is that this array can hold objects of different types which implement the same class protocol. By using a generic you lock it down to a single type. For instance imagine having a singleton that holds such array as delegates. Then you would have some number of view controllers that would like to use this functionality. You would expect to call MyManager.delegates.append(self). But if MyManager is locked to some generic type then this is not very usable.
  • malhal
    malhal about 4 years
    Huh just use for object in hashtable.allObjects
  • Vasily  Bodnarchuk
    Vasily Bodnarchuk about 4 years
    @MaticOblak ok. Try this: protocol TP: class { } class MyManager { typealias Delegate = AnyObject & TP static var delegates = [Delegate?]() } class A: TP { } class B: TP { } //MyManager.delegates.append(A()) //MyManager.delegates.append(B())
  • Matic Oblak
    Matic Oblak about 4 years
    You now lost the generic part with the array which is a bit important :) I have a feeling that this is just not doable. A limitation of Swift for now...
  • Vasily  Bodnarchuk
    Vasily Bodnarchuk about 4 years
    @MaticOblak why? Look at last sample. I added objects of different classes to MyManager.delegates. I thought you were trying to reach this, no? also you can use type Any & TP if needed.
  • Matic Oblak
    Matic Oblak about 4 years
    Different classes corresponding to same protocol, yes. But to have a such array that it would be defined as generic. So that another manager could use the same tool with another protocol. So the idea is to have a container (an array) of weak objects corresponding to protocol defined through a generic.
  • KellyHuberty
    KellyHuberty about 3 years
    Also make sure order doesn't matter. NSHashTable doesn't maintain order of its elements.
  • MH175
    MH175 about 3 years
    Very nice. I think you lose compactMap and would need filter { $0() != nil }
  • MH175
    MH175 about 3 years
    If you wanted more convenient/descriptive syntax you can also declare a typealias WeakArray<T> = [() -> T?]
  • Roman Filippov
    Roman Filippov almost 3 years
    careful with index in get function
  • John Montgomery
    John Montgomery almost 3 years
    That's odd, it's correct in my actual code so I have no idea how that error got into the answer.