"최악의 경우에는 워치에 폰을 깨워야 한다는 알림이라도 보여줘야 무슨 일인지 알지."
초인종이 사주는 건 몇 초야
WatchConnectivity 레슨의 초인종은 진짜로 울려. 짝 iOS 앱이 꺼져 있으면 sendMessage가 켜주거든. 그런데 이번 달 가족의 대화 앱이 그 초인종의 한계에 두 번 부딪혔어. 둘 다 SDK 헤더에 적혀 있긴 해. 찾을 줄 알면 말이야. 하나, 메시지는 보내는 앱이 돌고 있고 상대에게 닿을 수 있을 때만 가. 가족 워치도 폰이 닿는다고 볼 때만 초인종을 울렸어. 그러니 말하고 나서 말끝을 기다리는 3초 사이에 팔을 내리면, 녹음이 준비됐을 땐 그 순간이 이미 지나 있어. 녹음 파일은 초인종 없이 폰의 WatchConnectivity 데몬까지만 갔고, 2분 뒤 폰 잠금을 풀 때까지 거기서 기다렸어. 둘, 초인종으로 깨어난 폰 앱이 받는 백그라운드 시간은 짧아. 가족이 재본 바로는 30초쯤이고, 받는 쪽은 그 시간을 백그라운드 작업으로 붙잡아 둬. Master Chief 턴 하나가 답하는 데 26초가 걸렸고, 그걸 실어 나르는 스트림까지 더하니 그 시간을 넘겼어. 앱은 스트림 도중에 멈췄고, 다시 깨워야 할 푸시는 아무것도 못 깨웠어. 손목은 누가 폰 앱을 직접 열 때까지 3분 동안 "Pippa is thinking…"만 띄웠고.
정리하면 초인종은 폰을 깨울 뿐이고, 깨어 있게 해주진 않아. 한 번 울리고 기다리는 설계는 주고받는 일 전체가 빌린 몇 초 안에 끝난다는 데 거는 거야.
받을 게 있는 쪽이 물어
답은 기다리기를 그만두는 거야. 워치가 받을 게 남아 있는 동안엔(답장이든, 답장의 목소리든) 당겨 와. 15초마다 작은 파일 하나를 transferFile로 보내고 옆에 초인종도 울려. 그 초인종 덕에 폰의 받는 쪽은 파일이 도착할 때까지 백그라운드 시간을 붙잡아. 폰은 당길 때마다 요청이 어디 있는지 대답해. 처리 중, 대기 중, 닿을 수 없음과 그 이유, 답함, 답을 소리 내 읽는 중 가운데 하나야. 깨어 있는 폰은 매번 대답하니까, 답이 오래 걸려도 침묵처럼 보이지 않아. 손목을 들면 다음 15초를 기다리지 않고 바로 당겨. 15분이 넘은 요청은 더 안 당겨. 끝내 안 온 답장은 폰의 대화 기록에서 따질 일이거든.
대답이 끊기면 어떡하냐는 건 주인 요청이 정했어. 폰에서 60초 동안 아무 말이 없으면, 그러니까 네 번 당겼는데 다 대답이 없으면 워치가 손목을 톡 치고 그렇다고 말해. "No word from the iPhone. Open Pippa on the iPhone to wake it." 같은 문장이 로컬 알림으로도 예약돼 있어. 시각은 당기기 한 번만큼 더 뒤, 그러니까 폰의 마지막 말에서 75초 뒤야. 워치는 당길 때마다 다시 예약하지만, 시각이 뒤로 밀리는 건 폰에서 새 말이 왔을 때뿐이야. 돌고 있는 워치는 60초에 말하고, 자기가 멈춰서 못 당긴 워치도 그 15초 뒤엔 알림으로 그 말을 해. "Thinking"은 폰에 대한 주장이야. 워치가 그 주장을 받쳐줄 수 없으면, 실제로 아는 걸 말해야지.
그만 물을 때까지 다시 말해
같은 함정이 폰 쪽에도 있어. 폰이 답장을 AirPods로 읽어줄 땐 워치한테 두 번 알려. 읽기 시작할 때 한 번, 끝날 때 한 번. 한 번씩만 보내면 그 순간 멈춰 있던 워치는 어느 쪽이든 놓칠 수 있어. 끝 소식을 놓친 워치는 긴 대비 타이머가 다 될 때까지 "Thinking"에 머물러. 그래서 이제 폰은 요청마다 마지막으로 한 말을 최근 20개까지 들고 있다가, 워치가 그만 물을 때까지 당길 때마다 그 말을 되풀이해. 되풀이가 안전하려면 두 가지가 필요해. 하나, 되풀이에 찍는 시각은 다시 말한 때가 아니라 읽기가 시작되거나 끝난 때야. 늦게 받은 "읽는 중"도 그게 사실이 된 순간부터 세야 하거든. 둘, 워치는 이미 아는 읽기는 무시해. 그래서 되풀이가 턴의 끝을 뒤로 미는 일은 없어. 중요한 말은 사건이 아니라 상태야. 받는 쪽이 필요한 만큼 몇 번이고 물어볼 수 있어야 해.
연결이 올라올 때 읽어
Pathfinder 워치는 폰의 길 안내를 receivedApplicationContext로 읽어 보여줘. 앱이 켜질 때와 앞으로 나올 때 읽지. 세비야를 걷다가 워치가 돌고 있는 안내를 못 받아오게 됐는데, 손목 쪽 코드는 일주일째 바뀐 게 없었어. 문제는 두 번의 읽기가 다 세션이 활성화되기 전에 돌 수 있다는 거였어. 활성화 안 된 세션엔 읽을 게 없어. 그렇게 빈손으로 끝난 읽기는 폰이 새로 내보낼 때까지 빈 채로 남았고, 주머니에서 멈춘 폰은 끝내 안 내보냈지. 고친 방법도 앞의 두 갈래랑 똑같아. 워치는 활성화가 끝나면 한 번 더 읽어. 그리고 앞에 나와 있는데 가진 상태가 없거나 10초보다 오래됐으면, 폰에 닿는 동안 많아야 10초에 한 번 당겨. 자세한 얘기는 Pathfinder 퀘스트에 있어.
행동은 아웃박스로, 사실은 컨텍스트로
Pathfinder 워치는 "내 길 안내 세션 살아 있어"라는 말도 30초마다 담기 레인으로 보냈어. 아웃박스 레슨에서 본 그 오래가는 아웃박스 말이야. 폰이 멀리 있으니 줄이 1분에 두 개씩 불었고, 활성화될 때마다 쌓인 걸 통째로 다시 내밀었어. 담기 레인은 행동을 위한 길이야. 한 번 일어났고 한 번 도착해야 하는 것들. 존재 신호, 설정, 위치처럼 최신값만 의미 있는 사실은 updateApplicationContext로 보내. 이건 가장 최근 값만 남겨. 이걸 부른다고 델리게이트를 따로 둘 필요는 없어서, 세션이 붙잡아 둔 델리게이트 하나가 그대로 유일하게 남아. 다만 뭔가 보내려면 그 델리게이트가 설치되고 세션이 활성화돼 있어야 하는 건 똑같아. 받는 쪽은 결정하는 바로 그 순간에 읽고, 얼마나 신선한지는 사실에 붙은 시각으로 판단해.
깨어 있으려면 iOS가 인정하는 이유가 있어야 해
당기기로는 폰의 관심을 몇 초씩 얻을 수 있어. 근데 대화 내내 폰이 깨어 있어야 하는 경우도 있어. 백그라운드에서 읽기를 시작하려면 그래. iOS는 시스템이 인정하는 일을 하는 동안에만 앱을 백그라운드에서 계속 돌게 둬. Pathfinder가 길 안내 중에 멈추지 않는 건 프로세스가 위치 구독을 쥐고 있어서야. 대화 앱에선 이걸 주인이 직접 정했어. 동의를 거친 제품 결정이야. 손목 대화가 이어지는 동안 폰은 일반 위치 업데이트를 돌리고, 받은 위치는 버려. 손목에서 무엇이든 당기면 시작하고, 대화가 끝나고 30초 뒤나 5분 동안 조용하면 멈춰. 그러려고 앱은 '항상' 위치 권한을 한 번 물었어. 주인이 고르고 기록해둔 개인정보와 배터리의 맞바꿈이야. 같은 결정 없이 베껴 쓸 요령은 아니야.