Apple, iOS 27 앱 제출 시작? Universal Links를 위한 필수 Scene 수명 주기 준비 방법

opoinstall
2026-09-14
5 min read

Apple의 iOS 27 앱 제출이 시작되었습니다. 2026년 9월 9일, Apple은 공식적으로 iOS 27, iPadOS 27, macOS 27, tvOS 27, visionOS 27 및 watchOS 27에 대한 앱 스토어 제출을 허용하고, 2026년 9월 14일 공개 운영체제 출시에 앞서 Xcode 27 Release Candidate를 배포했습니다. 즉각적인 제출 창을 통해 엔지니어링 팀은 출시 당일 소프트웨어 업데이트를 배포할 수 있게 되었지만, Apple은 두 가지 중요한 플랫폼 경계 조건을 제시했습니다. iOS 27 SDK로 빌드할 경우 UIKit Scene 기반 수명 주기를 도입하는 것이 필수적이며, 2027년 4월부터는 모든 앱 스토어 제출 시 iOS 27 및 iPadOS 27 SDK가 최소 필수 요구 사항이 됩니다. iOS 소프트웨어 아키텍트, 모바일 인프라 리드 및 그로스 엔지니어는 iOS 27 Universal Links 라우팅을 마스터하기 위해 구식의 단일 창(Single-window) 가정을 폐기하고, 링크 핸들러를 최신 UIWindowSceneDelegate 수명 주기로 마이그레이션하며, 동적 디스플레이 환경 전반에서 라우팅 상태가 올바르게 범위 지정되도록 해야 합니다.

앱 스토어 제출 기간, 런타임 요구 사항 및 2027년 지침

iOS 27 앱 스토어 제출 시작은 Apple의 플랫폼 전환 주기의 시작을 의미합니다. Apple 개발자 포털에 게시된 공지에 따르면, 개발자는 Xcode 27 Release Candidate로 소프트웨어를 컴파일 및 검증하고, TestFlight를 통해 빌드를 테스트하며, 6개의 모든 Apple 플랫폼에서 앱 리뷰를 위한 프로덕션 바이너리를 제출할 수 있습니다.

한눈에 보기

  • 제출 기간 시작: 2026년 9월 14일 월요일 공개 운영체제 출시에 앞서, 개발자는 2026년 9월 9일부터 iOS 27 SDK로 빌드된 애플리케이션을 제출할 수 있습니다.
  • iOS 27 SDK의 필수 Scene 수명 주기: iOS 27 SDK로 빌드된 UIKit 애플리케이션 중 Scene 기반 수명 주기를 도입하지 않은 앱은 런타임 시 실행되지 않습니다.
  • 2027년 4월 최소 요구 사항: 2027년 4월부터 App Store Connect에 업로드되는 모든 애플리케이션 및 업데이트는 iOS 27 및 iPadOS 27 SDK 이상으로 빌드되어야 합니다.

Apple 개발자 앱 스토어 제출 안내 배너

9to5Mac 보도에 따르면, 단기적으로 iOS 27 SDK 도입은 선택 사항이지만, Xcode 27과 최신 SDK로 빌드하면 개발자는 Apple Intelligence 통합 및 파운데이션 모델 프레임워크에 즉시 액세스할 수 있습니다. 별도로 App Store Connect에는 업데이트된 제품 페이지 헤더와 자녀 보호 기능을 위한 연령 제한 규정 준수 설문조사 등 머천다이징 업데이트가 도입되었습니다.

UI 업데이트와 머천다이징 변화 외에도, 최신 SDK로 빌드하는 것은 핵심 아키텍처 현대화를 강제합니다. UIKit Scene 기반 수명 주기로 전환하기에 관한 Apple의 공식 문서는 iOS 27부터 최신 SDK로 빌드된 앱은 반드시 Scene 기반 수명 주기를 도입해야 하며, 그렇지 않을 경우 실행되지 않는다는 점을 명시하고 있습니다. 이는 수년간 지속된 기존 앱 델리게이트 창 관리를 완전히 대체하는 것입니다.

+-------------------------------------------------------------------------+
|                  APPLE SDK 제출 및 런타임 일정                          |
+-------------------+--------------------+--------------------------------+
| 마일스톤 날짜     | 관리 계층          | 운영 및 기술 범위              |
+-------------------+--------------------+--------------------------------+
| 2026년 9월 9일    | App Store Connect  | Xcode 27 RC 및 iOS 27 SDK      |
|                   |                    | 빌드 제출 시작 (선택 사항)      |
| 2026년 9월 14일   | 운영체제           | 호환 기기 대상 iOS 27          |
|                   |                    | 정식 출시                      |
| iOS 27 최신 SDK   | 런타임 계약        | UIKit 앱은 Scene 수명 주기를   |
|                   |                    | 도입해야 함 (미도입 시 실행 불가)|
| 2027년 4월        | 앱 스토어 지침     | 모든 앱 업로드 시 iOS/iPadOS   |
|                   |                    | 27 SDK 이상 필수 적용          |
+-------------------------------------------------------------------------+

2027년 4월까지는 시간적 여유가 있지만, Xcode 27로 컴파일하는 기술 팀은 단일 글로벌 UIWindow에 의존하는 기존 라우팅 패턴을 즉시 해결해야 합니다.

Scene 수명 주기 해체 및 다중 창 고려 사항

수년간 기존 iOS 애플리케이션은 UIApplicationDelegate 진입점을 통해 들어오는 딥링크를 처리했으며, 종종 application(_:open:options:) 또는 application(_:continue:restorationHandler:)에 의존해 왔습니다. 이러한 단일 창 모델에서 엔지니어링 팀은 일반적으로 글로벌 싱글톤을 중심으로 탐색 라우터를 구성하고 UIApplication.shared.windows.first(where: { $0.isKeyWindow })를 쿼리하여 대상 뷰 컨트롤러를 표시해 왔습니다.

Apple 플랫폼 개발자 아이콘 및 SDK 도구 세트

Scene 수명 주기 도입과 여러 동시 창 지원을 구분하는 것이 중요합니다. iOS 27 SDK로 빌드된 UIKit 애플리케이션은 UIScene 수명 주기를 반드시 도입해야 하지만, 여러 창을 동시에 지원하는 것은 별도의 설정 가능한 기능으로 남아 있습니다. 그러나 iPadOS의 Split View, Vision Pro 공간 창, iPhone Duo와 같은 다중 디스플레이 구성 등 최신 하드웨어 및 소프트웨어 환경은 애플리케이션을 다중 창 환경으로 점점 더 많이 배치하고 있습니다.

애플리케이션이 다중 창을 지원하거나 동적 분할 화면에서 공존하는 경우, 기존의 싱글톤 라우팅은 심각한 프레젠테이션 실패를 유발합니다:

  1. 창 컨텍스트 모호성: 애플리케이션이 여러 창 인스턴스를 동시에 지원하는 경우, 글로벌 싱글톤을 통해 라우팅되는 Universal Link는 어떤 Scene이 결과를 수신하고 표시해야 하는지 판단할 수 없습니다.
  2. 백그라운드 Scene 하이재킹: UIApplication.shared가 반환하는 첫 번째 Key Window에 대상 뷰 컨트롤러를 표시하면 종종 비활성 또는 백그라운드 Scene 위에 새 뷰를 밀어넣게 되어, 사용자는 변경되지 않은 활성 화면을 보게 됩니다.
  3. 계층 구조 표시 충돌: 임의의 창 컨텍스트에서 라우팅 명령을 실행하면 잘못된 표시 상태, 중복된 탐색 순서 또는 비활성 뷰 계층에서 표시하려는 시도가 발생할 수 있습니다.
+-------------------------------------------------------------------------+
|               기존 싱글톤 라우팅 VS. 다중 Scene 라우팅                  |
+-------------------------------------------------------------------------+
|                                                                         |
|  [ 기존 결함 있는 모델: 글로벌 애플리케이션 창 가정 ]                   |
|                                                                         |
|  들어오는 Universal Link                                                |
|         |                                                               |
|         v                                                               |
|  [ 앱 델리게이트 / 글로벌 라우터 싱글톤 ]                              |
|         |                                                               |
|         |-- 쿼리: 글로벌 key-window 조회 (windows.first: keyWindow)     |
|         v                                                               |
|  [ 임의의 창에 표시 (위험: 백그라운드 창 / 뷰 누출) ]                   |
|                                                                         |
+-------------------------------------------------------------------------+
|                                                                         |
|  [ 현대적인 Scene 기반 모델: 컨텍스트 UIWindowScene 전달 ]              |
|                                                                         |
|  들어오는 Universal Link                                                |
|         |                                                               |
|         +---------------------------------------+                       |
|         | (실행 시 Scene 연결)                  | (이미 실행 중인 Scene)  |
|         v                                       v                       |
|  [ scene(_:willConnectTo:options:) ]    [ scene(_:continue:) ]          |
|         |                                       |                       |
|         +-------------------+-------------------+                       |
|                             |                                           |
|                             v                                           |
|  [ 명시적 Scene 컨텍스트 내에서 NSUserActivity 추출 ]                   |
|         |                                                               |
|         v                                                               |
|  [ 해당 Scene의 로컬 루트 계층에 대상 목적지 라우팅 ]                  |
|                                                                         |
+-------------------------------------------------------------------------+

이러한 충돌을 제거하려면 iOS 27 SDK로 빌드하는 개발자는 Apple Universal LinksUIWindowScene으로 앱 수명 주기 관리하기 문서에 따라 Scene 수명 주기를 통해 엄격하게 처리해야 합니다.

UIKit은 Scene의 실행 상태에 따라 링크 전달을 이원화합니다:

  • 실행 또는 활성화 시 Scene 연결: 앱 실행이나 활성화의 일부로 Scene이 연결되고 해당 연결 옵션에 Universal Link 활동이 포함된 경우, UIKit은 connectionOptions 페이로드 내의 scene(_:willConnectTo:options:)를 통해 NSUserActivity를 전달합니다.
  • 웜/일시 중단된 Scene 실행: Scene이 이미 연결되어 실행 중이거나 메모리에서 일시 중단된 경우, UIKit은 scene(_:continue:)로 활동을 직접 전달합니다.

어떤 실행 흐름이든 탐색은 UIKit이 전달한 특정 UIWindowScene 인스턴스를 기준으로 실행되어야 하며, 대상 뷰 컨트롤러가 해당 Scene의 자체 창 계층 구조 내에서 인스턴스화되도록 해야 합니다.

// Scene 기반 Universal Links 라우팅을 위한 Swift 구현 예시.
// iOS 27 UIKit Scene 기반 수명 주기를 준수하고 다중 창 실행을 지원하기 위해
// 전역 애플리케이션 싱글톤으로부터 딥링크 처리 로직을 분리하는 방법을 보여줍니다.

import UIKit

struct DeepLinkRoute {
    enum Destination {
        case productDetail(sku: String)
        case campaignLanding(campaignId: String)
        case generalWebFallback(url: URL)
    }
    
    let destination: Destination
    let rawURL: URL
    
    static func parse(from url: URL) -> DeepLinkRoute {
        guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
            return DeepLinkRoute(destination: .generalWebFallback(url: url), rawURL: url)
        }
        
        let pathSegments = components.path.split(separator: "/").map(String.init)
        if let productIndex = pathSegments.firstIndex(of: "products"), productIndex + 1 < pathSegments.count {
            let sku = pathSegments[productIndex + 1]
            return DeepLinkRoute(destination: .productDetail(sku: sku), rawURL: url)
        }
        
        if let campaignId = components.queryItems?.first(where: { $0.name == "campaign_id" })?.value {
            return DeepLinkRoute(destination: .campaignLanding(campaignId: campaignId), rawURL: url)
        }
        
        return DeepLinkRoute(destination: .generalWebFallback(url: url), rawURL: url)
    }
}

class SceneDelegate: UIResponder, UIWindowSceneDelegate {
    var window: UIWindow?

    // 시나리오 1: 들어오는 Universal Link와 함께 콜드 런치 또는 활성화 중 Scene 연결
    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = scene as? UIWindowScene else { return }
        
        // 이 윈도우 Scene 컨텍스트에 엄격하게 바인딩된 전용 창 초기화
        let window = UIWindow(windowScene: windowScene)
        let navigationController = UINavigationController(rootViewController: MainDashboardViewController())
        window.rootViewController = navigationController
        self.window = window
        window.makeKeyAndVisible()
        
        // 이 Scene 연결이 Universal Link 활동에 의해 시작되었는지 확인
        if let userActivity = connectionOptions.userActivities.first(where: { $0.activityType == NSUserActivityTypeBrowsingWeb }),
           let incomingURL = userActivity.webpageURL {
            let route = DeepLinkRoute.parse(from: incomingURL)
            routeWithinSceneContext(route: route, on: navigationController)
        }
    }

    // 시나리오 2: 이미 실행 중이거나 메모리에서 일시 중단된 기존 Scene에 Universal Link 전달
    func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
        guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
              let incomingURL = userActivity.webpageURL,
              let navigationController = self.window?.rootViewController as? UINavigationController else {
            return
        }
        
        let route = DeepLinkRoute.parse(from: incomingURL)
        routeWithinSceneContext(route: route, on: navigationController)
    }

    /// 이 Scene의 뷰 컨트롤러 계층 내에서만 엄격하게 탐색 라우팅
    /// 백그라운드 Scene 손상을 방지하기 위해 UIApplication.shared.windows 쿼리 회피
    private func routeWithinSceneContext(route: DeepLinkRoute, on navigationController: UINavigationController) {
        switch route.destination {
        case .productDetail(let sku):
            let detailVC = ProductDetailViewController(sku: sku)
            navigationController.pushViewController(detailVC, animated: true)
            
        case .campaignLanding(let campaignId):
            let campaignVC = CampaignViewController(campaignId: campaignId)
            navigationController.present(campaignVC, animated: true, completion: nil)
            
        case .generalWebFallback(let url):
            // 시스템 URL 핸들러를 통해 처리되지 않은 또는 외부 URL 열기
            UIApplication.shared.open(url, options: [:], completionHandler: nil)
        }
    }
}

// 애플리케이션 Scene 계층을 나타내는 스텁 뷰 컨트롤러
class MainDashboardViewController: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "대시보드"
        view.backgroundColor = .systemBackground
    }
}

class ProductDetailViewController: UIViewController {
    let sku: String
    init(sku: String) {
        self.sku = sku
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "제품: \(sku)"
        view.backgroundColor = .systemGroupedBackground
    }
}

class CampaignViewController: UIViewController {
    let campaignId: String
    init(campaignId: String) {
        self.campaignId = campaignId
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
    override func viewDidLoad() {
        super.viewDidLoad()
        title = "캠페인: \(campaignId)"
        view.backgroundColor = .secondarySystemBackground
    }
}

다운스트림 모바일 획득 및 설치 경계

Scene 기반 라우팅은 앱 설치 후 앱 내 탐색을 위한 올바른 수명 주기 컨텍스트를 제공하지만, 마케팅 및 고객 획득 캠페인은 더 넓고 분리된 수명 주기 내에서 작동합니다. 업스트림 획득 퍼널은 외부 모바일 웹 페이지, 파트너 마케팅 캠페인 및 디지털 광고를 통해 잠재 고객과 소통합니다.

사용자가 가맹점이나 서비스와 연결된 Universal Link를 클릭하면, 로컬 설치 상태에 따라 운영체제의 동작이 달라집니다:

+-------------------------------------------------------------------------+
|             별도의 다운스트림 모바일 획득 여정                          |
+-------------------------------------------------------------------------+
|                                                                         |
|  [ 외부 터치포인트: H5 캠페인 랜딩 / 프로모션 링크 ]                    |
|         |                                                               |
|         |-- (사용자가 iOS 기기에서 링크 탭)                             |
|         v                                                               |
|  [ iOS가 검증된 Apple-App-Site-Association (AASA) 평가 ]               |
|         |                                                               |
|         +---------------------------------------+                       |
|         |                                       |                       |
|         v                                       v                       |
|  [ 기기에 앱 설치됨 ]                 [ 기기에 앱 설치 안 됨 ]          |
|         |                                       |                       |
|         v                                       v                       |
|  [ UIWindowSceneDelegate를 통한        [ 사용자 브라우저에서 웹           |
|    직접 Scene 핸드오프 ]                대상으로 해결 ]                 |
|         |                                       |                       |
|         v                                       v                       |
|  [ 네이티브 앱 내 탐색 ]              [ 사용자 스토어 다운로드 CTA 클릭 ] |
|                                                 |                       |
|                                                 v                       |
|                                       [ 스토어로 라우팅 ]               |
|                                                 |                       |
|                                                 v                       |
|                                       [ 표준 스토어 흐름은 임의의       |
|                                         웹 컨텍스트를 자동으로          |
|                                         재구성하지 않음 ]               |
|                                                 |                       |
|                                                 v                       |
|                                       [ 지연된 딥링크 엔진 ]            |
|                                                 |                       |
|                                                 v                       |
|                                       [ 콜드 부팅 시 컨텍스트 복원 ]    |
|                                                                         |
+-------------------------------------------------------------------------+

이미 사용자의 핸드셋에 애플리케이션이 설치되어 있는 경우, 유효한 Universal Links는 웹 목적지 대신 관련 설치 앱을 직접 열 수 있습니다. 그러나 대상 애플리케이션이 없는 경우 링크는 사용자의 브라우저에서 웹 목적지를 엽니다.

설치되지 않은 사용자가 나중에 앱 스토어에서 앱을 다운로드하는 경우, 표준 앱 스토어 설치 흐름은 첫 번째 실행 시 임의의 원본 웹 URL과 전체 캠페인 컨텍스트를 자동으로 재구성하지 않습니다. 플랫폼별 리퍼러 메커니즘이 제한된 설치 메타데이터를 노출할 수는 있습니다. 앱의 최초 콜드 부팅 시 사용자에게 일반적인 환영 화면만 표시되어 의도된 전환 흐름에서 벗어나게 됩니다.

엔지니어링 팀은 이러한 설치 경계 문제를 해결하기 위해 여러 링크 처리 프레임워크를 검토합니다:

라우팅 아키텍처 설치된 앱 처리 미설치 앱 폴백 설치 경계 컨텍스트 보존 운영 소유권 모델
커스텀 URI 스킴 네이티브 코드의 로컬 스킴 등록을 통해 가로채기 앱이 없을 때 네이티브 목적지 없음; 명시적인 웹/스토어 폴백 처리 필요 없음; 스토어 리다이렉트 시 매개변수 손실 애플리케이션 소유 (높은 유지보수 비용)
검증된 Universal Links Scene 수명 주기를 통해 뷰 계층으로 직접 네이티브 라우팅 폴백 웹 랜딩 페이지로 연결 네이티브로 없음; 표준 스토어 다운로드 흐름은 임의의 쿼리 문자열을 전달하지 않음 도메인 + 애플리케이션 소유 (AASA 호스팅 및 DNS 설정 필요)
지연된 딥링크 아키텍처 Universal Links 또는 네이티브 스킴으로 위임 웹 랜딩을 거쳐 스토어 다운로드로 라우팅 최초 실행 시 적합한 사전 설치 매개변수 복원 SDK 지원 (관리형 어트리뷰션 클라이언트 및 서버 프레임워크)

엔터프라이즈 프로덕션 아키텍처에서 모바일 엔지니어링 팀은 Branch, AppsFlyer, Adjust 또는 Opoinstall과 같은 전문화된 지연된 어트리뷰션 인프라를 배포합니다. Opoinstall과 같은 플랫폼은 앱 스토어 리다이렉트 이전에 웹 계층에서 채널 식별자, 추천 토큰 또는 딥 콘텐츠 경로와 같은 사전 설치 캠페인 메타데이터를 기록합니다. 애플리케이션의 최초 실행 시 클라이언트 SDK는 어트리뷰션 백엔드를 쿼리하여 세션을 매칭하고 캐시된 매개변수를 검색합니다. Opoinstall 홈페이지의 공식 플랫폼 문서에 따르면, 이 지연된 매개변수 전달 프레임워크는 최초 실행 시 적합한 인스턴스의 최대 98%에서 매개변수를 복원할 수 있어 수동 프로모션 코드를 대체하는 자동화된 대안을 제공합니다.

설치 경계 전반에서 필요한 획득 메커니즘으로부터 Scene 안전한 인앱 탐색을 분리함으로써, 개발 조직은 새로운 운영체제 업데이트와 다양한 창 환경 전반에서 확장 가능한 회복력 있는 고객 여정을 구축합니다.

자주 묻는 질문 (FAQ)

개발자는 즉시 iOS 27 SDK를 사용해야 하나요?
아니요. 제출 창은 2026년 9월 9일 Xcode 27 Release Candidate와 함께 열렸지만, 단기적으로 iOS 27 SDK 도입은 선택 사항입니다. 개발자는 2027년 4월까지 이전의 지원되는 SDK로 빌드된 업데이트를 계속 제출할 수 있습니다. 2027년 4월부터 App Store Connect에 제출되는 모든 새로운 앱과 업데이트는 iOS 27 및 iPadOS 27 SDK 이상을 사용하여 빌드되어야 합니다.
iOS 27 Scene 수명 주기 요구 사항이 다중 창 지원을 의무화하나요?
아니요. iOS 27 SDK로 빌드된 UIKit 앱은 `UIScene` 수명 주기를 도입하는 것이 필수적이지만, 여러 동시 창을 지원하는 기능은 선택 사항으로 유지됩니다. 애플리케이션은 Scene 수명 주기를 도입하면서도 창 인스턴스를 단일 Scene으로 제한할 수 있습니다. 그러나 `UIWindowSceneDelegate`를 도입하면 애플리케이션의 라우팅 아키텍처가 Split View, 외부 디스플레이 및 폴딩 기기에 대비할 수 있게 됩니다.
애플리케이션은 콜드 런치와 웜 세션 모두에서 어떻게 Universal Links를 캡처하나요?
Scene을 도입하는 최신 UIKit 아키텍처에서 개발자는 `UIWindowSceneDelegate` 내에 두 가지 델리게이트 메서드를 구현합니다: 1. `scene(_:willConnectTo:options:)`: 실행 또는 활성화 중에 Scene이 연결될 때 `connectionOptions.userActivities`를 검사하여 들어오는 URL을 추출합니다. 2. `scene(_:continue:)`: 대상 Scene이 이미 연결되어 메모리에서 실행 중일 때 들어오는 `NSUserActivity`를 수신합니다.

참조

Share this article