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()
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]
Related videos on Youtube
![Bill](https://i.stack.imgur.com/XbCBl.jpg?s=256&g=1)
Bill
Updated on July 14, 2022Comments
-
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 about 10 yearsWhat 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 about 10 yearswhy don't you use an NSPointerArray ?
-
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 about 10 yearsRight, 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 about 10 yearsHave you tried
Array<weak AnyObject>
? -
Mick MacCallum about 10 yearsJust as another option, NSHashTable exists. It's basically an NSSet that allows you to specify how it should reference the objects it contains.
-
nielsbot about 10 years@Sulthan yes, you will have to use an indirect access... like, if (nil) { remove handle from array }
-
Chris Conover over 9 yearsThis 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 about 10 yearsHow do you remove the wrapper objects from the array when their value is deallocated?
-
Bill about 10 yearsI 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 about 10 yearsYes, it crashed the compiler.
-
Bill about 10 yearsHas to be a
class
to useweak
vars -
Bill about 10 yearsIn my experience, it's trivial to crash the compiler when you start using weak vars.
-
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 defineclass WeakArray : Array { ... }
-
Joshua Weinberg about 10 yearsSays 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 about 10 yearsSorry. I could have sworn I just got a compiler message that said "Cannot use weak variables in structs". You're correct - that compiles.
-
Sulthan about 10 years@GoZoner What if you need
count
? -
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 about 10 yearsThis is correct. It only crashes in Playground but works fine in a normal project
-
jasamer over 9 yearsIn 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 over 9 yearsstrange, but doesn't work with structs anymore. Says
EXC_BAD_ACCESS
for me. With class works just fine -
David H about 9 yearsThe question is how to create an array (or say Set) of weak objects.
-
Bartłomiej Semańczyk almost 9 yearsIt doesn't work. The controller is still not deinitialised because of this.
-
GoZoner almost 9 yearsPlease post your problem code in a new question; no reason to ding my answer when it might be your code!
-
onmyway133 over 8 years@JoshuaWeinberg what if Foo is a protocol?
-
Lukáš Kubánek over 8 yearsWhat about using
flatMap
instead offilter
&map
? -
Ramis over 8 yearsBest answer and do not waist time for wrappers!
-
Aaron Brager over 8 yearsThis is clever, but like GoZoner's answer, this doesn't work with types that are
Any
but notAnyObject
, such as protocols. -
Brian Stewart over 8 yearsIn Xcode 7.2.1 / Swift 2 this causes a compiler error: Using as a concrete type conforming to protocol AnyObject is not supported.
-
GoZoner over 8 yearsMore info please. In a 7.2.1 Playground, the provided code compiles/executes as expected.
-
olejnjak about 8 years@onmyway133 AFAIK if protocol is declared to be implemented only by classes it would work.
protocol Protocol : class { ... }
-
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 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 about 8 yearsWeak-ness only really applies to reference types - hence
AnyObject
or:class
types. You can observe this trying to declareweak var value : <type>
with different types - the Swift compiler will limit your choices to 'class types'. -
Aaron Brager almost 8 years@SteveWilford But a protocol can be implemented by a class, which would make it a reference type
-
David Goodine almost 8 yearsStructs 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 almost 8 yearsyour example crashes with swift 3.0 beta :(
-
jboi almost 8 yearsWith 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 almost 8 yearsa protocol can extend class and then you can use it as weak (e.g. protocol MyProtocol: class)
-
eonil almost 8 years@DavidH I updated my answer to answer the question. I hope this helps.
-
gokeji over 6 yearsI've adopted this solution for my project. Great job! Just one suggestion, this solution doesn't seem to remove
nil
values from the internalSet
. So I've added areap()
function mentioned in the top answer, and made sure to callreap()
every time theWeakObjectSet
is accessed. -
gokeji over 6 yearsHmm 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 over 6 yearsI 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 over 6 yearsThanks 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 over 6 yearsThank 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 asUITableViews
,UICollectionViews
. These components implement a class protocolA
, 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 atspecialized WeakObject.init(object:)
. -
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 over 6 yearsA struct would be better, as it would be kept on the stack instead of needing a heap fetch.
-
simonseyer over 6 yearsI've made the experience that the pointer that is returned by
UnsafeMutablePointer<T>(&object)
can change randomly (same withwithUnsafePointer
). I now use a version backed by aNSHashTable
: gist.github.com/simonseyer/cf73e733355501405982042f760d2a7d. -
Ale Ravasio almost 6 yearsI loved this approach, and I think it's super clever. I made a class implementation using this strategy. Thank you!
-
Greg over 5 yearsI get a compiler error with
MyProtocol: class
andNSHashTable<MyProtocol>.weakObjects()
. "'NSHashTable' requires that 'MyProtocol' be a class type. -
XmasRights over 4 yearsOnly thing to consider: the
.count
method forNSHashTable
has a few issues with caching, such that making an object nil, and immediately callingcount
gives you the wrong value. Generally not a problem in the codebase, but unit tests do fail as a result. -
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 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 over 4 yearsI 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 over 4 yearsVery nice. How do you remove an object from the weakset?
-
Dan Selig over 4 years
let subs = persons.allobjects.filter { $0 !== subscriber } persons = WeakObjectSet<AnyObject>() persons?.addObjects(subs)
-
Kaz Yoshikawa over 4 yearsThere are some removing methods if you are referring following gist. gist.github.com/codelynx/73919293f4166edd767f77a4cd178274
-
Matic Oblak about 4 yearsNot 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 likeself.items = Array(array1.map({ $0() }))
as this beats the purpose. -
Matic Oblak about 4 yearsMy 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 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 about 4 yearsThe 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 callMyManager.delegates.append(self)
. But ifMyManager
is locked to some generic type then this is not very usable. -
malhal about 4 yearsHuh just use for object in hashtable.allObjects
-
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 about 4 yearsYou 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 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 about 4 yearsDifferent 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 about 3 yearsAlso make sure order doesn't matter. NSHashTable doesn't maintain order of its elements.
-
MH175 about 3 yearsVery nice. I think you lose compactMap and would need
filter { $0() != nil }
-
MH175 about 3 yearsIf you wanted more convenient/descriptive syntax you can also declare a typealias
WeakArray<T> = [() -> T?]
-
Roman Filippov almost 3 yearscareful with index in
get
function -
John Montgomery almost 3 yearsThat's odd, it's correct in my actual code so I have no idea how that error got into the answer.