iOS Development Guide

AppDelegate와 SceneDelegate 역할 차이

핵심은 범위(scope)다. AppDelegate는 "앱 전체 레벨" 이벤트를 담당하고, SceneDelegate는 "각 화면 세션(window scene) 레벨" 생명주기를 담당한다.

App lifecycle scope Scene lifecycle scope Knowledge item #21
학습 날짜

기록 없음

한눈에 비교

구분 AppDelegate SceneDelegate
관리 범위 앱 프로세스 전체 각 `UIWindowScene`(창/세션) 단위
대표 책임 푸시 등록, 앱 시작 초기화, 글로벌 서비스 설정 window 생성/연결, 화면 전환 진입점 구성, scene 상태 반응
멀티윈도우 대응 전체 정책 관리 scene마다 독립 상태 관리

AppDelegate에서 주로 하는 일

  • 앱 런치 시 의존성 초기화 (`didFinishLaunchingWithOptions`)
  • 푸시 토큰 등록/갱신, 원격 알림 처리 진입
  • 앱 전체 공통 설정(로그, 분석, SDK 초기화)

SceneDelegate에서 주로 하는 일

  • scene 연결 시 `window` 생성 및 root 화면 구성
  • scene 활성/비활성 전환 대응 (`sceneDidBecomeActive` 등)
  • scene별 상태 저장/복원 (`state restoration`)

왜 둘로 나뉘었나

iPad 멀티윈도우(여러 scene) 환경에서 한 앱이 여러 화면 세션을 동시에 가질 수 있기 때문이다. 앱 전체 이벤트와 개별 화면 세션 이벤트를 분리해야 책임이 명확해진다.

iOS 13+에서 Scene 기반 생명주기가 도입되었고, 프로젝트 설정에 따라 SceneDelegate를 사용하지 않는 앱도 존재할 수 있다.

실무에서 헷갈리는 지점

// AppDelegate: 앱 전체 레벨
func application(
  _ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
  // 글로벌 초기화
  return true
}

// SceneDelegate: scene(창) 레벨
func scene(
  _ scene: UIScene,
  willConnectTo session: UISceneSession,
  options connectionOptions: UIScene.ConnectionOptions
) {
  guard let windowScene = scene as? UIWindowScene else { return }
  let window = UIWindow(windowScene: windowScene)
  window.rootViewController = RootViewController()
  self.window = window
  window.makeKeyAndVisible()
}
"root 설정"은 보통 SceneDelegate에서, "SDK 초기화/앱 전역 설정"은 AppDelegate에서 처리하는 편이 유지보수에 유리하다.

충돌 없이 책임 분리하는 실무 패턴

입력 이벤트 수신 위치 처리 원칙
로그인 상태 변경 AppDelegate 또는 전역 Auth 서비스 전역 상태만 갱신하고, 화면 전환은 활성 scene 라우터에 위임
딥링크 SceneDelegate (`openURLContexts`, `continue userActivity`) scene 단위 라우팅 큐에 적재 후 현재 화면 상태에서 안전하게 소비
푸시 탭 진입 AppDelegate 수신 후 scene 식별/선택 즉시 화면 전환하지 말고 대상 scene 결정 후 라우팅 이벤트 전달
핵심 규칙: AppDelegate는 전역 이벤트 허브, SceneDelegate는 화면 전환 실행자로 고정하면 충돌을 크게 줄일 수 있다.
// 1) AppDelegate: 이벤트를 전역으로 수집
func application(_ application: UIApplication,
                 didReceiveRemoteNotification userInfo: [AnyHashable: Any]) async
  -> UIBackgroundFetchResult {
  AppEventBus.shared.publish(.push(userInfo))
  return .noData
}

// 2) SceneDelegate: 활성 scene에서만 라우팅 실행
func sceneDidBecomeActive(_ scene: UIScene) {
  Task { @MainActor in
    for await event in AppEventBus.shared.events {
      guard let windowScene = scene as? UIWindowScene else { continue }
      SceneRouter(windowScene: windowScene).handle(event)
    }
  }
}

오해 정리

  • "AppDelegate가 없어졌다"는 오해: 없어지지 않았다. 역할이 분리된 것이다.
  • "모든 라이프사이클을 SceneDelegate에서 처리"는 과도하다. 앱 전역 이벤트는 AppDelegate 책임이다.
  • "SceneDelegate는 iPad 전용"도 오해다. iPhone에서도 Scene 설정을 쓸 수 있다.

실무 체크리스트

  • 전역 초기화 코드가 SceneDelegate에 섞여 있지 않은가?
  • window/root 생성 책임이 명확한가?
  • 딥링크/푸시 진입 로직이 앱 레벨과 scene 레벨로 구분되어 있는가?
  • 멀티 scene 환경에서 상태 충돌이 없는가?

Q