"앱이 이미 켜져 있을 땐 보여주기가 매번 됐어. 꺼진 상태에서 켜질 땐 조용히 아무것도 안 했고."
진입점은 두 가지
SwiftUI 앱은 @main struct … : App에서 시작하고, AppKit 콜백이 필요하면 @NSApplicationDelegateAdaptor로 델리게이트를 붙여. AppKit이 주인인 앱은 @main 타입의 static func main()에서 시작해. 거기서 NSApplication.shared를 만들고, 델리게이트를 달고, 활성화 정책을 정하고, run()을 불러. 가족 Mac 앱 몇 개는 실행 순서가 눈에 보인다는 이유로 두 번째 방식을 써.
활성화 정책은 앱이 자기를 어떻게 드러내는지 정해. 선택지는 셋이야. .regular는 Dock 아이콘과 메뉴 막대가 있고 맨 앞으로 올 수 있어. .accessory는 Dock 아이콘 없이 창만 띄울 수 있는, 메뉴 막대 유틸리티 형태야. 나머지 하나는 .prohibited야. Info.plist에 LSUIElement를 넣으면 코드가 돌기도 전, 실행 첫 순간부터 accessory처럼 동작해.
실행 과정엔 네가 만들지 않은 경합이 있어
파일 작업대 앱이 다른 프로그램에서 오는 "이 파일 보여줘" 요청을 application(_:open:)으로 받아. 앱이 켜져 있을 땐 완벽했는데, 꺼진 상태에서 켜질 땐 아무것도 안 했어. 서로 상관없는 경합 두 개가 얽혀 있어서, 하나만 고치면 버그가 여전히 남았어.
- 열기 이벤트가
applicationDidFinishLaunching이 창 컨트롤러를 만들기 전에 올 수 있어. 그러면 아직 없는 컨트롤러에 대고 한 옵셔널 체이닝 호출은 아무 일도 안 해. - 그 뒤에 끝나는 비동기 세션 복원이, 방금 보여준 위치를 복원된 위치로 덮어써.
고친 방법은 이래. 들어오는 URL은 전부 대기열에 넣어두고, 컨트롤러가 있고 복원도 끝났다는 두 조건이 다 맞을 때만 꺼내서 처리해. 그리고 복원은 결과가 비었을 때도 에러가 났을 때도 끝났다고 알려야 해. 안 그러면 요청이 영원히 기다려. 꺼진 상태에서 켜질 때랑 켜져 있을 때를 따로 테스트해. 켜져 있을 때 통과한 건 실행 순서에 대해 아무것도 증명 못 해.
세션을 안 잃고 끝내기
네이티브 창 탭을 쓰는 터미널이 창이 닫힐 때마다 탭 배치를 저장했어. 종료할 때 보면, applicationShouldTerminate는 창이 전부 열려 있는 상태에서 불려. 그 뒤로 창이 하나씩 닫히면서 그때마다 창이 하나씩 줄어든 스냅숏이 저장됐고, 결국 마지막 저장은 빈 세션을 기록했어. 고친 방법은 applicationShouldTerminate에서 스냅숏을 찍고, terminating 플래그를 세우고, 그 뒤의 창별 저장은 아무것도 안 하게 하는 거야.
함대 관리 도구라면 알아야 할 종료 관련 사실이 하나 더 있어. 문서 앱은 죽이지 말고 정중하게 종료해야 해(Apple Event나 종료 메뉴로). 저장 안 된 문서를 챙기는 applicationShouldTerminate는 SIGTERM에선 절대 안 불려.