What is an app extension, how is it launched, and what limits do extensions run under?

19.1.1 · L3 · Interview

Predict first

A widget that loaded a large image framework is intermittently blank on real devices but fine in the simulator. What is the most likely cause?

// A: the simulator is faster
// B: the extension exceeds its small memory limit on device and is terminated by the system
// C: widgets cannot show images
  • A
  • B
  • C

Mental model. An extension is a pop-up kiosk inside someone else’s shopping centre. The centre (the host) decides when it opens and closes, gives it a tiny counter and a shutter that comes down without warning, and it cannot see the stockroom (your app’s memory) unless you leave goods in a shared locker.

An app extension is a separate executable, an .appex bundle inside your app, that the system or another app (the host) starts to provide a feature at an extension point: the share sheet, a widget, a keyboard, a notification service, a Safari content blocker. It is declared in its own Info.plist under NSExtension: the NSExtensionPointIdentifier says which point it extends (com.apple.share-services, com.apple.widgetkit-extension, com.apple.usernotifications.service…) and either a NSExtensionPrincipalClass (the class the system instantiates) or a storyboard. Since it is a different process, it shares nothing with your app by default: not memory, not singletons, not UserDefaults.standard, not the sandbox container, not the Keychain’s default access group. The system launches it on demand and on its own schedule: the user opens the share sheet and your share extension starts; a push arrives and the notification service extension starts; the widget gallery or Home Screen asks for a timeline; the keyboard is chosen. Your main app need not be running, and the extension cannot assume it is. The extension’s code is also bundled and signed together with the app and updates with it, but it can run an older or newer data format than the app that last wrote shared data, so data contracts matter.

Extensions run under hard limits, much tighter than an app. Memory: a small, point-specific ceiling (tens of megabytes; the exact numbers vary by extension type and OS version, and some, like widgets and notification service extensions, are very low); exceeding it makes the system terminate the process (a jetsam kill) with no warning and no crash dialog, which looks to users like “the widget is blank” or “the notification arrived unmodified”. Wall-clock time: a notification service extension has about 30 seconds (and a serviceExtensionTimeWillExpire callback just before), a widget timeline provider must return quickly, a share extension can be dismissed by the user, and most extensions are suspended or killed shortly after their work ends. No background freedom: they cannot start long-running tasks (use the containing app or a background URLSession with a shared container), many frameworks and APIs are unavailable or restricted (UIApplication.shared is not available, so use NSExtensionContext to open a URL), and capabilities such as the camera, location or network may need explicit entitlements and permissions that the extension must hold on its own. Design consequences: keep the extension tiny and fast; link only what you need (a heavy framework can exhaust memory before your code runs); put shared logic in a lean shared module; avoid singletons that assume app startup; handle being launched cold with no prior state; and test on a real device, with the Memory Debugger and Instruments attached to the extension process (Debug ▸ Attach to Process by PID or Name), since the simulator’s limits differ. When an extension is killed for memory, the fix is to reduce its footprint, not to catch an error.

Extension memory ceilings are low and enforced on device, so heavy decoding or frameworks can get the process killed, which shows as a blank widget.

Common traps

  • Assuming the main app’s singletons, caches or state exist in the extension. It is a separate process that starts cold; share through app-group storage.
  • Linking a large framework “just in case”. Load-time memory counts against the tiny budget and can get the extension killed before it runs.

They might follow up with

  • How does the system start an extension? On demand, when a host or the system uses its extension point, with the principal class from Info.plist.
  • What happens when an extension exceeds its memory limit? The system terminates it silently (a jetsam kill), so it must stay lean.

Related questions

  • 19.1.2 How do app groups, shared containers and keychain access groups let an app and its extensions share data, and…
  • 19.2.1 How do Notification Service and Notification Content extensions work: mutable content, the deadline…
  • 6.6.2 What is jetsam, and how does it differ from a crash?

How do app groups, shared containers and keychain access groups let… →