- Swift Arrays Holding Elements With Weak References
- Original author: Marco Santarossa
- The Nuggets translation Project
- Translator: zhangqippp
- Proofreader: ZhangRuixiang, Danny1451
Swift array that holds weak references to elements
In iOS development we often face the question “To use weak references or not to use weak references, that’s the question.” . Let’s look at how to use weak references in arrays.
An overview of the
In this article, I’ll talk about memory management but won’t explain it because it’s not the subject of this article.The official documentationIs a good starting point for learning about memory management. If you have any other questions, please leave a message and I will get back to you as soon as possible.
Array is the most used collection in Swift. It holds strong references to its elements by default. This default behavior is useful most of the time, but in some scenarios you may want to use weak references. So Apple has given us an alternative to Array: NSPointerArray, which holds weak references to its elements.
Before we dive into this class, let’s look at an example to see why we need to use it.
Why use weak references?
For example, we have a ViewManager that has two properties of type View. In its constructor, we add these views to an array inside the Drawer, which the Drawer uses to draw something inside its views. Finally, we have a destroyViews method to destroy both views:
class View {}class Drawer {
private let views: [View]
init(views: [View]) {
self.views = views
}
func draw() {
// draw something in views}}class ViewManager {
private var viewA: View? = View()
private var viewB: View? = View()
private vardrawer: Drawer init() { self.drawer = Drawer(views: [viewA!, viewB!] ) } func destroyViews() { viewA = nil viewB = nil } }Copy the code
However, the destroyViews method does not destroy these two views because the array inside the Drawer still has strong references to these views. We can avoid this problem by using NSPointerArray.
NSPointerArray
NSPointerArray is an alternative to Array, the main difference being that it does not store objects but rather objects’ Pointers (UnsafeMutableRawPointer).
This type of array can manage weak or strong references, depending on how it is initialized. It provides two static methods so that we can use different initialization methods:
let strongRefarray = NSPointerArray.strongObjects() // Maintains strong references
let weakRefarray = NSPointerArray.weakObjects() // Maintains weak referencesCopy the code
We need a weak reference array, so we use NSPointerArray. WeakObjects ().
Now we add a new object to the array:
class MyClass {}var array = NSPointerArray.weakObjects()
let obj = MyClass()
let pointer = Unmanaged.passUnretained(obj).toOpaque()
array.addPointer(pointer)Copy the code
If you find using Pointers like this annoying, you can use this extension I wrote to simplify NSPointerArray:
extension NSPointerArray {
func addObject(_ object: AnyObject?) {
guard let strongObject = object else { return }
letpointer = Unmanaged.passUnretained(strongObject).toOpaque() addPointer(pointer) } func insertObject(_ object: AnyObject? , at index: Int) { guard index < count,let strongObject = object else { return }
let pointer = Unmanaged.passUnretained(strongObject).toOpaque()
insertPointer(pointer, at: index)
}
func replaceObject(at index: Int, withObject object: AnyObject?) {
guard index < count, let strongObject = object else { return }
let pointer = Unmanaged.passUnretained(strongObject).toOpaque()
replacePointer(at: index, withPointer: pointer)
}
func object(at index: Int) -> AnyObject? {
guard index < count, let pointer = self.pointer(at: index) else { return nil }
return Unmanaged<AnyObject>.fromOpaque(pointer).takeUnretainedValue()
}
func removeObject(at index: Int) {
guard index < count else { return }
removePointer(at: index)
}
}Copy the code
With this extension class, you can replace the previous example with:
var array = NSPointerArray.weakObjects()
let obj = MyClass()
array.addObject(obj)Copy the code
If you want to clean up the array and set everything to nil, you can call the compact() method:
array.compact()Copy the code
At this point, we can replace the previous section “Why weak References?” The example in refactoring to the following code:
class View {}class Drawer {
private let views: NSPointerArray
init(views: NSPointerArray) {
self.views = views
}
func draw() {
// draw something in views}}class ViewManager {
private var viewA: View? = View()
private var viewB: View? = View()
private var drawer: Drawer
init() {
let array = NSPointerArray.weakObjects()
array.addObject(viewA)
array.addObject(viewB)
self.drawer = Drawer(views: array)
}
func destroyViews() {
viewA = nil
viewB = nil
}
}Copy the code
Note:
- You may have noticed
NSPointerArray
Store onlyAnyObject
, which means you can only store classes — structs and enumerations don’t work. You can store withclass
Keyword agreement:
protocolMyProtocol: class{}Copy the code
- If you want to try it out
NSPointerArray
I recommend not using Playground because you might get some weird behavior due to reference counting problems. A simple app is better.
An alternative to
NSPointerArray is useful for storing objects and keeping references weak, but it has one problem: it’s not type-safe.
“Non-type safe”, in this case, means that the compiler cannot fail to infer the type of the object implicit in NSPointerArray because it uses a pointer to an AnyObject. Therefore, when you get an object from an array, you need to convert it to the type you want:
if let firstObject=array.object(at:0)as? MyClass{// Cast to MyClass
print("The first object is a MyClass")}Copy the code
object(at:)
The method comes from what I showed you earlierNSPointerArray
Extension classes.
If we want to use a type-safe alternative to arrays, we can’t use NSPointerArray.
A possible solution is to create a new class WeakRef with a common weak attribute value: WeakRef
class WeakRef<T>whereT: AnyObject{ private(set)weakvarvalue:T? init(value:T?) { self.value=value } }Copy the code
private(set)
Method will bevalue
Set to read-only mode so that its value cannot be set outside the class.
Then, we can create a set of WeakRef objects and store your MyClass object in their value property:
var array=[WeakRef<MyClass>]()
let obj=MyClass()
let weakObj=WeakRef(value:obj)
array.append(weakObj)Copy the code
We now have a type-safe array that holds weak references internally to your MyClass object. The downside of this implementation is that we have to add a layer (WeakRef) in our code to wrap weak references in a type-safe way.
If you want to clean up an array and get rid of objects whose values are nil, you can use the following method:
func compact(){
array=array.filter{$0.value! =nil} }Copy the code
filter
Returns a new array whose elements meet the given criteria. You can be inThe documentFor more information.
Now, we can divide the question “Why weak references?” The example in the section is refactored into the following code:
class View {}class Drawer {
private let views: [WeakRef<View>]
init(views: [WeakRef<View>]) {
self.views = views
}
func draw() {
// draw something in views}}class ViewManager {
private var viewA: View? = View()
private var viewB: View? = View()
private var drawer: Drawer
init() {
var array = [WeakRef<View>]()
array.append(WeakRef<View>(value: viewA))
array.append(WeakRef<View>(value: viewB))
self.drawer = Drawer(views: array)
}
func destroyViews() {
viewA = nil
viewB = nil
}
}Copy the code
A more concise version using a type alias looks like this:
typealias WeakRefView = WeakRef<View>
class View {}class Drawer {
private let views: [WeakRefView]
init(views: [WeakRefView]) {
self.views = views
}
func draw() {
// draw something in views}}class ViewManager {
private var viewA: View? = View()
private var viewB: View? = View()
private var drawer: Drawer
init() {
var array = [WeakRefView]()
array.append(WeakRefView(value: viewA))
array.append(WeakRefView(value: viewB))
self.drawer = Drawer(views: array)
}
func destroyViews() {
viewA = nil
viewB = nil
}
}Copy the code
The Dictionary and Set
This article focuses on Array. If you need a dictionary-like alternative to NSPointerArray, you can refer to NSMapTable and the Set alternative to NSHashTable.
If you need a type-safe Dictionary/Set, you can do so by using a WeakRef object.
conclusion
You probably won’t use arrays with weak references very often, but that’s not a reason not to see how it works. Memory management is very important in iOS development and we should avoid memory leaks because iOS has no garbage collector. ¯ _ (ツ) _ / ¯
The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. Android, iOS, React, front end, back end, product, design, etc. Keep an eye on the Nuggets Translation project for more quality translations.