# Share Extensions in Practice – Part 5: Memory-Safe SwiftUI in Extensions

Table of Contents

This is part 5 of a 5-part series on building robust iOS Share Extensions:

Apple explicitly notes that app extensions have tighter memory budgets than foreground apps and may be terminated aggressively for responsiveness (source).

In my case, this showed up as a production issue: a user reported that opening the Share Extension instantly closed it. After profiling, the root cause was memory pressure from loading too much API data during extension startup.

What To Watch For In Real AppsLink to heading

Most extension memory issues are data-path issues, not UI-style issues.

Common pitfalls:

  • Fetching large payloads when the extension opens
  • Loading full backend models although the screen only needs a subset
  • Missing fetch limits for searchable lists

My Solution: Core Data FirstLink to heading

I could not reduce the backend dataset itself, so I changed where the extension reads from:

  • Main app refreshes labels at app start
  • Labels are persisted to Core Data
  • Share Extension reads labels from Core Data (not API)
  • Main app and share extension using the same view with data from Core Data

This removed the startup spike and gave both screens a faster first load.

let swiftUIView = ShareBookmarkView(viewModel: viewModel)
.environment(\.managedObjectContext, CoreDataManager.shared.context)
let hostingController = UIHostingController(rootView: AnyView(swiftUIView))

When we create the SwiftUI view, we inject the shared Core Data context as an environment value. This allows the View to use @FetchRequest and other Core Data features without needing to manage the context directly in the extension.

CoreDataManager.shared.context is a singleton that provides access to the shared Core Data stack. This way, both the main app and the share extension can read from the same data source.

Search Needs ContextLink to heading

I pushed it a bit further by implementing search in the extension as well. This was a bit tricky because NSFetchRequest is not a standalone query object – it needs to be executed against a context.

Useful clarification:

  • NSFetchRequest describes what to fetch
  • Execution still requires NSManagedObjectContext (fetch / executeFetchRequest) (source, source)
  • @FetchRequest in SwiftUI also relies on context from \.managedObjectContext (source)

For searchable lists, this was enough:

func performSearch(text: String, context: NSManagedObjectContext) throws -> [LabelEntity] {
let request: NSFetchRequest<LabelEntity> = LabelEntity.fetchRequest()
request.predicate = text.isEmpty ? nil : NSPredicate(format: "name CONTAINS[cd] %@", text)
request.fetchLimit = 50
return try context.fetch(request)
}

Now we have a small and performance-friendly search that runs against the local Core Data store, without hitting the API or loading large datasets into memory.

OutcomeLink to heading

  • Share Extension opened reliably without crashes
  • Create screen in the main app also got faster
  • Users felt the difference quickly because label data was ready
  • Refresh cache from main app lifecycle, not inside extension startup
  • Test on real devices with large datasets

If memory is tight, optimize data flow first. In my case, that had far more impact than UI-layer changes. The highest goal is to make the user happy and the app useable and not to have a perfect architecture.

My avatar

Thanks for reading my blog post! Feel free to check out my other posts or contact me via the social links in the footer.


More Posts