Skip to content

fix: iOS 27에서 앱이 실행되지 않는 문제를 UIScene 생명주기로 고친다 - #47

Merged
seongwon030 merged 1 commit into
mainfrom
seongwon030/ios-beta-app-crash
Oct 16, 2026
Merged

seongwon030 merged 1 commit into
mainfrom
seongwon030/ios-beta-app-crash

Conversation

@seongwon030

@seongwon030 seongwon030 commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

무엇

iOS 27 베타 기기에서 앱이 열리지 않는 문제를 고칩니다. 앱을 UIScene 생명주기로 전환합니다.

  • ios/app/SceneDelegate.swift (신규): AppDelegate가 띄운 RN window를 Scene에 붙이고, 들어온 URL은 AppDelegate의 기존 메서드로 위임합니다
  • ios/app/Info.plist: UIApplicationSceneManifest 추가
  • ios/app.xcodeproj/project.pbxproj: SceneDelegate를 앱 타깃에 등록
  • plugins/withIosPrebuildFixes.js, plugins/ios-prebuild-support/SceneDelegate.swift: expo prebuild 뒤에도 위 변경이 다시 생성되도록 함

왜

iOS 27 SDK로 빌드한 앱은 실행 시점에 UIKit이 Scene 매니페스트를 검사합니다. 우리 앱은 매니페스트 없이 AppDelegate가 UIWindow(frame:)로 창을 직접 띄우는 예전 방식이라, 이 검사(UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption)에서 EXC_BREAKPOINT로 멈춥니다.

  • 참고: https://newland435.tistory.com/139 , https://stackoverflow.com/questions/80004869
  • 문제의 빌드 22는 Xcode Cloud prod 워크플로에서 Xcode 27 (27A266a) / macOS 27 (26A428)로 빌드됐습니다. 즉 iOS 27 SDK로 링크되어 강제 조건에 해당합니다 (Xcode Cloud 빌드 상세에서 확인).
  • iOS 26.6.1 기기에서는 같은 TestFlight 빌드(1.7.2 / 22)가 크래시 없이 정상 실행되는 것을 확인했습니다. iOS 27에서만 생기는 문제라는 근거입니다.
  • Expo SDK 54의 ExpoAppDelegate에는 Scene 지원이 없어서, SDK 업그레이드 대신 SceneDelegate를 직접 추가했습니다.

설계: 기존 동작을 바꾸지 않기 위한 선택

  • URL 처리는 AppDelegate 한 곳에 둡니다. Scene 방식에서는 iOS가 AppDelegate의 open url과 continue userActivity를 호출하지 않습니다. 그래서 앱이 꺼진 상태에서 들어온 URL(connectionOptions)과 켜진 상태에서 들어온 URL(Scene 콜백)을 모두 AppDelegate의 기존 메서드로 넘깁니다. 그러면 firebaseauth 필터 → Expo 구독자 → RCTLinkingManager 순서가 그대로 유지됩니다.
    • SO 답변처럼 RCTLinkingManager를 직접 호출하면 expo-linking 구독자를 건너뜁니다. 그러면 expo-router가 읽는 ExpoLinkingRegistry.initialURL이 비어서, 앱이 꺼진 상태에서 딥링크로 열 때 홈으로 떨어집니다.
  • window를 재사용합니다. RNFB messaging과 expo-system-ui가 delegate.window를 읽기 때문에 같은 객체를 써야 합니다.
  • AppDelegate 생명주기 콜백(applicationDidBecomeActive 등)은 Scene 방식에서 호출되지 않습니다. 하지만 우리 의존성 중 이 콜백을 쓰는 곳은 없습니다. RN AppState, Mixpanel, RNFB는 UIApplication*Notification을 구독하는데, 이 알림은 Scene 방식에서도 계속 나옵니다.
  • 푸시 탭은 UNUserNotificationCenter 경로라 Scene과 관계가 없습니다.

검증

iOS 26.2 시뮬레이터에서 Release 빌드로 확인했습니다. Scene 매니페스트가 있으면 iOS 26에서도 Scene 방식으로 실행되므로, 기존 동작이 깨지는지는 iOS 26에서 확인할 수 있습니다.

테스트 결과
일반 실행 (스플래시 → 홈 웹뷰) ✅
앱 켜진 상태에서 딥링크 moadongapp://modal ✅ modal로 이동
백그라운드 → 복귀 ✅ PID 유지, 화면 유지
앱 꺼진 상태에서 딥링크 moadongapp://modal ✅ modal로 바로 진입
expo prebuild 재생성 (임시 복사본) ✅ SceneDelegate, 매니페스트, pbxproj 항목이 동일하게 생성됨

Scene 방식에서는 iOS가 AppDelegate의 open url을 직접 호출하지 않습니다. 따라서 딥링크가 동작했다는 것은 SceneDelegate의 위임이 작동한다는 증거입니다.

아직 확인하지 못한 것

  • iOS 27 베타 기기에서 TestFlight 빌드가 실행되는지. 이 PR의 목적이며, 로컬에는 iOS 27 환경이 없습니다.
  • 유니버설 링크 https://www.moadong.com/...: 시뮬레이터 빌드가 서명되지 않아 테스트하지 못했습니다
  • 푸시 알림 탭으로 진입: 코드 경로상 Scene과 무관하지만 실기기에서 확인이 필요합니다

참고 (리뷰에서 나온 것)

  • Scene 방식에서는 RN의 Linking.getInitialURL()이 앱이 꺼진 상태에서 들어온 URL을 받지 못합니다(launchOptions에 URL이 없음). 지금 앱은 expo-router의 getLinkingURL()만 쓰므로 영향이 없습니다. 앞으로 초기 URL은 getLinkingURL 계열로 읽어야 합니다.
  • ios/app/SceneDelegate.swift와 plugins/ios-prebuild-support/SceneDelegate.swift는 같은 파일입니다. 기존 support 파일들과 같은 패턴이며, 한쪽만 고치면 prebuild 때 덮어써집니다.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • 개선 사항
    • iOS 앱이 단일 화면(Scene) 구성을 사용하도록 설정했습니다.
    • 화면 연결 시 URL 및 사용자 활동을 기존 앱 처리 흐름으로 전달합니다.

iOS 27 SDK로 링크된 앱은 실행 시점에 UIKit이 Scene 매니페스트를 검사한다.
우리 앱은 매니페스트 없이 AppDelegate가 UIWindow(frame:)로 창을 직접 띄우는
예전 방식이라 이 검사(UIApplicationEvaluateRuntimeIssueForNoSceneLifecycleAdoption)
에서 EXC_BREAKPOINT로 멈추고, iOS 27 베타 기기에서 앱이 열리지 않았다.
iOS 26.6.1 기기에서는 같은 TestFlight 빌드(1.7.2/22)가 정상 실행됐다.

- SceneDelegate를 얇은 어댑터로 추가한다. RN은 지금처럼 didFinishLaunching에서
  시작하고, 그 window를 연결된 UIWindowScene에 붙인다. window를 새로 만들지
  않는 건 RNFB messaging·expo-system-ui가 delegate.window를 읽기 때문이다
- Scene 생명주기에서는 iOS가 AppDelegate의 open url / continue userActivity를
  부르지 않는다. cold(connectionOptions)·warm(scene 콜백) URL을 모두 AppDelegate의
  기존 메서드로 위임해 firebaseauth 필터 → Expo 구독자 → RCTLinkingManager
  순서를 그대로 탄다. RCTLinkingManager를 직접 부르면 expo-linking 구독자를
  건너뛰어 expo-router가 읽는 ExpoLinkingRegistry.initialURL이 비고, cold start
  딥링크가 홈으로 떨어진다
- withIosPrebuildFixes가 매니페스트와 SceneDelegate를 생성하게 해서
  expo prebuild --clean 뒤에도 유지된다

Expo SDK 54의 ExpoAppDelegate에는 Scene 지원이 없어 SDK 업그레이드 대신 이 방식을 택했다.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 93bc4f32-ec48-4712-bc98-a94533f384fc
📥 Commits

Reviewing files that changed from the base of the PR and between bd696e9 and 1c742fe.

📒 Files selected for processing: 5
  • ios/app.xcodeproj/project.pbxproj
  • ios/app/Info.plist
  • ios/app/SceneDelegate.swift
  • plugins/ios-prebuild-support/SceneDelegate.swift
  • plugins/withIosPrebuildFixes.js

Included review availability: This review used your included allowance. Your plan provides up to 1 included review per hour; 0 remain after this review.

Walkthrough

iOS Scene 지원을 추가했습니다. SceneDelegate가 window를 Scene에 연결하고 URL 및 user activity 처리를 AppDelegate에 전달합니다. 설정 플러그인과 앱 프로젝트에 Scene manifest와 SceneDelegate 파일 등록을 추가했습니다.

Changes

iOS Scene 지원

Layer / File(s) Summary
Scene 이벤트 전달
ios/app/SceneDelegate.swift, plugins/ios-prebuild-support/SceneDelegate.swift
Scene 연결 시 AppDelegate의 window를 Scene에 연결해 표시합니다. 연결 옵션의 URL과 user activity를 각 AppDelegate 처리 메서드에 전달합니다.
SceneDelegate 및 manifest 등록
plugins/withIosPrebuildFixes.js, ios/app/Info.plist, ios/app.xcodeproj/project.pbxproj
설정 플러그인이 Scene manifest를 적용하고 SceneDelegate 파일을 생성 프로젝트에 추가합니다. 앱 프로젝트의 Info.plist와 Sources 빌드 단계에도 Scene 설정과 파일 참조를 등록합니다.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix

Sequence Diagram(s)

sequenceDiagram
  participant iOS as iOS Scene
  participant SceneDelegate
  participant AppDelegate
  iOS->>SceneDelegate: Scene 연결 옵션 전달
  opt URL이 있는 경우
    SceneDelegate->>AppDelegate: application(_:open:options:) 호출
  end
  opt user activity가 있는 경우
    SceneDelegate->>AppDelegate: application(_:continue:restorationHandler:) 호출
  end
Loading

Suggested reviewers: seonghoonc

Merge Risk: ⚪ Minimal · up to 1c742

No concrete issue remains that would block merging after normal checks. iOS 27 device validation is still outstanding.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. (2 skipped: 2… Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Description Check Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check Passed 제목은 iOS 27에서 발생하는 앱 실행 문제와 UIScene 생명주기 전환이라는 핵심 변경을 정확히 설명합니다.
Full details: Docstring Coverage

Explanation

Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 10 functions across 3 files. (2 skipped: 2 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seongwon030 seongwon030 self-assigned this Oct 15, 2026
@seongwon030
seongwon030 merged commit 8932ad5 into main Oct 16, 2026
2 checks passed
@seongwon030 seongwon030 mentioned this pull request Oct 16, 2026
6 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant