"못 읽는 항목을 건너뛴 건 맞았어. 개수를 버린 게 버그였지."
Swift 에러는 설계할 수 있는 값이야
실패할 수 있는 Swift 함수는 throws로 선언하고, 호출하는 쪽은 try를 붙여야 해. Swift 6부터는 타입 있는 throws도 돼. throws(InboxError)라고 쓰면 호출자가 캐스팅 없이 실패의 종류를 정확히 알아. 에러 타입은 사람이 알아야 할 내용을 담는 그릇이야. 어느 파일인지, 어느 엔타이틀먼트인지, 어떻게 고치는지. CustomStringConvertible이나 LocalizedError를 붙이면 그게 화면에 보여줄 수 있는 문장이 돼.
try?는 어떤 에러든 nil로 바꿔버리는 비상구야. 실패해도 진짜 상관없는 데서만 써. 나머지 데선 지난 레슨의 조용한 else { return }이랑 쌍둥이야.
스스로 비었다고 보고한 큐
항목마다 파일을 따로 두는 캡처 큐가 폴더를 이렇게 읽었어. 매니페스트를 하나씩 디코딩하다가 실패하면 continue로 넘어가는 식이었지. 망가진 항목을 넘어서 계속 읽는 건 맞았어. 항목별 폴더 저장소를 쓰는 이유가 그거니까. 근데 디코딩 안 되는 매니페스트가 실패로 드러나질 않고 안 보이게 됐어. 파일은 디스크에 그대로 있는데 어디서 세든 항목이 없다고 나왔어. 로그도 없었고, 앱은 빈 큐를 보여줬지.
가정으로 끝날 얘기가 아니었어. 항목 타입에 필드가 처음 하나 추가되는 순간, 그 전에 쓴 매니페스트가 한꺼번에 디코딩이 안 돼(왜 그런지는 다음 레슨에서 볼 거야). 네트워크 없이 찍은 캡처의 유일한 사본이 그 큐에만 있는 폰이었다면, 앱을 한 번 업데이트하는 순간 큐가 아무 말 없이 비어버렸을 거야. 고친 버전은 건너뛰는 건 그대로 하되 숨기는 건 그만뒀어. 목록 함수는 읽은 항목과 못 읽은 항목을 이유와 함께 돌려줘. 그러면 앱은 "이 빌드가 못 읽는 캡처 N개, 이 기기에 손대지 않고 그대로 있음"이라고 보여줘.
늘 성공만 하던 공유 확장
공유 확장 하나가 try?로 저장하고 무슨 일이 있었든 completeRequest를 불렀어. 파일이 안 불러와져도, 폴더가 없어도, iOS랑 사용자한텐 전부 성공으로 보고됐어. 가족이 여기서 가져온 규칙이 이거야. 확장은 어느 방향으로도 조용히 실패할 수 없어야 해. 거절은 시트에서 보이고, 에러는 알림이 되고, 멈춰버리면 타임아웃이 걸려서 알림으로 바뀌어.
Swift가 못 잡는 실패도 있어
Swift do/catch는 Swift 에러를 다뤄. Objective-C NSException은 못 잡아. 가족 받아쓰기 앱이 엔진에 탭을 하나도 안 단 상태에서 AVAudioEngine.prepare()를 불렀어. macOS 26에선 그게 AVFoundation 안에서 예외를 던지고, 앱은 실행 0.5초쯤 뒤에 SIGABRT로 죽었어. 어떤 catch도 소용없었을 거야. 막는 방법은 Objective-C API를 문서가 요구하는 순서와 상태를 지켜서 부르는 것뿐이야.