Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

126
Views
Weak view reference won't get deallocated after temporarily added to view hierarchy

I ran into the weirdest thing, maybe someone has an explanation. Steps:

  1. Create a UIView A
  2. Create a weak reference to A
  3. Add A to the view hierarchy
  4. Remove A from the view hierarchy
  5. Set A to nil
  6. 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)

over 4 years ago · Santiago Trujillo
1 answers
Answer question

0

There's an autorelease attached to TestView when you add and remove it to the superview. If you make sure to drain the autorelease pool, then this test behaves as you expect:

autoreleasepool {
    holderView.addSubview(element!)
    element?.removeFromSuperview()
}

That said, you cannot rely on this behavior. There is no promise about when an object will be destroyed. You only know it will be after you release your last strong reference to it. There may be additional retains or autoreleases on the object. And there's no promise that deinit will ever be called, so definitely make sure not to put any mandatory logic there. It should only perform memory cleanup. (Both Mac and iOS, for slightly different reasons, never call deinit during program termination.)

In your failing test case,deinit is printed after the assertion fails. You can demonstrate this by placing breakpoints on the failing assertion and the print. This is because the autorelease pool is drained at the end of the test case.

The underlying lesson is that it is very common for balanced retain/autorelease calls to be be placed on an object that is passed to ObjC code (and UIKit is heavily ObjC). You should generally not expect UIKit objects to be destroyed before the end of the current event loop. Don't rely on that (it's not promised), but it's often true.

over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!