오늘도 공부
애플 앱스토어 심사 규칙 2.5.19 정리: 숨겨진 기능 금지와 iOS 개발자의 6가지 대응법 본문
2026년 10월 16일, 애플이 App Store 심사 지침에 2.5.19 항목을 추가했습니다. 핵심은 단순합니다. 심사관과 사용자에게 알리지 않은 기능을 앱 안에 숨겨두지 말라는 것입니다.
그런데 이 소식이 알려지면서 iOS 개발자들 사이에서는 다른 질문이 나왔습니다. 그렇다면 개발 중 사용하는 디버그 메뉴, 가짜 결제 서비스, 원격 기능 플래그(feature flag)는 어떻게 관리해야 할까?
개발자 Luka Shaw가 X에 공유한 글과 후속 토론에는 실무적인 대응법 6가지가 모였습니다. 이 글에서는 애플 공식 규칙과 개발자 커뮤니티의 제안을 구분하고, 실제 배포 파이프라인에 적용할 수 있도록 정리합니다.
먼저 결론: 테스트 코드는 출시 앱에서 분리하고, 심사 대상 기능은 리뷰어가 확인할 수 있게 하며, 최종 Release 빌드를 CI에서 검증해야 합니다. 단, 이 절차를 거쳤다는 이유만으로 심사 승인이 보장되는 것은 아닙니다.
1. App Review Guideline 2.5.19는 무엇인가?
애플은 2026년 10월 16일 심사 지침 업데이트에서 숨겨진(hidden), 비활성 상태(dormant), 문서화되지 않은(undocumented) 기능에 관한 오랜 정책을 새로운 2.5.19 항목으로 명확히 분리했습니다. 이전에는 관련 내용이 2.3.1에 포함되어 있었습니다.
애플 공식 업데이트 공지에 따르면 이는 갑자기 생긴 완전히 새로운 금지 원칙이라기보다, 기존 정책을 명확하게 표현한 개정입니다.
심사 지침 원문에서 강조하는 내용은 다음과 같습니다.
- 앱의 기능은 사용자와 App Review 심사팀에 분명해야 합니다.
- 숨겨진 기능이나 미공개 기능을 실행 파일 안에 숨기는 행위는 문제가 됩니다.
- 코드 난독화 등으로 미공개 기능을 은폐하려는 시도는 앱 삭제와 개발자 계정 종료 사유가 될 수 있습니다.
여기서 오해하지 말아야 할 점이 있습니다. 2.5.19가 모든 feature flag 자체를 일률적으로 금지한다고 명시한 것은 아닙니다. 하지만 플래그 뒤에 심사관이 확인할 수 없는 기능이 숨어 있다면 위험해질 수 있습니다. 반대로 심사 노트에 적었다고 해서 모든 비활성 기능이 자동으로 허용되는 것도 아닙니다.
2. 실무 대응법 ① 테스트 스위치는 #if DEBUG로 격리
개발 중에는 결제 없이 프리미엄 기능을 열거나, 디버그 화면을 호출하는 기능이 필요할 때가 있습니다.
문제는 그런 코드가 실제 사용자에게 배포하는 Release 빌드에 포함되는 경우입니다.
Swift에서는 컴파일 조건을 이용해 개발 전용 구현을 분리할 수 있습니다.
#if DEBUG
let purchaseService: PurchaseService = MockPurchaseService()
#else
let purchaseService: PurchaseService = StoreKitPurchaseService()
#endif
이 코드는 구조를 설명하는 예시입니다. PurchaseService 프로토콜과 각 구현은 별도로 정의해야 합니다. 특히 MockPurchaseService의 구현 자체도 #if DEBUG 안이나 별도의 테스트 타깃에 두어야, 의도한 대로 Release에서 제외할 수 있습니다.
반드시 확인할 사항은 두 가지입니다.
- 개발 전용 클래스와 우회 기능이 Release 산출물에 남지 않았는가?
- 반대로 실제 StoreKit 결제 구현은 Release에 제대로 포함되었는가?
컴파일 조건이 있는 것과 출시 앱에 실수로 테스트 기능이 들어가지 않는 것은 별도의 검증 문제입니다.
3. 실무 대응법 ② CI에서 최종 .app을 자동 검사
코드를 확인하는 것만으로는 부족할 수 있습니다. 공유 Swift Package나 다른 프레임워크에서 테스트용 기능이 유입될 가능성도 있기 때문입니다.
다음은 빌드된 .app 번들에서 금지 문자열을 탐색하는 아주 간단한 검사 예시입니다.
#!/usr/bin/env bash
set -euo pipefail
APP_PATH="${1:?Usage: check-release.sh path/to/App.app}"
DENY='DebugMenu|MockPurchaseService|ForcePremium|BypassPayment'
if LC_ALL=C grep -R -a -l -E "$DENY" "$APP_PATH"; then
echo "ERROR: Release bundle contains a denylisted token."
exit 1
fi
echo "PASS: No denylisted token was found."
Release 빌드가 끝난 뒤 CI에서 다음과 같이 호출하면 됩니다.
bash scripts/check-release.sh build/Release-iphoneos/MyApp.app
이 검사는 문자열 기반의 보조 안전장치입니다. 이름이 최적화 과정에서 사라지면 놓칠 수 있고, 리소스에 같은 문자열이 들어 있어도 탐지될 수 있습니다. 따라서 '검색 결과 없음 = 애플 규정 준수 완료'로 해석하면 안 됩니다.
실무에서는 소스 정적 검사, 최종 번들 검사, Release UI 테스트를 함께 사용하는 편이 더 안전합니다.
4. 실무 대응법 ③ 구매 모킹 대신 .storekit 파일 사용
앱 내부에 가짜 결제 성공 스위치를 추가하는 대신, Xcode가 제공하는 StoreKit 테스트 환경을 활용할 수 있습니다.
설정 흐름은 다음과 같습니다.
- Xcode 프로젝트에 StoreKit Configuration File을 생성합니다.
- 테스트할 상품과 구독을 설정합니다.
- Product → Scheme → Edit Scheme → Run → Options로 이동합니다.
- StoreKit Configuration에서 .storekit 파일을 선택합니다.
- 시뮬레이터 또는 지원되는 테스트 환경에서 구매와 구독 갱신, 실패 등을 검증합니다.
이 방식은 앱에 별도의 결제 우회 UI를 넣지 않고도 실제 StoreKit API를 호출하는 구매 흐름을 검사할 수 있다는 장점이 있습니다. .storekit은 Xcode 테스트 설정용 파일이며, 배포 번들에 불필요하게 포함되지 않았는지는 실제 패키징 설정도 확인해야 합니다.
다만 로컬 StoreKit 테스트만으로 출시 준비가 끝나는 것은 아닙니다. App Store Connect의 실제 상품 정보와 연동하는 Sandbox 및 TestFlight 테스트도 병행하는 것이 좋습니다.
공식 문서: Setting up StoreKit Testing in Xcode · Testing In-App Purchases with sandbox
5. 실무 대응법 ④ 서버에서 켜지는 기능은 심사 노트에 설명
실무 앱에는 서버가 기능의 활성화 여부를 결정하는 경우가 많습니다.
- 특정 지역에만 노출되는 기능
- 일부 사용자에게 먼저 공개하는 실험 기능
- 원격 설정으로 구성되는 구매 화면
- 관리자 승인이 있어야 보이는 메뉴
문제는 이런 기능이 심사 때는 보이지 않다가 출시 후에만 나타날 수 있다는 점입니다.
App Store Connect의 App Review Notes에는 심사에 필요한 기능 설명, 특수 설정, 테스트 방법 등을 기록할 수 있습니다. 애플 공식 도움말에도 관련 안내가 있습니다.
다만 개발자 커뮤니티의 '원격 가격 실험은 괜찮다'는 의견을 애플의 포괄적인 허용 선언처럼 받아들이면 안 됩니다. 결제 가격과 상품 표시는 별도의 인앱결제 정책 및 StoreKit 요구사항을 지켜야 하고, 미심사 기능을 노트에 적기만 하면 허용되는 것도 아닙니다.
공식 자료: App Store Connect – Review Information
6. 실무 대응법 ⑤ 심사관이 실제 기능을 확인할 수 있게 한다
로그인이 필요한 앱이라면 리뷰어용 계정을 제공해야 합니다. 가능하다면 심사 대상 주요 기능을 직접 확인할 수 있는 상태로 준비하는 것이 중요합니다.
아래는 App Review Notes에 적을 수 있는 설명의 예시입니다.
App Review Notes
1. Demo account
Credentials are provided in the App Review Information fields.
2. Remote configuration
The app uses remote configuration for staged rollout
of the [feature name] feature.
3. Review access
The provided demo account can access the feature
through Settings > [menu].
4. Purchases
In-app purchases use Apple's StoreKit APIs.
The purchase and restore flows are available
from Settings > Subscription.
이 예시는 제출 정보의 설명 방식을 보여주는 것이지 애플이 승인한 표준 템플릿은 아닙니다. 실제 로그인 정보는 공개 블로그에 쓰지 말고 App Store Connect의 심사 정보란에 제공해야 합니다.
또한 해당 기능이 심사 대상인데도 리뷰어 계정에서 끝내 접근할 수 없다면, 노트 작성만으로 문제가 해결되지는 않습니다.
7. 실무 대응법 ⑥ 제거 검사만 하지 말고 '남아 있어야 할 기능'도 확인
가장 놓치기 쉬운 항목입니다.
개발용 코드를 제거하려다가 컴파일 조건을 잘못 작성해 실제 결제 기능까지 제외하는 실수가 발생할 수 있습니다.
따라서 Release 테스트는 양방향으로 진행해야 합니다.
| 가짜 구매 성공 처리 | 실제 StoreKit 구매 요청 |
| 결제 우회 스위치 | 구매 복원 |
| 디버그 전용 메뉴 | 정식 구독 상태 확인 |
| 프리미엄 강제 활성화 | 서버 구매 검증 및 권한 반영 |
특히 테스트용 Debug 실행이 아니라 Release 구성으로 실행하거나 TestFlight에 설치한 앱에서 실제 사용자 구매 경로를 확인해야 합니다. 실제 비용이 청구되지 않는 Sandbox 테스트 계정을 활용할 수 있습니다.
8. 출시 전 체크리스트
앱 제출 전에 아래 항목을 한 번에 확인해 보세요.
- #if DEBUG와 별도 테스트 타깃으로 개발 전용 코드를 격리했다.
- Release .app과 포함된 프레임워크를 검사했다.
- 금지 문자열 검사를 CI 파이프라인에 연결했다.
- 실제 StoreKit 결제·복원·구독 기능을 Release 환경에서 테스트했다.
- 원격 설정으로 달라질 수 있는 기능을 확인하고 필요한 내용을 심사 노트에 설명했다.
- 리뷰어가 심사 대상 주요 기능에 접근할 수 있다.
- 출시 후 앱 동작이 심사 때 공개한 기능과 일치한다.
9. 이번 변경의 진짜 의미
2.5.19를 단순히 '테스트 코드를 없애라는 새 규정'으로 이해하면 핵심을 놓치기 쉽습니다.
이번 조항은 앱을 심사받을 때 보여주는 모습과 실제 사용자에게 제공하는 기능이 일치해야 한다는 원칙을 더 선명하게 보여줍니다.
개발자 관점에서 중요한 변화는 심사 대응을 제출 직전의 수작업으로 끝내지 않는 것입니다.
- 개발 단계에서는 테스트와 제품 코드를 분리하고,
- CI 단계에서는 Release 산출물을 검사하고,
- 테스트 단계에서는 실제 구매 및 주요 기능 경로를 확인하고,
- 심사 단계에서는 원격 기능과 접근 방법을 투명하게 설명해야 합니다.
특히 원격 구성, 구독 결제, 단계적 기능 배포가 많은 앱이라면 심사 준수를 배포 파이프라인의 일부로 설계할 필요가 있습니다.
마지막으로, 이 글의 6가지 방법은 X 개발자들이 제안한 실무 관행이지 애플이 인증한 준수 절차가 아닙니다. 실제 심사 여부는 앱의 구현과 제출 맥락에 따라 판단됩니다.
참고 자료
- Apple App Review Guidelines – 2.5.19
- Apple Developer – 2026년 10월 16일 심사 지침 개정 공지
- Apple – StoreKit Testing in Xcode
- Apple – Sandbox 구매 테스트
- Apple – 심사 정보 제출 안내
- Luka Shaw의 X 정리 글 (2026년 10월 20일) — 원문의 개발자 제안 및 크레딧 참고
작성 기준: 2026년 10월 21일. 공개된 애플 공식 문서와 개발자 커뮤니티의 논의를 바탕으로 정리했습니다.
'개발상식' 카테고리의 다른 글
| 안드레 카파시가 말하는 ‘어떤 분야에서 전문가가 되는 법’ (0) | 2026.08.10 |
|---|---|
| 메인은 GPT-5.6, 서브에이전트는 DeepSeek V4 Flash로 설정하기 (0) | 2026.08.06 |
| Cerebras는 어떻게 사내 지식 검색 시스템을 구축했을까? (0) | 2026.08.05 |
| # 🚀 한눈에 정리하는 23가지 핵심 디자인 패턴 (Design Patterns for Humans) (0) | 2026.08.05 |
| Codex에서 GPT-5.6 Luna를 서브에이전트로 사용하는 방법 (0) | 2026.08.01 |
