I ran into the weirdest thing, maybe someone has an explanation. Steps:
- Create a UIView A
- Create a weak reference to A
- Add A to the view hierarchy
- Remove A from the view hierarchy
- Set A to nil
- The weak reference still exists.
If you skip step 3 and 4 the weak reference becomes nil as expected.
Code to test:
TestView to check deinit
class TestView: UIView {
deinit {
print("deinit")
}
}
Unit test
class RetainTests: XCTestCase {
func testRetainFails() {
let holderView = UIView()
var element: TestView? = TestView()
holderView.addSubview(element!)
element?.removeFromSuperview()
weak var weakElement = element
XCTAssertNotNil(weakElement)
// after next line `weakElement` should be nil
element = nil
// console will print "deinit"
XCTAssertNil(weakElement) // fails!
}
func testRetainPasses() {
var element: TestView? = TestView()
weak var weakElement = element
XCTAssertNotNil(weakElement)
// after next line `weakElement` should be nil
element = nil
// console will print "deinit"
XCTAssertNil(weakElement)
}
}
Both tests will print out deinit on the console, but in the case of the failing test the weakElement still holds the reference if element was in a view hierarchy for any time, although element got successfully deallocated. How can that be?
(Edit: This is inside an app, NOT a playground)