What's new in Swift 5.10
안녕하세요. 그린입니다 🍏
이번 포스팅에서는 Swift 5.10에서 어떤것들이 변했는지 학습해보겠습니다 🙋🏻
사실 Swift 5.10은 얼마전인 3월 5일에 릴리즈가 되었고, 현재 Swift 공식 레포를 들여다보면 Swift 6.0 릴리즈를 준비중이에요.
실제로 ChangeLog를 살펴보면 Swift 6.0 변화를 위해 현재 활발히 진행중인걸 볼 수 있습니다!
그래서 곧 나올 Swift 6.0을 알아보기전 Swift 5 버전대의 마지막인 Swift 5.10에 대해 살펴보겠습니다 🚀
Swift 5.10
완전히 엄격화된 동시성 체크
Swift 5.10에서는 완전히 엄격화된 동시성 체크를 통해 알려진 모든 정적 데이터 경쟁 상태에 대해 안전해졌습니다.
-strict-concurrency=complete 옵션을 사용하여 코드를 작성할 때, Swift 5.10에서는 nonisolated(unsafe)나 @unchecked Sendable을 사용하지 않는 한, 컴파일 시간에 데이터 레이스에 대한 가능성을 모두 체크합니다.
예를들어, Swift 5.9에선 아래 코드를 살펴보면 @MainActor에 의해서 격리된 이니셜라이저가 액터 외부에서 평가되어 런타임에 크래쉬가 발생했지만, -strict-concurrency=complete 하에서는 이러한 크래쉬가 발생되지 않습니다.
@MainActor
class MyModel {
init() {
MainActor.assertIsolated()
}
static let shared = MyModel()
}
func useShared() async {
let model = MyModel.shared
}
await useShared()
위 코드는 @MainActor에 의해서 격리된 정적 변수가 첫 접근 시에 @MainActor에 의해 격리된 초기값을 평가하는데, 해당 변수가 비격리 컨텍스트에서 동기적으로 접근되기에 데이터 레이스를 허용하게 됩니다.
Swift 5.10에선 -strict-concurrency=complete 옵션을 사용해 코드를 컴파일하면, 비동기적으로 접근해야한다고 아래처럼 경고를 나타내줍니다.
warning: expression is 'async' but is not marked with 'await'
let model = MyModel.shared
^~~~~~~~~~~~~~
await
Swift 5.10은 Sendable과 액터 격리 검사에서 수많은 버그들을 수정하여, 완전한 동시성 검사의 보장을 강화했습니다.
Swift 5.10의 완전한 동시성 모델은 보수적이라고 합니다.
Swift 6가 나오기에 앞서 엄격한 동시성 검사의 사용성을 향상시키기 위해서 여러 Swift Evolution 제안들이 반영되어 활발히 개발중이라고 합니다.
아마 그렇다는건 이제 Swift 6에서 더욱 더 강화된 동시성 관련된것들이 나올거라 예상이 되네요 🤔
전역 변수에 대한 엄격한 동시성 (SE-0412)
전역 변수와 정적 변수는 모든 프로그램 컨텍스트에서 액세스할 수 있는 메모리를 제공하기에 데이터 레이스를 발생시키기가 쉽습니다.
Swift 5.10의 엄격하고 향상된 동시성 검사는 아래 항목들중 하나를 요구하여 전역 및 정적 변수에 대한 데이터 레이스를 방지합니다.
1️⃣ 글로벌 액터에게 고립되어 있는지
2️⃣ 불변하면서 Sendable 타입인지
var mutableGlobal = 1
// warning: var 'mutableGlobal' is not concurrency-safe because it is non-isolated global shared mutable state
// (unless it is top-level code which implicitly isolates to @MainActor)
@MainActor func mutateGlobalFromMain() {
mutableGlobal += 1
}
nonisolated func mutateGlobalFromNonisolated() async {
mutableGlobal += 10
}
struct S {
static let immutableSendable = 10
// okay; 'immutableSendable' is safe to access concurrently because it's immutable and 'Int' is 'Sendable'
}
예를들어서, 새로운 nonisolated(unsafe) 수정자는 글로벌 또는 정적 변수에 주석을 달아 수동 동기화가 제공될 때 데이터 격리 위반을 억제하는데 사용할 수 있습니다.
import Dispatch
// 'MutableData' is not 'Sendable'
class MutableData { ... }
final class MyModel: Sendable {
private let queue = DispatchQueue(...)
// 'protectedState' is manually isolated by 'queue'
nonisolated(unsafe) private var protectedState: MutableData
}
nonisolated(unsafe)는 저장 프로퍼티와 지역 변수를 포함한 모든 형태의 저장소에 사용할 수 있는, 더 세밀한 Sendable 검사에 대한 선택적 제외인 opt out 방법으로 활용될 수 있습니다.
이는 많은 사용 사례에서 @unchecked Sendable 래퍼 타입의 필요성을 제거해줍니다.
동기화 매커니즘을 올바르게 구현하여 데이터 격리를 달성하지 않으면, 독점성 집행의 동적 런타임 분석이나 Thread Sanitizer와 같은 툴을 통해 여전히 실패를 감지할 수 있습니다.
격리된 기본값 표현식 (SE-0411)
Swift 5.10은 이전에 격리된 기본 저장 프로퍼티가 액터 외부에서 동기적으로 평가되도록 허용했던 데이터 경쟁의 허점을 안전하게 매꿨습니다.
액터 외부에서 평가된 경우, 예를들어서 아래 코드는 Swift 5.9에서 -strict-concurrency=complete 옵션을 사용하여 컴파일할 때 경고 없이 컴파일이 되지만, 런타임에서 MainActor.assertIsolated()를 호출 시에 크래쉬가 발생하게 됩니다.
@MainActor func requiresMainActor() -> Int {
MainActor.assertIsolated()
return 0
}
@MainActor struct S {
var x = requiresMainActor()
var y: Int
}
nonisolated func call() async {
let s = await S(y: 10)
}
await call()
이런 문제는 requiresMainActor()가 S의 멤버와이즈 이니셜라이저에 기본 인자로 사용되지만, 기본 인자들은 항상 호출자에 의해서 평가되기 때문에 발생합니다.
이 경우에 호출자는 제네릭 실행자에서 실행되기에, 기본 인자 평가에서 크래쉬를 발생시킵니다.
Swift 5.10에서는 -strict-concurrency=complete 하에, 기본 인자 값은 포함한 함수나 저장 프로퍼티와 동일한 격리를 안전하게 공유할 수 있습니다.
위 코드는 여전히 유효하지만, 격리된 기본 인자는 호출된 함수의 격리 도메인에서 평가될거란걸 보장하게 됩니다.
@UIApplication & @NSApplicationMain 디프리케이트 (SE-0383)
이제 두 프로퍼티 래퍼에 대해 지원이 중단됩니다.
이제는 Swift 5.3에서 도입된 @main을 사용해야합니다.
정확히는 Swift 6에서 @main을 사용하지 않으면 오류가 발생될거에요!
@UIApplicationMain // warning: '@UIApplicationMain' is deprecated in Swift 5
// fixit: Change `@UIApplicationMain` to `@main`
final class MyApplication: UIResponder, UIApplicationDelegate {
/**/
}
// 이렇게 사용!
@main
final class MyApplication: UIResponder, UIApplicationDelegate {
/**/
}
이렇게 많이들 이제는 사용하고 계셨겠지만, 기존 @UIApplicationMain으로 사용하셨다면, 이제는 걷어낼때가 온것 같습니다.
Non-Generic 컨텍스트에 프로토콜 중첩 허용 (SE-0404)
이전에는 클래스나 구조체 등 타입 안에 프로토콜 자체가 중첩되어 포함될 수 없었습니다.
즉, 항상 모듈 내 최상위에 존재해야했어요.
이제는 아래와 같이 중첩하여 포함할 수 있습니다.
class TableView {
protocol Delegate: AnyObject {
func tableView(_: TableView, didSelectRowAtIndex: Int)
}
}
class DelegateConformer: TableView.Delegate {
func tableView(_: TableView, didSelectRowAtIndex: Int) {
// ...
}
}
더욱 편리하고 유연성이 높아졌네요!
이렇게 핵심적인 Swift 5.10의 업데이트 사항을 살펴봤습니다 😃
마무리
Swift 5.10이 확실히 이전보다 Swift 동시성에 초점을 둔 업데이트였고, 더욱 안정성을 높인것 같아요!
이제 곧 나올 Swift 6.0을 위한 초석이라고 봐도 될것 같습니다.
6.0도 현재 릴리즈는 되지 않았지만, 업데이트되고 있는 항목들은 볼 수 있는데 어느정도 많은 변화가 있을것 같습니다.