How to Access the AppDelegate Instance Across iOS Apps

How to Access the AppDelegate Instance Across iOS Apps

Written by

in

Quick answer: In UIKit, retrieve the delegate from the shared application singleton and cast it to your concrete class: 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 AppDelegate as 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 @MainActor to prevent compiler diagnostics under modern Swift concurrency rules.
  • In modern scene-based UIKit applications, inspect SceneDelegate for UI window and navigation root updates rather than AppDelegate.

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.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 4.0; this article is shared under the same license.