"빌드 설정은 의도를 말할 뿐이고, 번들이 사실이야."
번들 하나, 산출물 셋
가족 워치 앱은 App Store Connect의 별도 앱이 아니야. iOS 앱의 Watch 폴더 안에 들어가는 watchOS 애플리케이션 타깃이고, 컴플리케이션은 그 워치 앱 안에 품은 WidgetKit 확장이야. 아카이브도 빌드 번호도 TestFlight 업로드도 하나씩이고, 워치 앱은 폰을 거쳐 설치돼. XcodeGen으로 옮기면 타깃 셋에 embed: true 의존성 둘이야. 번들 식별자는 폰 것 뒤에 이어 붙여서 워치 앱은 …mobile.watchkitapp, 컴플리케이션은 거기에 또 붙인 …watchkitapp.widgets야. 워치 앱 Info.plist는 WKCompanionAppBundleIdentifier로 짝 앱을 가리켜. 이 레슨을 쓰면서 아이폰 시뮬레이터용으로 빌드해보니 산출물이 딱 그 모양이었어. SparkMobile.app/Watch/Spark.app이 있고, 그 안에 SparkWatchWidgets.appex가 들어 있었지.
올리고 나서야 거절된 업로드
일지 앱에 워치 앱을 넣은 첫 TestFlight 빌드는 업로드까진 성공했는데, 그 뒤에 App Store Connect가 거절했어. 워치 애플리케이션에 아이콘이 없고, Info.plist에 CFBundleIconName이 빠졌다고. 로컬에선 전부 안심되는 신호뿐이었어. 빌드는 초록불이었고, 워치 번들엔 Assets.car가 있었고, 아이콘을 가리키는 빌드 설정도 켜져 있었어. 근데 GENERATE_INFOPLIST_FILE은 watchOS 타깃에 CFBundleIconName을 안 써줘. 빌드된 워치 앱에 PlistBuddy를 돌려보면 바로 확인돼. 해결책은 두 가지야. 하나는 CFBundleIconName, WKApplication, 짝 앱 식별자를 담은 워치 타깃 전용 Info.plist고, 다른 하나는 업로드 관문의 아카이브 검사야. 이 검사는 워치 앱이 들어 있으면 그 최상위 키와 컴파일된 카탈로그, 컴플리케이션 확장이 실제로 있어야 통과시켜.
워치 없이 보기
네이티브 Pippa에서 워치 스킴만 -sdk watchsimulator로 빌드하니까 컴파일에 들어가기도 전에 "Multiple commands produce …/Pippa.app/Pippa"로 실패했어. -sdk는 스킴이 건드리는 모든 타깃에 적용돼서, iOS 앱도 워치 SDK로 빌드됐고 같은 제품 이름의 워치 앱과 부딪혔거든. 그러니까 iOS 스킴을 아이폰 시뮬레이터용으로 빌드해. 안에 든 워치 앱은 simctl install로 켜져 있는 워치 시뮬레이터에 바로 설치되고, 페어링도 필요 없어. 인터페이스를 보기엔 이걸로 충분하지만, 받아쓰기가 걸린 일엔 모자라. 그건 진짜 워치에서만 증명돼.
케이블로 연결한 진짜 워치도 빌드 확인일 뿐이야
진짜 워치에 케이블로 설치하려면 관문 셋을 따로 넘어야 했어. 첫째는 워치 자체 설정에서 켜는 개발자 모드야. 둘째는 워치 타깃 스킴인데, 이게 있어야 xcodebuild가 워치를 빌드 대상으로 지정해 등록할 수 있어. 셋째로 Xcode 기기 계층이 워치를 알아봐야 하는데, xcrun xcdevice list에 available로 뜨면 된 거야. 이 중 어느 것도 배포는 아니야. 배포는 TestFlight가 맡아. 릴리스처럼 서명해서 폰을 거쳐 워치 앱을 설치하지.