Xcode 27로 빌드된 React Native 앱의 93%가 실행되지 않는 이유: UIScene 변경과 충돌을 일으키는 라이브러리
요약
Apple이 Xcode 27(iOS 27 SDK)로 빌드된 앱에 UIScene life cycle 채택을 의무화하면서, UIKit scene life cycle을 사용하지 않은 React Native 및 Expo 앱들이 실행 오류를 겪고 있습니다. 이 변화는 개발자들이 SceneDelegate와 관련 설정을 업데이트해야 함을 의미합니다.
핵심 포인트
- Xcode 27 빌드 시 UIScene Life Cycle 채택 필수
- React Native 0.88 버전부터 Scene Delegate 도입됨
- Expo SDK 57 이상에서 scene 지원 활성화 필요
- 기존 라이브러리들이 Window 속성을 찾으면서 충돌 발생
Apple은 지난 9월, 2027년 4월부터 App Store Connect에 업로드되는 모든 앱은 iOS 27 SDK로 빌드되어야 한다고 발표했습니다. 이는 Xcode 27을 의미합니다. 이 SDK로 빌드된 앱 중 UIKit scene life cycle을 채택하지 않은 앱은 실행되지 않습니다. 다음과 같은 메시지와 함께 시작 단계에서 멈춥니다:
Application failed to launch: UIScene life cycle is required for apps built with this SDK.
Apple은 TN3187에서 이 규칙을 발표했습니다. 이 확인 절차는 기기의 iOS 버전이 아닌, 어떤 SDK로 빌드했는지에 따라 달라집니다. Xcode 26으로 빌드된 동일한 앱은 여전히 iOS 27에서 실행되므로 오늘날 사용자에게는 아무 문제도 없습니다. 문제는 사용자가 Xcode 27로 이동하는 날 발생합니다.
저는 수백 개의 오픈 소스 React Native 및 Expo 앱에 대해 건강 검진(health check)을 수행했습니다 (제가 NativeKeel 테스트에 사용하는 세트). 2026년 10월 11일 기준으로:
| Apps | |
|---|---|
| React Native 및 Expo 앱 중 iOS 타겟이 있는 경우 | 628 |
| ... |
이 숫자는 해당 앱들에 대한 판단이 아닙니다. 몇 주 전까지는 어떤 React Native나 Expo 템플릿도 scene을 사용하지 않았습니다.
React Native와 Expo의 현황
- React Native 0.88은 이 기능을 채택한 첫 번째 버전입니다.
SceneDelegate.swift가 window scene에서 React Native를 시작하며,Info.plist에UIApplicationSceneManifest로 이름을 지정합니다. 0.88은 여전히 릴리스 후보(7월 10일 기준 rc.4)입니다. 0.87 이하 버전에는 템플릿에 해당 내용이 없으므로, 업그레이드 후 Upgrade Helper에서 두 파일을 모두 복사하는 것이 방법입니다. - Expo SDK 57은 선택적으로 기능을 활성화할 수 있습니다.
expo57.0.23+ 및expo-build-properties57.0.20+:
["expo-build-properties", { "ios": { "enableSceneSupport": true } }]
이후 npx expo prebuild를 다시 실행해야 합니다.
- Expo SDK 58(9월 15일 이후 베타)은 기본적으로 scene delegate를 생성합니다.
- SDK 56 이하 버전은 먼저 SDK 업그레이드가 필요합니다.
어떻게 채택했는지 어떻게 알 수 있을까요? Info.plist에 장면 구성(scene configurations)을 가진 UIApplicationSceneManifest가 있거나, 앱 델리게이트(app delegate)가 application(_:configurationForConnecting:options:)를 구현하고 있다면 그렇습니다. 단순히 UIApplicationSupportsMultipleScenes만 설정한 매니페스트는 해당되지 않습니다.
채택 후 문제가 발생하는 부분: AppDelegate에 window가 없음
0.88 템플릿에서는 UIWindow가 장면 델리게이트(scene delegate)에 속합니다. 따라서 AppDelegate에는 더 이상 window 속성이 전혀 없습니다.
많은 iOS 라이브러리가 여전히 다음과 같은 방식으로 표시할 화면을 찾습니다:
UIViewController *root = [UIApplication sharedApplication].delegate.window.rootViewController;
하지만 새로운 템플릿에서는 이 라인이 오류를 발생시킵니다:
-[AppDelegate window]: unrecognized selector sent to instance
저는 인기 있는 React Native 라이브러리의 iOS 소스 코드에서 respondsToSelector: 확인 없이 호출되는 이 호출을 검색했고, 현재 사용 중인 15개에서 발견했습니다. 그런 다음 최신 React Native 0.88.0-rc.4 앱을 새로 빌드하여 그중 두 개(react-native-blob-util과 react-native-google-mobile-ads)와 충돌하는 것을 확인했습니다. 이 둘 모두 한 가지 변경 사항 이후에 충돌이 멈췄습니다:
UIViewController *root = RCTPresentedViewController(); // 또는 RCTKeyWindow().rootViewController
두 수정 사항 모두 풀 리퀘스트(pull requests)로 열려 있습니다 (react-native-blob-util#498, react-native-google-mobile-ads#911). 실행 시점에 호출하는 @react-native-firebase/messaging의 경우, 수정 사항이 이미 상위 레벨에서 제안되었습니다 (react-native-firebase#9359).
628개의 앱 중 68개가 이 라이브러리들 중 적어도 하나에 의존합니다. 가장 흔한 것은 다음과 같습니다:
| Library | Apps | When it crashes |
|---|---|---|
| react-native-blob-util | 17 | open-in / options 메뉴 표시 시 |
| ... |
수정 사항이 배포될 때까지, 위의 라인을 포함하는 patch-package 패치가 작동합니다.
조용한 문제: 딥 링크가 도착하지 않음
Scene을 사용하면서 UIKit은 열린 URL과 유니버설 링크를 scene delegate에게 전달합니다. 더 이상 AppDelegate의 application(_:open:options:)나 application(_:continue:restorationHandler:) 메서드가 호출되지 않습니다. 만약 scene delegate가 이들을 RCTLinkingManager로 포워딩하지 않는다면, 아무것도 충돌(crash)하지는 않지만, Linking은 아예 발생하지 않습니다.
저는 이미 Scene으로 마이그레이션한 지갑 앱에서 이런 문제를 발견했습니다. 해당 앱의 zcash: 결제 링크가 앱 실행 중일 때와 링크로 앱을 시작할 때 모두 JavaScript에 도달하지 못했는데, 그 이유는 launchOptions가 nil이었고 URL이 오직 scene의 connectionOptions(zingo-mobile#1595)에서만 도착했기 때문입니다. 만약 앱이 딥 링크를 처리한다면, 앱 실행 중일 때와 앱 종료 상태일 때 모두 테스트해 보세요.
체크리스트
- Scene을 채택했는지 확인합니다 (위의
Info.plist테스트). - Expo: SDK 57에
enableSceneSupport를 사용하거나, SDK 58을 사용합니다. Bare React Native: 0.88 업그레이드를 계획합니다. - 위에 나열된 라이브러리들과 의존성을 확인하고, Xcode 27로 빌드하기 전에 업데이트하거나 패치합니다.
- Scene delegate에서 URL과 사용자 활동(user activities)을
RCTLinkingManager로 포워딩합니다. - 에이프릴(April) 이전에 충분히 Xcode 27로 빌드합니다. 앱 시작, 피커나 메뉴 또는 광고를 표시하는 모든 화면, 그리고 콜드 스타트 시의 딥 링크를 테스트합니다.
한 명령어에 앱 확인하기
npx nativekeel
이 명령어는 앱이 Scene 생명주기를 채택했는지, 설치된 라이브러리 중 어떤 것이 AppDelegate에서 window를 읽어오는지, 그리고 URL 핸들러가 여전히 AppDelegate에만 있는지 보고합니다. 이 명령어는 계정 없이 로컬에서 실행되며 코드를 업로드하지 않습니다. 만약 이미 충돌이 발생했다면:
npx nativekeel explain "-[AppDelegate window]: unrecognized selector"
AI 에이전트를 사용하여 업그레이드하는 경우에도 MCP를 통해 동일한 검사가 가능하며, Claude Code 플러그인(claude plugin marketplace add AlicanAkyol/nativekeel)이 있으므로 에이전트는 버전 변경을 시작하기 전에 이를 확인합니다. 출처가 포함된 더 자세한 참고 자료는 nativekeel.com/fix/uiscene-life-cycle-required-xcode-27에서 확인할 수 있습니다.
위에 언급된 라이브러리 중 하나를 유지 관리하고 수정 작업에 도움이 필요하다면, PR(Pull Request)을 보내드리겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기