Does deinit run when the app exits, and which objects never get one?

3.8.2 · L3 · Interview

Predict first

Which deinit lines print?

final class Global: Sendable { deinit { print("Global deinit") } }
final class Single: Sendable {
    static let shared = Single()
    deinit { print("Single deinit") }
}
final class Local: Sendable { deinit { print("Local deinit") } }
let g = Global()
_ = Single.shared
func f() { _ = Local() }
f()
print("end of main")
  • Local deinit, end of main, Global deinit, Single deinit
  • Local deinit, end of main
  • end of main only

Mental model. When the shop closes, the owner switches off the lights and walks out; nobody returns each item to its shelf. Things that must be put away have to be put away before closing time.

No. At process exit nothing is torn down: Swift does not release globals or static properties, so objects reachable from a top-level let or a static let shared never see deinit, and calling exit() skips even the cleanup of locals still on the stack (the check prints Local deinit because that object had been released earlier, but never Global deinit or Single deinit). On iOS the system usually does not exit your app cleanly either: it suspends it and later kills it, with no callback to your objects at all. A singleton is therefore immortal by design.

Consequences: never rely on deinit to persist data or flush a log or end a transaction. Save at lifecycle points instead (scenePhase moves to background, applicationDidEnterBackground, sceneDidDisconnect), write files atomically so a kill mid-write is harmless, and treat deinit as a place to free in-process resources only. For tests this matters too: a singleton keeps state between tests, so inject the dependency or add a reset hook. And if you see a “leak” of a singleton in Instruments, check whether it is reachable from a global before you chase it; reachable memory is not a leak (Q158).

Objects held by globals and statics are never released at exit; the local was released as soon as it was unused.

Common traps

  • Putting “save on quit” into deinit. It will not run for global or singleton owners, and the app is rarely quit in the desktop sense.
  • Expecting a singleton to be reset between unit tests. It lives for the whole test process.

They might follow up with

  • Does a static property ever deinit? Not in a normal process; you can set an optional static to nil to release it yourself.
  • What does the system do with your memory at exit? The kernel reclaims the whole address space at once.

Related questions

  • 3.8.1 What state is an object in while its deinit runs, and what happens if you let self escape from it?
  • 3.7.1 What is the difference between a leak, abandoned memory, fragmentation and a memory spike?
  • 3.7.5 What is memory footprint, and what gets an iOS app killed?
  • 6.6.1 What should an app do on a memory warning, and how reliable is the warning?

← What state is an object in while its deinit runs, and what happens if…   Which thread does deinit run on, and what does isolated deinit change… →