guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return }. Under modern Swift concurrency, ensure this access runs on @MainActor.Why this happens
In UIKit architecture, the singleton UIApplication.shared owns the application life cycle, while custom global behavior resides in your custom class conforming to UIApplicationDelegate. However, the UIApplication.shared.delegate property is typed as an optional (any UIApplicationDelegate)?. Because the framework does not know about your specific subclass, you cannot access custom properties—such as a persistent Core Data container, analytics coordinator, or configuration state—without downcasting.
Beginning in Swift 5.10 and Swift 6, strict concurrency checking adds an additional consideration: UIApplication.shared and its delegate are bound to the @MainActor. Attempting to access UIApplication.shared.delegate from an arbitrary background task or detached thread produces compiler errors regarding non-isolated actor access.
Additionally, projects created with UISceneDelegate split window management from application launch. Developers often attempt to access window hierarchies or state from AppDelegate that actually resides in SceneDelegate, or try to reach AppDelegate directly from pure SwiftUI view hierarchies without using proper property wrappers.
How to fix it
1. Safely Downcast UIApplication.shared.delegate
The standard way to obtain a reference in UIKit is to read UIApplication.shared.delegate and cast it to your custom AppDelegate class. Using a conditional cast (as?) prevents unrecoverable crashes if tests or mock environments use a different delegate.
import UIKit
func fetchCoreDataContext() {
guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else {
return
}
let context = appDelegate.persistentContainer.viewContext
// Perform operations with context
}
If you are writing code inside a UIViewController, this call is naturally isolated to @MainActor, so no concurrency warnings occur.
2. Expose a Static Shared Helper on AppDelegate
Typing UIApplication.shared.delegate as? AppDelegate across view controllers creates repetitive boilerplate. A clean pattern is to add a type-safe static helper directly onto your AppDelegate definition.
import UIKit
@main
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
@MainActor
static var shared: AppDelegate {
guard let delegate = UIApplication.shared.delegate as? AppDelegate else {
fatalError("AppDelegate is not registered as the application delegate.")
}
return delegate
}
// Example custom property
var appSessionID: String = UUID().uuidString
}
You can now call AppDelegate.shared from anywhere on the main thread:
let sessionID = AppDelegate.shared.appSessionID
3. Access AppDelegate Safely Under Swift Concurrency
Under strict concurrency checks, accessing UIApplication.shared from a background asynchronous context will trigger compiler errors such as Call to main actor-isolated property 'delegate' in a synchronous nonisolated context. To resolve this, wrap the access in a @MainActor context or use MainActor.run:
Task {
// Background work here
let result = await performDataFetch()
// Hop to the main actor to interact with AppDelegate
await MainActor.run {
let delegate = AppDelegate.shared
delegate.handleFetchCompletion(with: result)
}
}
If a service class needs regular access to AppDelegate, mark that service class or method with @MainActor.
4. Connect to AppDelegate in SwiftUI Using @UIApplicationDelegateAdaptor
If your app uses the SwiftUI App life cycle instead of an Info.plist storyboard entry point, you do not query UIApplication.shared.delegate. Instead, register and reference your AppDelegate using the @UIApplicationDelegateAdaptor property wrapper.
import SwiftUI
class CustomAppDelegate: NSObject, UIApplicationDelegate {
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
// Setup push notifications or third-party SDKs
return true
}
}
@main
struct MainApp: App {
@UIApplicationDelegateAdaptor(CustomAppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
SwiftUI instantiates the delegate and delivers all life-cycle callbacks automatically. To pass delegate properties down to views, expose them through an ObservableObject or modern @Observable model injected into the environment.
5. Replace AppDelegate Storage with Dependency Injection
Storing domain models or database stacks (such as Core Data or SwiftData) inside AppDelegate is a legacy pattern that couples UI code directly to application life cycle classes. A cleaner architectural solution is to inject shared services into your view controllers during initialization.
import UIKit
import CoreData
final class ProfileViewController: UIViewController {
private let context: NSManagedObjectContext
init(context: NSManagedObjectContext) {
self.context = context
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented. Use init(context:) instead.")
}
}
This completely removes the need to query UIApplication.shared.delegate across your presentation layer and makes your view controllers unit-testable without launching the full application runner.
How to prevent it
- Do not use
AppDelegateas a general-purpose global storage container for business models or temporary state. - Avoid forced unwrapping (
as! AppDelegate) in reusable libraries or frameworks where host app delegates may vary. - Explicitly annotate delegate accessor functions with
@MainActorto prevent compiler diagnostics under modern Swift concurrency rules. - In modern scene-based UIKit applications, inspect
SceneDelegatefor UI window and navigation root updates rather thanAppDelegate.
FAQ
Can I access AppDelegate from a widget or notification extension?
No. App extensions run in separate processes without an UIApplication instance. Attempting to use UIApplication.shared inside an extension produces a build error because the API is restricted from application extension contexts.
Why does UIApplication.shared.delegate return nil?
This occurs if you read the property before UIApplicationMain completes delegate initialization, such as in static variable initializers of other classes that run during pre-main binary load time. Always wait until the app finishes launching before referencing the delegate.
Should I store Core Data context in AppDelegate or SceneDelegate?
Neither is recommended for modern codebases. Create a dedicated container class (such as DataController or PersistenceController) and inject the managed object context into your view controllers or SwiftUI environment at the root view instantiation level.
Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.
