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

19

235

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.

Microsporophyll answered 9/6, 2014 at 19:34 Comment(8)
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)Dressage
why don't you use an NSPointerArray ?Oaken
@Dressage 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.Assegai
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.Microsporophyll
Have you tried Array<weak AnyObject>?Indirection
Just as another option, NSHashTable exists. It's basically an NSSet that allows you to specify how it should reference the objects it contains.Woolf
@Assegai yes, you will have to use an indirect access... like, if (nil) { remove handle from array }Dressage
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.Tabithatablature
M
191

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.

Montero answered 9/6, 2014 at 20:7 Comment(17)
How do you remove the wrapper objects from the array when their value is deallocated?Assegai
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.Microsporophyll
Yes, it crashed the compiler.Montero
In my experience, it's trivial to crash the compiler when you start using weak vars.Microsporophyll
@Assegai 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 { ... }Montero
@Montero What if you need count?Assegai
@Montero 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. :-)Microsporophyll
This is correct. It only crashes in Playground but works fine in a normal projectSylvie
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.Benzol
It doesn't work. The controller is still not deinitialised because of this.Houk
Please post your problem code in a new question; no reason to ding my answer when it might be your code!Montero
In Xcode 7.2.1 / Swift 2 this causes a compiler error: Using as a concrete type conforming to protocol AnyObject is not supported.Acoustics
More info please. In a 7.2.1 Playground, the provided code compiles/executes as expected.Montero
@EdGamble The provided code works as it is, but fails if you replace the class Stuff by a protocol; see this related questionSalpingotomy
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'.Montero
your example crashes with swift 3.0 beta :(Largescale
A struct would be better, as it would be kept on the stack instead of needing a heap fetch.Embowel
S
87

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.

Selfconceit answered 24/11, 2014 at 15:51 Comment(7)
Best answer and do not waist time for wrappers!Lanielanier
This is clever, but like GoZoner's answer, this doesn't work with types that are Any but not AnyObject, such as protocols.Apology
@SteveWilford But a protocol can be implemented by a class, which would make it a reference typeApology
a protocol can extend class and then you can use it as weak (e.g. protocol MyProtocol: class)Jonellejones
I get a compiler error with MyProtocol: class and NSHashTable<MyProtocol>.weakObjects(). "'NSHashTable' requires that 'MyProtocol' be a class type.Danaus
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.Oram
Also make sure order doesn't matter. NSHashTable doesn't maintain order of its elements.Hauger
E
48

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() }
Ellsworth answered 16/3, 2020 at 14:38 Comment(2)
Very nice. I think you lose compactMap and would need filter { $0() != nil }Corymb
If you wanted more convenient/descriptive syntax you can also declare a typealias WeakArray<T> = [() -> T?]Corymb
I
17

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.

Impersonality answered 23/3, 2016 at 16:53 Comment(12)
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.Insurmountable
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 anymoreInsurmountable
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.Impersonality
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.Insurmountable
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:).Shivers
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.Overhand
@Overhand Please add this as an answer describing why you made it and how to use it; I'll vote for it!Alonsoalonzo
@Alonsoalonzo 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.Overhand
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/73919293f4166edd767f77a4cd178274Impersonality
Very nice. How do you remove an object from the weakset?Christy
let subs = persons.allobjects.filter { $0 !== subscriber } persons = WeakObjectSet<AnyObject>() persons?.addObjects(subs)Christy
There are some removing methods if you are referring following gist. gist.github.com/codelynx/73919293f4166edd767f77a4cd178274Impersonality
I
14

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]
Irrational answered 10/6, 2014 at 8:32 Comment(2)
strange, but doesn't work with structs anymore. Says EXC_BAD_ACCESS for me. With class works just fineBodycheck
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.Chamber
E
11

How about functional style wrapper?

class Class1 {}

func captureWeakly<T> (_ target:T) -> (() -> T?) where T: AnyObject {
    return { [weak target] in
        return target
    }
}

let obj1 = Class1()
let obj2 = Class1()
let obj3 = Class1()
let captured1 = captureWeakly(obj1)
let captured2 = captureWeakly(obj2)
let captured3 = captureWeakly(obj3)

Just call returned closure to check the target is still alive.

let isAlive = captured1() != nil
let theValue = captured1()!

And you can store this closures into an array.

let array1 = Array<() -> (Class1?)>([captured1, captured2, captured3])

And you can retrieve the weakly captured values by mapping calling the closures.

let values = Array(array1.map({ $0() }))

Actually, you don't need a function to make a closure. Just capture an object directly.

let captured3 = { [weak obj3] in return obj3 }
Elson answered 2/11, 2014 at 8:22 Comment(5)
The question is how to create an array (or say Set) of weak objects.Bombay
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.Dapple
@DavidH I updated my answer to answer the question. I hope this helps.Elson
I loved this approach, and I think it's super clever. I made a class implementation using this strategy. Thank you!Stormystorting
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.Metamorphic
F
10

You can do this by creating a wrapper object to hold a weak pointer.

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

And then using these in the array

var weakThings = WeakThing<Foo>[]()
Ferrick answered 9/6, 2014 at 20:9 Comment(7)
Has to be a class to use weak varsMicrosporophyll
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 referenceFerrick
Sorry. I could have sworn I just got a compiler message that said "Cannot use weak variables in structs". You're correct - that compiles.Microsporophyll
@JoshuaWeinberg what if Foo is a protocol?Barneybarnhart
@Barneybarnhart AFAIK if protocol is declared to be implemented only by classes it would work. protocol Protocol : class { ... }Factoring
@Barneybarnhart 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.Salpingotomy
@Microsporophyll 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)Hampden
Q
9

Details

  • Swift 5.1, Xcode 11.3.1

Solution

struct WeakObject<Object: AnyObject> { weak var object: Object? }

Option 1

@propertyWrapper
struct WeakElements<Collect, Element> where Collect: RangeReplaceableCollection, Collect.Element == Optional<Element>, Element: AnyObject {
    private var weakObjects = [WeakObject<Element>]()

    init(wrappedValue value: Collect) { save(collection: value) }

    private mutating func save(collection: Collect) {
        weakObjects = collection.map { WeakObject(object: $0) }
    }

    var wrappedValue: Collect {
        get { Collect(weakObjects.map { $0.object }) }
        set (newValues) { save(collection: newValues) }
    }
}

Option 1 usage

class Class1 { // or struct
    @WeakElements var weakObjectsArray = [UIView?]() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

Option 2

struct WeakObjectsArray<Object> where Object: AnyObject {
    private var weakObjects = [WeakObject<Object>]()
}

extension WeakObjectsArray {
    typealias SubSequence = WeakObjectsArray<Object>
    typealias Element = Optional<Object>
    typealias Index = Int
    var startIndex: Index { weakObjects.startIndex }
    var endIndex: Index { weakObjects.endIndex }
    func index(after i: Index) -> Index { weakObjects.index(after: i) }
    subscript(position: Index) -> Element {
        get { weakObjects[position].object }
        set (newValue) { weakObjects[position] = WeakObject(object: newValue) }
    }
    var count: Int { return weakObjects.count }
    var isEmpty: Bool { return weakObjects.isEmpty }
}

extension WeakObjectsArray: RangeReplaceableCollection {
    mutating func replaceSubrange<C : Collection>( _ subrange: Range<Index>, with newElements: C) where Element == C.Element {
        weakObjects.replaceSubrange(subrange, with: newElements.map { WeakObject(object: $0) })
    }
}

Option 2 usage

class Class2 { // or struct
    var weakObjectsArray = WeakObjectsArray<UIView>() // Use like regular array. With any objects

    func test() {
        weakObjectsArray.append(UIView())
        weakObjectsArray.forEach { print($0) }
    }
}

Full sample

do not forget to paste solution code

import UIKit

class ViewController: UIViewController {

    @WeakElements var weakObjectsArray = [UIView?]()
    //var weakObjectsArray = WeakObjectsArray<UIView>()

    override func viewDidLoad() {
        super.viewDidLoad()
        addSubviews()
    }

    private func printArray(title: String) {
        DispatchQueue.main.async {
            print("=============================\n\(title)\ncount: \(self.weakObjectsArray.count)")
            self.weakObjectsArray.enumerated().forEach { print("\($0) \(String(describing: $1))") }
        }
    }
}

extension ViewController {

    private func createRandomRectangleAndAdd(to parentView: UIView) -> UIView {
        let view = UIView(frame: CGRect(x: Int.random(in: 0...200),
                                        y: Int.random(in: 60...200),
                                        width: Int.random(in: 0...200),
                                        height: Int.random(in: 0...200)))
        let color = UIColor(red: CGFloat.random(in: 0...255)/255,
                            green: CGFloat.random(in: 0...255)/255,
                            blue: CGFloat.random(in: 0...255)/255,
                            alpha: 1)
        view.backgroundColor = color
        parentView.addSubview(view)
        return view
    }

    private func addSubviews() {
        (0...1).forEach { _ in addView() }
        addButtons()
    }

    private func createButton(title: String, frame: CGRect, action: Selector) -> UIButton {
        let button = UIButton(frame: frame)
        button.setTitle(title, for: .normal)
        button.addTarget(self, action: action, for: .touchUpInside)
        button.setTitleColor(.blue, for: .normal)
        return button
    }

    private func addButtons() {
        view.addSubview(createButton(title: "Add",
                                     frame: CGRect(x: 10, y: 20, width: 40, height: 40),
                                     action: #selector(addView)))

        view.addSubview(createButton(title: "Delete",
                                     frame: CGRect(x: 60, y: 20, width: 60, height: 40),
                                     action: #selector(deleteView)))

        view.addSubview(createButton(title: "Remove nils",
                                     frame: CGRect(x: 120, y: 20, width: 100, height: 40),
                                     action: #selector(removeNils)))
    }

    @objc func deleteView() {
        view.subviews.first { view -> Bool in return !(view is UIButton) }?
            .removeFromSuperview()

        printArray(title: "First view deleted")
    }

    @objc func addView() {
        weakObjectsArray.append(createRandomRectangleAndAdd(to: view))
        printArray(title: "View addded")
    }

    @objc func removeNils() {
        weakObjectsArray = weakObjectsArray.filter { $0 != nil }
        printArray(title: "Remove all nil elements in weakArray")
    }
}
Quintus answered 23/11, 2017 at 20:50 Comment(7)
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>() }Metamorphic
@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?]() }Quintus
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.Metamorphic
@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())Quintus
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...Metamorphic
@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.Quintus
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.Metamorphic
H
8

I had the same idea to create weak container with generics.
As result I created wrapper for NSHashTable:

class WeakSet<ObjectType>: SequenceType {

    var count: Int {
        return weakStorage.count
    }

    private let weakStorage = NSHashTable.weakObjectsHashTable()

    func addObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.addObject(object as? AnyObject)
    }

    func removeObject(object: ObjectType) {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        weakStorage.removeObject(object as? AnyObject)
    }

    func removeAllObjects() {
        weakStorage.removeAllObjects()
    }

    func containsObject(object: ObjectType) -> Bool {
        guard object is AnyObject else { fatalError("Object (\(object)) should be subclass of AnyObject") }
        return weakStorage.containsObject(object as? AnyObject)
    }

    func generate() -> AnyGenerator<ObjectType> {
        let enumerator = weakStorage.objectEnumerator()
        return anyGenerator {
            return enumerator.nextObject() as! ObjectType?
        }
    }
}

Usage:

protocol MyDelegate : AnyObject {
    func doWork()
}

class MyClass: AnyObject, MyDelegate {
    fun doWork() {
        // Do delegated work.
    }
}

var delegates = WeakSet<MyDelegate>()
delegates.addObject(MyClass())

for delegate in delegates {
    delegate.doWork()
}

It's not the best solution, because WeakSet can be initialized with any type, and if this type doesn't conform to AnyObject protocol then app will crash with detailed reason. But I don't see any better solution right now.

Original solution was to define WeakSet in this way:

class WeakSet<ObjectType: AnyObject>: SequenceType {}

But in this case WeakSet can't be initialized with protocol:

protocol MyDelegate : AnyObject {
    func doWork()
}

let weakSet = WeakSet<MyDelegate>()

Currently above code can't be compiled (Swift 2.1, Xcode 7.1).
That's why I dropped conforming to AnyObject and added additional guards with fatalError() assertions.

Hardpan answered 14/11, 2015 at 5:44 Comment(1)
Huh just use for object in hashtable.allObjectsButyrate
R
5

Since NSPointerArray already handles most of this automatically, I solved the problem by making a type-safe wrapper for it, which avoids a lot of the boilerplate in other answers:

class WeakArray<T: AnyObject> {
    private let pointers = NSPointerArray.weakObjects()
    
    init (_ elements: T...) {
        elements.forEach{self.pointers.addPointer(Unmanaged.passUnretained($0).toOpaque())}
    }
    
    func get (_ index: Int) -> T? {
        if index < self.pointers.count, let pointer = self.pointers.pointer(at: index) {
            return Unmanaged<T>.fromOpaque(pointer).takeUnretainedValue()
        } else {
            return nil
        }
    }
    func append (_ element: T) {
        self.pointers.addPointer(Unmanaged.passUnretained(element).toOpaque())
    }
    func forEach (_ callback: (T) -> ()) {
        for i in 0..<self.pointers.count {
            if let element = self.get(i) {
                callback(element)
            }
        }
    }
    // implement other functionality as needed
}

Example usage:

class Foo {}
var foo: Foo? = Foo()
let array = WeakArray(foo!)
print(array.get(0)) // Optional(Foo)
foo = nil
DispatchQueue.main.async{print(array.get(0))} // nil

It's more work up front, but the usage in the rest of your code is much cleaner IMO. If you want to make it more array-like, you can even implement subscripting, make it a SequenceType, etc. (but my project only needs append and forEach so I don't have the exact code on hand).

Ryeland answered 14/9, 2018 at 18:13 Comment(2)
careful with index in get functionFrohman
That's odd, it's correct in my actual code so I have no idea how that error got into the answer.Ryeland
T
4

The existing example of the WeakContainer is helpful, but it doesn't really help one use weak references in existing swift containers such as Lists and Dictionaries.

If you want to use List methods such as contains, then the WeakContainer will need to implement Equatable. So I added the code to allow the WeakContainer to be equatable.

In case you wanted to use the WeakContainer in dictionaries, I also made it hashable so it can be used as dictionary keys.

I also renamed it to WeakObject to stress that this is only for class types and to differentiate it from the WeakContainer examples:

struct WeakObject<TYPE where TYPE:AnyObject> : Equatable, Hashable
{
    weak var _value : TYPE?
    let _originalHashValue : Int

    init (value: TYPE)
    {
        _value = value

        // We keep around the original hash value so that we can return it to represent this
        // object even if the value became Nil out from under us because the object went away.
        _originalHashValue = ObjectIdentifier(value).hashValue
    }

    var value : TYPE?
    {
        return _value
    }

    var hashValue: Int
    {
        return _originalHashValue
    }
}

func ==<T>(lhs: WeakObject<T>, rhs: WeakObject<T>) -> Bool
{
    if lhs.value == nil  &&  rhs.value == nil {
        return true
    }
    else if lhs.value == nil  ||  rhs.value == nil {
        return false
    }

    // If the objects are the same, then we are good to go
    return lhs.value! === rhs.value!
}

This allows you to do some cool stuff like use a Dictionary of weak references:

private var m_observerDict : Dictionary<WeakObject<AnyObject>,FLObservationBlock> = Dictionary()

func addObserver( observer:AnyObject, block:FLObservationBlock )
{
    let weakObserver = WeakObject(value:observer)
    m_observerDict[weakObserver] = block
}


func removeObserver( observer:AnyObject )
{
    let weakObserver = WeakObject(value:observer)
    m_observerDict.removeValueForKey(weakObserver)
}
Toluol answered 4/10, 2015 at 21:29 Comment(0)
M
3

Here's how to make @GoZoner's great answer conform to Hashable, so it can be indexed in Container objects like: Set, Dictionary, Array, etc.

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

    var hashValue : Int {
       // ObjectIdentifier creates a unique hashvalue for objects.
       return ObjectIdentifier(self.value).hashValue
    }
}

// Need to override so we can conform to Equitable.
private func == <T>(lhs: Weak<T>, rhs: Weak<T>) -> Bool {
    return lhs.hashValue == rhs.hashValue
}
Motorman answered 22/11, 2015 at 23:24 Comment(0)
P
2

You could create wrapper around Array. Or use this library https://github.com/NickRybalko/WeakPointerArray let array = WeakPointerArray<AnyObject>() It is type safe.

Polysyllabic answered 19/7, 2017 at 14:3 Comment(0)
P
2

This is a type safe collection that holds containers of weak objects. It also auto removes nil the containers/wrappers when it's accessed.

Example:

protocol SomeDelegate: class {
    func doSomething()
}

class SomeViewController: UIViewController {
    var delegates: WeakCollection<SomeDelegate> = []

    func someFunction(delegate: SomeDelegate) {
        delegates.append(delegate)
    }

    func runDelegates() {
        delegates.forEach { $0.doSomething() }
    }
}

The custom collection https://gist.github.com/djk12587/46d85017fb3fad6946046925f36cefdc

import Foundation

/**
 Creates an array of weak reference objects.
 - Important:
    Because this is an array of weak objects, the objects in the array can be removed at any time.
    The collection itself will handle removing nil objects (garbage collection) via the private function cleanUpNilContainers()
 */

class WeakCollection<T>: RangeReplaceableCollection, ExpressibleByArrayLiteral {
    typealias Index = Int
    typealias Element = T
    typealias Iterator = IndexingIterator<[Element]>

    private var weakContainers: [WeakReferenceContainer]

    required convenience init(arrayLiteral: Element...) {
        self.init()
        self.weakContainers = WeakCollection.createWeakContainers(from: arrayLiteral)
    }

    required init() {
        weakContainers = []
    }

    required init<S>(_ elements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers = WeakCollection.createWeakContainers(from: elements)
    }

    static private func createWeakContainers<S>(from weakCollection: S) -> [WeakReferenceContainer] where S: Sequence,
        WeakCollection.Element == S.Element {
            return weakCollection.compactMap { WeakReferenceContainer(value: $0 as AnyObject) }
    }

    func append<S>(contentsOf newElements: S) where S: Sequence, WeakCollection.Element == S.Element {
        self.weakContainers.append(contentsOf: WeakCollection.createWeakContainers(from: newElements))
    }

    var startIndex: Index {
        return references.startIndex
    }

    var endIndex: Index {
        return references.endIndex
    }

    func replaceSubrange<C, R>(_ subrange: R, with newElements: C) where
        C: Collection, R: RangeExpression, WeakCollection.Element == C.Element, WeakCollection.Index == R.Bound {
            weakContainers.replaceSubrange(subrange, with: WeakCollection.createWeakContainers(from: newElements))
    }

    func index(after i: Int) -> Int {
        return references.index(after: i)
    }

    func makeIterator() -> IndexingIterator<[Element]> {
        return references.makeIterator()
    }

    subscript(index: Int) -> Element {
        get {
            return references[index]
        }
        set {
            weakContainers[index] = WeakReferenceContainer(value: newValue as AnyObject)
        }
    }
}

extension WeakCollection {
    private class WeakReferenceContainer {
        private(set) weak var value: AnyObject?

        init(value: AnyObject?) {
            self.value = value
        }
    }

    private func cleanUpNilContainers() {
        weakContainers = weakContainers.compactMap { $0.value == nil ? nil : $0 }
    }

    private var references: [Element] {
        cleanUpNilContainers()
        return weakContainers.compactMap { $0.value as? T }
    }
}
Plaza answered 22/8, 2019 at 4:39 Comment(0)
F
2

In many cases it is more clean to return a cancellable. This allows the call-site to decide when to destroy the value explicitly (as well as implicitly by descoping):


public protocol Cancellable {
    func cancel()
}

private struct MyValue: Identifiable {
    let id: String
    // ...
}

private class CancellationHandler: Cancellable {
    let handler: () -> ()
    init(handler: @escaping () -> ()) { self.handler = handler }
    func cancel() { handler() }
    deinit { handler() }
}

public class Container {
    private var array = [MyType]()

    public func add() -> Cancellable {
        let value = MyValue(...)
        array.append(value)
        return CancellationHandler {
            array.removeFirst(where: { $0.id == value.id })
        }
    }
}

let cancellable = container.add()

// Both cancellable.cancel() and the cancellable descoping 
// will call the `cancel` function, removing the value from array.
Favoritism answered 20/1, 2022 at 18:9 Comment(0)
P
1

Other answers have covered the generics angle. Thought I'd share some simple code covering the nil angle.

I wanted a static array (read occasionally) of all the Labels that currently exist in the app, but didn't want to see nil's where the old ones used to be.

Nothing fancy, this is my code...

public struct WeakLabel {
    public weak var label : Label?
    public init(_ label: Label?) {
        self.label = label
    }
}

public class Label : UILabel {
    static var _allLabels = [WeakLabel]()
    public static var allLabels:[WeakLabel] {
        get {
            _allLabels = _allLabels.filter{$0.label != nil}
            return _allLabels.filter{$0.label != nil}.map{$0.label!}
        }
    }
    public required init?(coder: NSCoder) {
        super.init(coder: coder)
        Label._allLabels.append(WeakLabel(self))
    }
    public override init(frame: CGRect) {
        super.init(frame: frame)
        Label._allLabels.append(WeakLabel(self))
    }
}
Persephone answered 27/9, 2015 at 3:29 Comment(1)
What about using flatMap instead of filter & map ?Colquitt
V
1

Yet another solution to the same problem... the focus of this one is on storing a weak reference to an object but allowing you to store a struct too.

[I'm not sure how useful it is, but it did take a while to get the syntax right]

class WeakWrapper : Equatable {
    var valueAny : Any?
    weak var value : AnyObject?

    init(value: Any) {
        if let valueObj = value as? AnyObject {
            self.value = valueObj
        } else {
            self.valueAny = value
        }
    }

    func recall() -> Any? {
        if let value = value {
            return value
        } else if let value = valueAny {
            return value
        }
        return nil
    }
}


func ==(lhs: WeakWrapper, rhs: WeakWrapper) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}



class Stuff {}
var weakArray : [WeakWrapper] = [WeakWrapper(value: Stuff()), WeakWrapper(value: CGRectZero)]

extension Array where Element : WeakWrapper  {

    mutating func removeObject(object: Element) {
        if let index = self.indexOf(object) {
            self.removeAtIndex(index)
        }
    }

    mutating func compress() {
        for obj in self {
            if obj.recall() == nil {
                self.removeObject(obj)
            }
        }
    }


}

weakArray[0].recall()
weakArray[1].recall() == nil
weakArray.compress()
weakArray.count
Verso answered 18/11, 2015 at 22:2 Comment(0)
S
0

I based this on @Eonil 's work, since I loved the closure weak-bind strategy, but I did not want to use a function operator for a variable, since it felt extremely counter intuitive

What I did, instead, is as follows:

class Weak<T> where T: AnyObject {
    fileprivate var storedWeakReference: ()->T? = { return nil }

    var value: T? {
        get {
            return storedWeakReference()
        }
    }

    init(_ object: T) {
        self.storedWeakReference = storeWeakReference(object)
    }

    fileprivate func storeWeakReference<T> (_ target:T) -> ()->T? where T: AnyObject {
        return { [weak target] in
            return target
        }
    }
}

This way you can do something such as:

var a: UIViewController? = UIViewController()
let b = Weak(a)
print(a) //prints Optional(<UIViewController: 0xSomeAddress>)
print(b.value) //prints Optional(<UIViewController: 0xSomeAddress>)
a = nil
print(a) //prints nil
print(b.value) //prints nil
Stormystorting answered 16/8, 2018 at 21:36 Comment(0)
F
-1
final class WeakReference<T> {
  weak var value: T?

  init(_ value: T?) {
    self.value = value
  }
}

class MyClass {
  // using UIViewController as an example but can be any class
  var arrayOfWeak: [WeakReference<UIViewController>] = []
  
  func addItem(_ item: UIViewController) { 
    arrayOfWeak.append(WeakReference(item))
  }

  func removeItem(_ item: UIViewController) {
    arrayOfWeak.removeAll(where: { $0.value == item })
  }
}
Featherhead answered 12/4, 2024 at 18:16 Comment(3)
Where are the weak references in your code? And shouldn’t your remove method use === instead of ==?Emotion
in the WeakReference class, the value is weak. and no, it shouldn’t be === but you certainly can make it that if you need to. “Should”? No. “Can”? Yes.Featherhead
Yes, your edit now makes it weak as it should be.Emotion

© 2022 - 2025 — McMap. All rights reserved.