How do I declare an array of weak references in Swift?
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]