Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

People also ask

How do you make a weak reference in Swift?

Before you learn about strong and weak reference, make sure to understand how classes and objects work in Swift. Note: The declaration of a property is strong by default. To declare a weak reference we use the weak keyword.

How do you declare an array in Swift?

Array in swift is written as **Array < Element > **, where Element is the type of values the array is allowed to store. The type of the emptyArray variable is inferred to be [String] from the type of the initializer. The groceryList variable is declared as “an array of string values”, written as [String].

What is a weak reference Swift?

Weak References. A weak reference is a reference that doesn't keep a strong hold on the instance it refers to, and so doesn't stop ARC from disposing of the referenced instance. This behavior prevents the reference from becoming part of a strong reference cycle.

Are arrays passed by reference in Swift?

Classes are passed by reference. Array and Dictionary in Swift are implemented as structs. Array is not copied / passed by value in Swift - it has very different behavior in Swift compared to regular struct.


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.


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.


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() }

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.


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]

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>[]()