Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

130
Visualizações
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 Respostas
Responde à pergunta

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 Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda