스토어 문구에 "워치 앱 지원"이라고 쓰기 전에, 정말 되는지 확인하기로 했습니다. 워치 앱이 딸린 앱이 8개였습니다.
7개는 실배선이었습니다. 1개는 아니었습니다.
당신의 앱에 딸린 부속 앱(워치·위젯·확장), 마지막으로 "실제로 값이 오가는지" 확인한 게 언제입니까?
화면은 정상이었습니다
문제의 앱은 수면 회복 점수를 보여주는 앱입니다. 워치 화면에는 숫자가 떠 있고, "저장" 버튼도 있고, 레이아웃도 멀쩡합니다. 스크린샷만 보면 완성품입니다.
코드를 열었습니다.
@State private var score: Int = 72 // 모든 사용자에게 같은 숫자
Button("common.save") { /* WCSession message to phone */ } // 본문이 주석뿐회복 점수가 상수였습니다. 모든 사용자에게 72가 떴습니다. 기준선 데이터가 없어서 점수가 존재할 수 없는 신규 사용자에게도 72가 떴습니다.
정본 로직인 RecoveryScorer.score는 기준선이 7박 미만이면 nil을 돌려줍니다. 즉 이 화면은 "점수 없음"이 정답인 사용자에게도 숫자를 보여주고 있었습니다.
"저장" 버튼은 본문이 주석 한 줄이었습니다. 빠르게 읽으면 그 줄이 코드인 줄 알고 넘어갑니다.
보내는 쪽은 성공했다고 적습니다
결정적인 건 따로 있었습니다. 워치 타깃에 WCSessionDelegate가 아예 없었습니다.
폰은 "스마트 알람 예약" 메시지를 매번 보내고 있었습니다. 받는 곳이 없었습니다. 그리고 보내는 쪽은 그 사실을 알 방법이 없습니다 — 전송은 성공으로 기록되고, 로그는 깨끗합니다.
받는 쪽이 없다는 것은 보내는 쪽의 로그에 절대 안 나타납니다.
여기서 선택이 갈립니다
같은 패턴(폰-워치 동기화)을 가진 앱이 8개입니다. 하나가 껍데기로 드러났습니다.
당신이라면 나머지 7개를 다시 열어보겠습니까, "이건 예외"로 닫겠습니까?
저는 다시 열었습니다. 7개는 전부 실배선이었습니다 — 즉 하나가 껍데기라고 나머지를 의심할 근거는 안 됐습니다. 반대로, "워치 앱이 있으니 당연히 되겠지"로 이 하나를 건너뛸 뻔했습니다. 8개를 다 연 이유는 의심이 아니라 문구를 쓰기 전이라는 이유 하나였습니다.
고치면서 두 개를 더 찾았습니다
"워치 앱 없음"보다 찾기 어려운 게 "워치 앱은 있는데 갱신이 끊기는" 문제입니다. 화면이 가끔은 맞는 값을 보여주니까요.
WatchSessionRelay.send가 도달 가능성만 보고sendMessage만 썼습니다. 이건 워치 앱이 떠 있을 때만 닿는 경로입니다. 백그라운드면 메시지가 사라집니다.updateApplicationContext를 항상 같이 갱신해야 재실행 때라도 읽힙니다.- 알람 설정 전달이 "기능이 켜져 있는가" 확인 뒤에 있었습니다. 사용자가 알람을 끄면 워치는 그 사실을 영영 못 듣고, 꺼진 알람을 켜진 것으로 계속 보여줍니다.
하나 더 배운 게 있습니다. 폰의 유료 설정을 워치에서 쓰기 가능하게 노출하면 그 자체가 페이월 우회입니다 — 구매 상태는 폰에만 있는데 워치에서 값을 바꿀 수 있으면, 워치만 쓰는 사람은 사실상 무료입니다. 워치 알람 화면은 읽기 전용으로 바꿨습니다.
자가진단
- 부속 앱의 화면 값이 상수인지 확인해봤습니까? 스크린샷으로는 구분이 안 됩니다.
- 메시지를 보내는 타깃 말고, 받는 타깃에 델리게이트가 실제로 있습니까?
- 심볼 존재 확인은 최상위 실행 파일이 아니라
.debug.dylib에서 해야 합니다. 최상위만 보면 심볼 0으로 나와서 "코드가 없다"는 오판이 납니다.
솔직한 부분
이 앱이 언제부터 이 상태였는지는 모릅니다. 커밋 이력상 워치 타깃이 처음부터 플레이스홀더로 시작해 그대로 나간 것으로 보이지만, 확정할 방법이 없습니다. 그래서 "몇 달째"라고 쓰지 않습니다.
수정 버전은 심사 대기 중이고, 같은 버전에서 스토어 문구에 있던 다른 거짓 주장도 함께 뺐습니다 — 그건 세 문장이 거짓이었던 이야기로 따로 썼습니다.
당신 앱에 딸린 부속 화면 하나를 지금 열어서, 거기 뜨는 숫자가 어디서 오는지 한 줄만 따라가 보세요.