본문 바로가기
C.W.K.
Stream
Lesson 04 of 06 · published

손목이 폰을 울려서 깨워

~15 min · watchos, watchconnectivity, measurement, idempotency

Level 0호기심
0 XP0/80 lessons0/18 achievements
0/100 XP to next level100 XP to go0% complete

문, 그리고 문이 될 자격

워치 앱은 거의 문밖에 없어. 주 문은 Talk to Pippa 야. 워치 페이스의 complication 이 그걸 열고, Action Button 은 published shortcut 을 통해 닿아. watchOS 가 허용하는 경로가 그것뿐이라서. 받아쓰기, 큐, 폰으로 넘기기.

한 번 더 작아진 적이 있어. 짝이 되는 라이브러리가 받아쓰기 문을 하나 더 제공했거든 — 지구본 달린 키보드 비슷한 입력 화면 — 그게 Other language 버튼으로 나갔어. 아빠 손목에서 바로 되돌아왔지.

other language 버튼 빼. 기능도 안 되고 의미 없어. swipe up으로 한영 전환 하는 거였어. — 아빠

한국어랑 영어는 주 문이 이미 여는 받아쓰기 화면 안에서 위로 쓸어서 바꿔. 그 두 번째 문은 제스처를 중복한 기능이었고, 다음 빌드에서 뺐어.

다음 날 두 번째 문이 정말로 생겼고, 그건 남았어. 아빠가 새로 시작할 방법을 원했거든. 기본은 기존 대화에 이어서 말하되, 가끔은 맥락을 초기화하고 싶을 때가 있으니까. 그래서 주 버튼 아래에 Talk in a new chat 이 있어. 메시지에 플래그 하나를 켜서 보내면, 폰이 새 대화를 만들고 그게 그때부터 손목의 스레드가 돼. 두 추가 문의 차이가 규칙 전부야. 언어 버튼은 주 문이 이미 할 수 있는 걸 했고, 새 대화 버튼은 주 문이 못 하는 딱 한 가지를 해. 문은 그것만 할 수 있는 일로 자리를 벌어.

512초

이 레슨이 존재하는 이유인 측정이 여기 있어. 아빠가 폰을 주머니에 잠긴 채로 두고 워치에 대고 말했어. 그게 512초 동안 안 갔어 — 직접 앱을 열 때까지.

아이폰에서 앱을 열어두고 해야 연동이 되면 워치 앱은 사실상 쓸모가 없어져 — 아빠

원인은 아무도 대조 안 한 믿음이었어. 코드가 큐 전송 문을 썼고, iOS 가 전송을 배달하려고 백그라운드에서 앱을 띄워준다고 가정했어. 프레임워크 문서는 평범한 말로 다르게 적어놨어. 큐 전송은 다음 실행 때 배달된다고. 상대 앱을 도착 시점에 띄우는 건 즉시 전송 쪽뿐이고.

문이 둘, 보장이 둘. 전송은 질겨 — 워치가 범위를 벗어나도 살아남고, 워치는 전송이 닿았다고 보고할 때만 자기 사본을 놔줘. 즉시 전송은 빠른 대신 휘발돼. 손목한테 필요한 건 둘 중 어느 하나만이 아니야.

그래서 워치는 이제 둘 다 해. 전송하고, 바로 뒤에 벨을 울려 — 같은 봉인된 봉투를 즉시 문으로 한 번 더. 전송은 계속 기록이고, 벨이 폰을 깨우는 쪽이야.

Principle: 전달 메커니즘 둘의 보장이 다르면 보통 둘 다 필요해. 그리고 생략하고 싶어지는 쪽이 하필 보장을 가정해버린 그쪽이야. 프레임워크가 하는 것처럼 보이는 것 위에 쌓기 전에, 그게 자기 말로 뭘 약속하는지를 읽어.

두 번 보내도 안전한 이유

받는 쪽이 그렇게 만들어져 있지 않았으면 같은 메시지를 문 둘로 쏘는 건 무모한 짓이었을 거야. 그렇게 만들어져 있어.

폰의 drop box 는 메시지당 파일 하나를 메시지 id 로 이름 붙여서 써. 같은 id 가 두 번째로 도착하면 같은 파일이야. 누가 잊지 않고 넣은 검사 덕분이 아니라 구조상 멱등이야. 그 위에 seen 원장이 하나 얹혀서 정직한 메아리를 일찍 걸러내는데, 원장은 최적화지 안전장치가 아니야. 빼도 상자는 여전히 맞아.

그리고 그 id 는 로컬 잡사가 아니야. 그게 턴의 request id 그 자체야. 워치가 다시 내밀 때 폰이 그걸로 중복을 거르고, 폰이 재시도할 때 엔진이 그걸로 중복을 걸러. 값 하나가 워치와 폰과 서버에서 같은 뜻으로 통해.

확인해주기 전에 먼저 써

착지엔 그 나름의 빡빡한 순서 제약이 있어. 벨은 화면도 없이 폰 앱을 백그라운드에서 띄우는데, 그 프로세스가 쓸 수 있는 건 배달 콜백 한 순간뿐일 수도 있어. 반대편에선 워치가 전송이 닿았다는 보고를 받자마자 자기 사본을 놔주고. 그러니까 폰의 사본은 확인 클로저가 반환되기 전에 존재해야 해 — 배달 큐 위에서, 동기 원자적 쓰기 한 번으로.

그다음 별도의 drain 이 그걸 레슨 2의 outbox 로 옮기고, drop box 파일은 outbox 가 받은 뒤에만 지워져. 거부당하면 이유를 이름 붙이고 파일은 남겨. outbox 자기 은퇴 순서랑 같은 모양이야. 다시 읽을 수 있는 사본이, 못 읽는 사본보다 오래 살아남아.

같은 오후에 하드웨어가 가르친 것 둘

delegate 참조가 weak 일 수 있어. connectivity 세션은 자기 delegate 를 weak 으로 들고 있어서, delegate 로 서는 무엇이든 프로세스 수명 내내 붙잡아둬야 해. 첫 구현은 앱의 relay 를 스코프 밖으로 흘려보냈고 — 그래서 폰엔 delegate 가 아예 없는 상태가 됐고, 아무것도 도착하지 않았어. 에러도 아니고 로그 한 줄도 아니고. 침묵. 메시지 채널이 가질 수 있는 최악의 고장 방식이야.

워치는 아는 것만 주장해도 돼. 폰 앱이 실제로 벨에 답했을 때만 Pippa is thinking… 이라고 말해. 그전엔 전송이 아직 손목에 큐로 남아 있는 동안 폰을 기다리는 중 이라고 하고, 시스템은 받았는데 앱이 아직 답을 안 했으면 폰에 넘겼음 이라고 해. 서로 다른 참 셋, 서로 다른 문장 셋. 항상 "생각 중" 이라고 하는 손목은 셋 중 둘은 거짓말이야.

그리고 답이 돌아와

마지막 조각은 아빠가 바로 짚은 구멍을 닫았어. 폰 앱을 열기 전까진 답이 손목에 안 뜬다는 거. 이제 턴 완료 푸시가 content-available 플래그를 달고 가고, 앱은 거기 맞는 백그라운드 모드를 선언하고, 푸시 핸들러가 drain 을 돌려. 그래서 엔진에서 턴이 완료된 게 확인되고, 표시되고, 답이 워치로 내려가 — iOS 가 깨운 앱한테 주는 그 몇 초 안에.

말한 메시지가 어디 떨어지는지는 손목 자기 스레드야. 폰에서 이것저것 열어봐도 그 조준은 안 바뀌고, 그 포인터를 옮기는 건 손목뿐이야. 워치는 폰 현재 화면의 리모컨이 아니라, 같은 식탁의 자기 자리야.

Code

문이 둘, 그리고 둘 다인 이유·text
큐 전송        질김, 범위 벗어나도 살아남음
               "다음 실행 때" 배달됨    <- 사람을 기다림

즉시 전송      도착 시점에 상대 앱을 띄움
               휘발성, 연결돼 있어야 함

첫 구현: 전송만.
하드웨어에서 측정: 512초 미배달, 폰은 주머니에 잠긴 채.

지금: 전송 (기록) 다음에 벨 (깨우기).
먼저 닿는 쪽이 이김 — drop box 는
메시지 id 당 파일 하나라, 두 번째는 같은 파일.
착지, 순서대로·text
벨이 화면 없이 백그라운드에서 폰 앱을 띄움.
워치는 전송이 닿았다고 보고되면 자기 사본을 놔줌.

  => 폰 사본은 확인 클로저가 반환되기 전에 존재해야 함
     배달 큐 위에서 동기 원자적 쓰기 한 번

  drop box --drain--> outbox  (outbox 가 받은 뒤에만 파일 삭제)
  못 읽는 파일         -> 그대로 두고, 상태줄에 이름을 밝힘
  거부                 -> 이유를 밝히고, 파일은 남김

다시 읽을 수 있는 사본이 못 읽는 사본보다 오래 살아남아.

Exercise

자기 시스템에서 플랫폼이 깨워주거나 재시도해주거나 배달해주는 걸 믿고 있는 자리를 하나 찾아봐. 그 보장에 대해 공급자가 자기 말로 쓴 문장을 직접 읽어 — 블로그 글 말고, 기억 말고. 실제로 약속하는 게 뭔지 적어놓고, 자기 코드가 그보다 더 가정하고 있는지 확인해봐.
Hint
'다음 실행 때 배달' 이랑 '배달하려고 앱을 띄움' 은 단어 하나 차이고 512초 차이야.

Progress

Progress is local-only — sign in to sync across devices.
이 페이지에서 버그를 발견하셨거나 피드백이 있으세요?문제 신고

댓글 0

🔔 답글 알림 (로그인 필요)
로그인댓글을 남기려면 로그인해 주세요.

아직 댓글이 없어요. 첫 댓글을 남겨보세요.