Weak reference collection

NOTE: This question involves CocoaTouch-Frameworks.

Assume you have the following setup:

Views won't be deinit-ed because there is still a strong pointer to them. To avoid that the red pointers should be weak. Swift uses always strong pointers in collections, I can't really change that for the view controllers subviews array. How ever, I could change this for my own collection. So the red drawn strong pointers should be weak.

ViewClass:

class SomeView: UIView {

    weak var someGroup: SomeGroup? {
        willSet {
            someGroup?.removeFromGroup(self)
            newValue?.addToGroup(self)
        }
    }

}

GroupClass:

class SomeGroup {

    var someViews: [SomeView] {
        return autoreleasepool { return weakReferencesSet.allObjects }
    }
    private var weakReferencesSet = NSHashTable<SomeView>.weakObjects()

    func addToGroup(_ someView: SomeView) {
        weakReferencesSet.add(someView)
    }

    func removeFromGroup(_ someView: SomeView) {
        weakReferencesSet.remove(someView)
    }

}

UnitTest:

    func testWeakReferences() {
        let group = SomeGroup()
        var sv1: SomeView! = SomeView()
        weak var weakRef = sv1

        XCTAssertEqual(group.someViews.count, 0)
        sv1.someGroup = group

        XCTAssert(group.someViews.contains(sv1))
        XCTAssertEqual(group.someViews.count, 1)
        XCTAssertNotNil(weakRef)

        sv1 = nil
        XCTAssertNil(weakRef)
        XCTAssertEqual(group.someViews.count, 0)
    }

Questions:

  1. Is there an alternative to NSHashTable? (Except creating a wrapper object and add that to a collection)
  2. Why is the autoreleasepool needed to successfully complete the unit test? I wouldn't expect it to make a difference here.

Is there an alternative to NSHashTable? (Except creating a wrapper
object and add that to a collection)

Those are the two common alternatives that I’m aware of.

Why is the autoreleasepool needed to successfully complete the unit
test?

It’s hard to say without looking at what ended up in the pool. You can work that out by calling _objc_autoreleasePoolPrint from LLDB. For example, with this code:

import Foundation

autoreleasepool {
    NSLog("Hello Cruel World!")
    for _ in 1...5 {
        NSLog("%@", NSObject())
    }
    NSLog("Goodbye Cruel World!")
}

if I set a breakpoint on line 8 I can do this:

… 09:21:04.811860+0000 xxst[37428:4058781] Hello Cruel World!
… 09:21:04.812532+0000 xxst[37428:4058781] <NSObject: 0x102861730>
… 09:21:04.812573+0000 xxst[37428:4058781] <NSObject: 0x102a00340>
… 09:21:04.812601+0000 xxst[37428:4058781] <NSObject: 0x102a016a0>
… 09:21:04.812631+0000 xxst[37428:4058781] <NSObject: 0x102a057a0>
… 09:21:04.812683+0000 xxst[37428:4058781] <NSObject: 0x102b00050>
(lldb) expr -l objc -- (void)_objc_autoreleasePoolPrint()
objc[37428]: ##############
objc[37428]: AUTORELEASE POOLS for thread 0x100dae340
objc[37428]: 6 releases pending.
objc[37428]: [0x103809000]  ................  PAGE  (hot) (cold)
objc[37428]: [0x103809038]  ################  POOL 0x103809038
objc[37428]: [0x103809040]       0x102861730  NSObject
objc[37428]: [0x103809048]       0x102a00340  NSObject
objc[37428]: [0x103809050]       0x102a016a0  NSObject
objc[37428]: [0x103809058]       0x102a057a0  NSObject
objc[37428]: [0x103809060]       0x102b00050  NSObject
objc[37428]: ##############

As you can see, each of the NSObject instances I created and printed with NSLog is shown in the autorelease pool dump.

Note I used -l objc so that the expression evaluates as Objective-C regardless of the language that LLDB thinks you’re in.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"

3 Likes