• 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:

  1. You may have noticedNSPointerArrayStore onlyAnyObject, which means you can only store classes — structs and enumerations don’t work. You can store withclassKeyword agreement:
protocolMyProtocol: class{}Copy the code
  1. If you want to try it outNSPointerArrayI 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 earlierNSPointerArrayExtension 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 bevalueSet 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

filterReturns 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.