Swift 6 풀린 지 한참 됐는데 5.10에서 곧장 6으로 점프하면 컴파일 에러가 줄줄이 뜨는 게 정상임. 가장 큰 이유는 strict concurrency 검사가 기본 켜진 것. 그 외에도 매일 만지는 코드에 영향 큰 변화가 꽤 있어서 한 번 정리해둔다.
1. Strict Concurrency 강제 — 가장 큰 변화
Swift 5.x에서 -strict-concurrency=complete로 켜야 봤던 검사가 Swift 6에선 기본. 즉 data race 가능한 코드는 컴파일 에러.
| |
정공법은 actor로 격리하거나, 변경 없는 값 타입으로 바꾸거나, @MainActor 같은 격리 어노테이션 명시하는 거.
| |
옛 코드 한 번에 다 고치는 게 부담스러우면 다음 항목(migration mode) 활용.
2. Migration Mode — 점진 마이그레이션의 친구
-language-mode 5로 일부 파일/모듈만 Swift 5로 유지하면서 점진적으로 Swift 6로 옮길 수 있음. Package.swift 또는 Xcode 빌드 설정에서 모듈 단위로 지정 가능.
| |
새로 짜는 모듈만 Swift 6로 가고, 옛 모듈은 5 유지 → 빌드는 통과. 한 모듈씩 차근차근 옮기는 게 회귀 위험 제일 적다.
3. Sendable 검사 강화
Sendable은 “스레드 경계 넘나들어도 안전한 타입"의 마커 프로토콜. Swift 6는 actor 경계나 Task.detached를 넘는 값에 대해 컴파일 타임에 Sendable인지 검사함.
| |
해결: struct Config: Sendable { ... }. 모든 stored property가 Sendable이면 자동 합성됨. 클래스면 final class + 모든 프로퍼티 let 또는 actor로 격리.
4. Typed Throws — 에러 타입 명시
throws 뒤에 구체 에러 타입을 적을 수 있다.
| |
옛 throws는 throws(any Error)랑 동일. 새 API 설계할 땐 가능한 구체 에러 타입 명시해서 호출자가 정확히 분기할 수 있게 하는 게 권장.
5. Existential any 강제
5.7에서 권장이었던 any 키워드가 6에선 필수. 옛 코드의 protocol Transport를 그냥 타입 자리에 쓰면 에러 뜸.
| |
성능 의도를 명시할 수 있는 자리에선 some이 우선. existential boxing 비용이 안 드는 쪽.
6. count(where:) 등 표준 라이브러리 추가
매일 만지는 collection 함수에 작은 편의 추가가 꽤 있음.
| |
filter { ... }.count보다 중간 배열을 안 만들어서 더 효율적. 비슷하게 Dictionary.subscript(_:default:)로 기본값 처리, String.trimmingPrefix(_:) / trimmingSuffix(_:) 등.
7. Noncopyable 타입 — ~Copyable
복사할 수 없는 타입을 정의할 수 있음. 파일 핸들, 락처럼 “복사하면 안 되는” 리소스를 컴파일 타임에 강제.
| |
소유권 모델이 들어온 첫걸음이라 모든 케이스에 쓸 일은 없지만, 리소스 관리 코드를 더 안전하게 짤 수 있다 ㅋㅋ.
옮기면서 가장 자주 만나는 패턴
옛 프로젝트에 Swift 6 켜면 거의 모든 에러가 다음 둘 중 하나로 수렴함.
- “X is not Sendable” — actor 경계 넘는 값이 Sendable이 아님. 값 타입이면
: Sendable추가, 클래스면 actor 격리나final class + Sendable검토. - “Reference to protocol ‘X’ requires ‘any’” — 프로토콜 타입을 변수/파라미터로 받는 자리.
any X또는some X로 명시.
이 둘만 정리하면 80%는 끝남. 나머지는 typed throws나 ~Copyable 도입 같은 추가 기능 영역이라 점진적으로 들이면 됨.
정리
- Swift 6의 핵심은 strict concurrency를 컴파일 타임 보장으로 끌어올린 것. 옛 경고가 다 에러로 격상됨.
- 한 번에 다 못 옮기면
swiftLanguageMode(.v5)로 모듈 단위 점진 마이그. - typed throws /
~Copyable/count(where:)같은 작은 개선이 매일 쓰는 코드에 의외로 자주 들어옴. - 5.7에서 권장이었던
any가 6에선 필수. 옛 코드의Transport를any Transport로 다 손봐야 함.
다음 단계로 strict concurrency를 본격적으로 다루는 글을 따로 정리할 예정. @preconcurrency, actor 격리 전략, nonisolated(unsafe) 우회 같은 실무 패턴은 분량이 커서 한 글에 다 안 들어감.
Swift 6 마이그레이션은 한 번에 끝낼 일이 아니라 모듈 단위로 천천히 옮기는 작업이라고 생각하면 부담이 훨씬 덜하다.