"값의 뜻이 그게 어디 있냐에 달려 있으면, 그 '어디'를 타입에 넣어."
사실은 구조체로, 정체성이 있는 건 클래스로
Swift struct는 값이야. 복사하면 서로 상관없는 사실이 두 개가 돼. class는 참조야. 변수를 복사해도 두 이름이 같은 객체 하나를 가리켜. 애플 플랫폼에서 뭘 어떻게 모델링할지는 대부분 이 차이로 정해져.
- 네 데이터는 값이야. 캡처, 설정 문서, 전송 메시지는 구조체로 해. 필드가 전부
Sendable이면 알아서Sendable이 되고,Equatable만 선언하면==로 비교되고, 앱의 다른 구석이 몰래 바꿀 수도 없어. - 프레임워크의 오래 사는 객체는 클래스야. 창, 뷰, 뷰 컨트롤러, 델리게이트, WatchConnectivity 세션. 리스폰더 체인에 들어가는 AppKit, UIKit 객체는 전부
NSObject서브클래스야. 정체성 자체가 핵심이거든. 네가 설정한 그 뷰가 화면에 있는 그 뷰여야 하니까. - 관찰 가능한 모델은 클래스야.
@Observable매크로는 클래스에 붙어. 뷰가 지켜봐야 하는 건 시간이 지나며 바뀌는 공유 인스턴스 하나거든.
서브클래스 만들 생각이 없으면 클래스에 final을 붙여. 의도가 드러나고, 컴파일러가 메서드를 바로 호출할 수 있어.
잘못된 호출 자체를 못 하게 만드는 enum
이 가족의 데스크톱 클라이언트엔 파일이랑 터미널을 보여주는 도크가 있었는데, 그게 "세션의 작업 경로"에 붙어 있었어. 그냥 URL 하나였지. 레포가 여러 Mac에 같은 절대 경로로 똑같이 있는 환경이라, 다른 Mac에서 도는 세션을 열면 도크는 이 Mac의 체크아웃을 보여주고 거기를 고쳤어. 모델은 원격 호스트에서 일하는데 말이야. 겉보기엔 다 멀쩡했어. URL은 자기가 어느 기계 거인지 말을 못 하니까.
고칠 때 호출하는 곳마다 검사를 넣진 않았어. 대신 도크 연결부가 맨 URL 말고 경우가 두 개인 enum을 받게 바꿨어. .local(URL) 아니면 .unavailable(reason:). 그 뒤론 어떤 호출도 "이 경로, 호스트는 모름"을 표현조차 못 하게 됐어. 나중에 원격 세션에서도 도크가 돌아가야 해서 세 번째 경우 .remote(socket)를 추가했는데, 호출하는 코드는 한 줄도 안 바뀌었어. 모든 화면이 이미 그 결정 하나를 거치고 있었거든.
가져다 쓸 만한 모델링 습관이 이거야. 틀린 답을 거부하려고 만든 타입이 나중에 맞는 답을 담는 그릇이 돼. 연관값 있는 enum, 컴파일러가 빠짐없이 다뤘는지 검사해주는 switch, 불가능한 상태를 이니셜라이저에서 거부하는 구조체. 이런 것들이 런타임 버그 한 부류를 통째로 컴파일 에러로 바꿔.
Swift가 Objective-C를 만나는 곳
값을 싣는 Swift enum은 Objective-C엔 없어. 제네릭 구조체도 없고. 프레임워크 API 밑바닥이 Objective-C라면, 넘겨주는 건 클래스나 @objc 프로토콜, 단순한 타입이 돼. 풍부한 Swift 모델은 경계 이쪽에 남겨둬. 이 경계가 퀘스트 내내 나와. 스레드 가정, nil 규칙, 런타임이 객체를 만드는 규칙이 다 거길 넘나들거든.