What's new in Swift6 (feat. WWDC 2024)
안녕하세요. 그린입니다 🍏
이번 포스팅에선 WWDC 2024에서 소개한 Swift6의 새로운 기능들에 대해 알아보겠습니다 🙋🏻
먼저, Swift가 그간 10년 동안 발전해온 발자취를 볼까요? 👣
Swift over the years
먼저 Swift는 2014년에 발표된 후 현재 10년이 지났는데요.
발표된 다음 해에 오픈 소스로 리눅스에서도 사용할 수 있게 되었죠.
그리고 커뮤니티를 통해 지속적으로 성장해왔습니다.
2016년에는 Swift 3와 함께 SPM도 발표가 되었죠.
특히 Swift 2에서 Swift 3로 마이그레이션 시 많은 변화가 있어 어려웠다고 합니다 🥲
새로운 언어 모드로 모두 옮겨야 했으니까요.
그런 경험을 토대로 2017년 Swift 4에서 모든 Swift 코드를 한번에 새로 나온 언어 버전 모드로 옮기지 않고도 새로운 언어 모드를 도입할 수 있게 했습니다.
즉, 이전과 이후 언어 모드의 모듈을 조합할 수 있게 된 것이죠.
점진적 도입이 가능하며, 준비가 다 된 후 마이그레이션을 할 수 있게 된 것이죠.
2018년엔 제네릭 시스템을 개선했습니다.
2019년엔 Swift 5가 지원되며 애플 플랫폼에 안정적인 ABI를 도입했습니다.
더 이상 앱에서 Swift 표준 라이브러리의 전체 사본을 번들로 구성하지 않아도 되어 앱 크기를 줄일 수 있게 되었어요.
Swift 표준 라이브러리 자체가 OS의 일부가 되고 해당 OS에 최적화되고 모든 Swift 앱과 프레임워크에서 공유되었습니다 😃
Swift를 온전히 잘 활용해 더 안전한 API와 프레임워크를 빌드할 수 있게 된 것이죠.
SwiftUI가 그러합니다.
2020년까지 거치면서 더 많은 플랫폼에서 지원을 하게 되었고, 그 중 하나로 윈도우로의 커뮤니티 포트가 swift.org에서 공식 제공되었어요.
2021년에는 동시성 관련하여 Concurrency를 도입했죠.
2022년에는 작년 배포된 액터를 도입해 네트워크 서비스를 더 쉽게 빌드할 수 있게 되었어요.
또한, 커뮤니티에서 VSCode용 Swift 확장 프로그램을 릴리즈해 Swift 개발을 위한 크로스 플랫폼을 지원하게 되었습니다.
2023년에는 C++을 지원하는 양방향 상호 운용성을 도입했습니다.
덕분에 Swift의 안정성과 표현력을 사용해 대규모 크래스 플랫폼 C++ 코드 기반을 개선할 수 있었습니다.
또, 중요한 부분이 매크로의 도입입니다.
매크로의 도입을 통해 SwiftData와 같은 API를 쉽게 지원하게 되었죠.
그리고 현재 Swift 6까지 왔습니다.
여태까지의 함축된 Swift 발전 과정을 흐름으로 보면 이렇습니다.
이제 Swift 6는 동시성에 대해 더욱 개선되고 데이터 레이스를 보장해줍니다.
Swift의 발전을 보았으니 Swift를 발전시켜온 생태계의 변화도 볼까요?
Swift project update
커뮤니티를 빼놓을 수 없습니다.
초기엔 Swift Core 팀이 유일한 운영 그룹이였습니다.
그 후, 커뮤니티의 성장에 따라 지난 몇 년 동안 운영 및 작업 그룹의 수가 늘어났죠.
올해는 플랫폼 팀을 만들어 더 많은 곳에서 Swift의 적용이 될 수 있게 집중하고 있습니다.
또한, 개발자 경험과 더 넓은 생태계에 중점을 둔 운영 그룹을 만드는 작업을 하고 있습니다.
그리고 하위 수준 환경에 대해 모멘텀을 지속하기 위해 임베디드 내장 팀을 만들고 있습니다.
Swift 커뮤니티에 참여하고 Swift.org/community에서 더 살펴볼 수 있어요!
또한, Swift.org 사이트도 한번 둘러보시면 좋을것 같아요.
웹 사이트 그룹이 피땀 흘려 잘 가꿔놓은 집이며 여러분들과 커뮤니티적으로 소통할 수 있는 공간입니다 😃
특히 Blog 파트를 보시면 해당 공간에서는 Swift 커뮤니티의 재밌는 개발 소식들을 만나 보실 수 있어요.
Swift everywhere
Swift는 점점 더 많은 플랫폼에 포팅되고 있기 때문에, 크로스 플랫폼 언어로 성장하고 있어요.
공식적으로는 현재 애플 플랫폼, Linux, Windows에서 지원되고 있습니다.
물론, 현재도 Swift 커뮤니티를 통해 성장하고 있는 플랫폼이 아주 많습니다.
이렇게 Linux를 확대해 여러 플랫폼 지원도 추가되고 있습니다.
그리고 Xcode는 Swift의 기본 IDE이죠.
하지만 개발 환경이 다양해서 애플은 SourceKit LSP를 개발했습니다.
IDE와 편집기에서 Swift 지원을 통합하도록 지원하는 Swift용 언어 서버 구현이죠.
VSCode, Neovim, Emacs 등에서 도입하여 가능케 해줍니다.
애플 플랫폼을 사용하고 있었다면, 이미 크로스 컴파일을 쓰고 있었던것입니다.
크로스 컴파일을 사용하면 한 환경에서 실행 파일을 생성하고 이를 다른 환경에서 실행할 수 있죠.
이걸 이제 Linux와 결합해보는겁니다.
macOS에서 개발하고 프로그램을 Linux 서버 혹은 컨테이너에 배포할 수 있죠.
macOS에서 Linux로 크로스 컴파일을 할 수 있는 완전히 정적인 Swift용 Linux SDK가 있습니다.
과정을 간단히 살펴보면 이렇습니다.
먼저 좌측을 보면, macOS용 서비스를 만들건데 Swift 빌드를 실행해 만듭니다.
이제 우측 상단 터미널을 통해 로컬 컴퓨터에서 이 서비스를 실행합니다.
로컬 호스트에서 수신하는 서버에 요청을 하고 기록하는 로컬 서버를 확인할 수 있어요.
이제 이 서비스를 Linux 서버에서 실행하도록 크로스 컴파일을 합니다.
가장 먼저 정적인 swift linux 패키지를 설치합니다.
Swift 빌드 명령에 플래그를 추가해 빌드를 합니다.
그 결과 생성된 바이너리가 리눅스에서 실행되도록 빌드해요.
이걸 리눅스 서버에 복사해 실행합니다.
이제 맥에서 Linux 서버에 요청할 수 있습니다.
이러면 이제 해당 API를 통해 고양이 이모지를 출력해볼 수 있는것이죠!
그럼 다음으로 Swift의 주요 라이브러리를 한번 살펴볼까요?
Foundation에는 JSON 디코딩, 날짜 및 시간, 파일 시스템 연산 등을 비롯한 중요한 API를 제공합니다.
애플의 가장 오래된 프레임워크 중 하나죠.
macOS X 초창기까지 흘러가야 합니다.
Swift가 처음 도입될때 이 API가 모든 플랫폼에서 유용할거라는걸 알았다고 해요.
그래서 발전을 거쳐 지금의 Foundation이 된것입니다.
Swift Testing에 대해서도 간단히 보겠습니다.
이제 테스팅에서도 매크로와 같은 최신 기능을 도입하고 동시성과 원활하게 통합됩니다.
또한, 크로스 플랫폼을 염두하여 개발되었어요.
비전은 기본 테스트 솔루션이 되는것에 있습니다.
테스트를 선언하려면 @Test를 선언해주면 됩니다.
그리고 인수로 맞춤형 표시 이름을 제공할 수 있어요.
그리고 expect 매크로를 이용해 테스트를 합니다.
또한, 태그를 사용하여 정리 및 필터링을 제공해주죠.
이건 아주 심플한 예시였고, 정말 다양한 기능이 존재합니다.
이는 Meet Swift Testing 세션을 보면 좋겠네요 😃
다음으로, 테스트를 넘어 Xcode에서 빌드 방식의 개선을 잠깐 볼께요.
Swift 코드를 빌드할 시, 각 모듈은 다른 모듈에 따라 달라지며 SDK의 모듈에 따라 달라지기도 합니다.
Swift 컴파일러가 SwiftUI 같은 모듈을 만나는 경우엔 해당 모듈의 바이너리 버전을 빌드해야할 수도 있습니다.
묵시적으로 발생하기에 알아차리지 못할 수 있죠.
하지만 많은 작업이 숨어 있어요.
사용해야 하는 모든 모듈을 빌드해야 되니까 말이죠 ☺️
그리고 다른 Swift 모듈이 빌드되면 같은 바이너리 모듈을 재사용하지만, 해당 모듈이 준비될때까지 기다려야 합니다.
그렇기에 빌드에서 원하는 정도의 유사성을 얻지 못해요.
또한, 디버거가 이 모든 모듈 파일의 자체 버전을 빌드해야 할 수 있습니다.
명시적으로 빌드된 모듈은 묵시적 단계를 명시적 빌드 단계로 전환하고 이는 모듈 빌드가 동시에 수행될 수 있으며, 빌드 로그에 명확하게 표시됨을 의미합니다.
그래서 더 예측 가능하고 안전한 빌드를 얻을 수 있죠.
또 디버거가 바이너리 모듈을 빌드와 공유하기에 더 빠른 디버깅이 가능해집니다.
Xcode 빌드 설정에서 명시적 모듈 빌드를 활성화 할 수 있어요.
Xcode 16부터 설정 가능합니다.
이 부분도 자세히 들여다보고 싶다면, WWDC 2024의 Demystify explicitly built modules를 참고하세요 😃
그리고 추가 여담으로 이제 swiftlang에서 Swift가 관리되기에 성장하는 과정을 여기서 지켜보시죠! 😀
길고 길었는데, 이제 Swift 6에서 어떤 새로운 기능들이 생겨났는지 알아보시죠 🙋🏻
Language updates
Noncopyable types
값, 참조 타입 등 모든 Swift 타입은 기본적으로 복사할 수 있어요.
그런데, noncopyable 타입은 기본 복사 가능성을 억제해 고유한 소유권을 표현하도록 만들어 줍니다.
예시로, 파일과 같은 고유한 시스템 리소스는 복사가 불가한 구조로 나타낼 수 있어요.
이런식으로 말이죠.
Swift 6는 모든 일반적인 컨텍스트 및 옵셔널 같은 표준 라이브러리의 중요한 일반적인 유형에서 noncopyable 타입에 대한 지원을 구현합니다.
noncopyable이나 copyable 타입을 모두 추상화하는 기능은 일반적으로 noncopyable 타입의 사용성을 확장합니다.
이건 더 딥하게 알아봐야 해서 추후에 WWDC 2024의 Consume noncopyable types in Swift 세션에서 알아보려 합니다 🙋🏻
noncopyable 타입을 사용해 성능을 세밀하게 제어할 수 있고, 리소스 제약이 심한 하위 수준 시스템에선 복사 코스트가 많이 들어 noncopyable이 적절할 수 있어요.
Embedded Swift
하위 수준 시스템은 메모리, 저장 공간 및 런타임 기능의 제약이 있습니다.
적은 메모리 공간 때문에 프로그래밍 시 C와 C++이 주로 선택되었죠.
그런데 이젠 Swift를 사용할 수 있어요.
Embedded Swift는 Swift의 새로운 언어 서브셋 및 컴파일 모델로 고도로 제한된 시스템에서 매우 작은 독립형 바이너리를 생성할 수 있습니다.
reflection이나 any와 같은 런타임 지원이 필요한 특정 언어 기능을 꺼버리고, full generics specialization, static linking 같은 컴파일러 기법을 사용해 적합한 바이너리를 생성합니다.
Embedded Swift를 활용해 Swift에서 게임 작성도 가능해요.
게임의 바이너리 크기는 몇 KB 밖에 되지 않죠.
물론, 게임을 넘어서 산업용 앱 빌드에 많이 쓰이는 다양한 ARM 및 RISC-V 마이크로컨트롤러 등에서도 사용할 수 있어요.
Apple Secure Enclave 프로세서에서도 Embedded Swift를 사용합니다.
해당 프로세스는 주 프로세서와 격리된 하위 프로세서로 중요한 데이털르 안전히 보호하는 역할을 합니다.
Embedded Swift가 Swift의 안정성을 보장해주기에 가능하죠.
C++ interoperability
작년에 C++를 지원하는 양방향 상호 운용성을 도입했습니다.
Swift와 C++를 직접 상호 운용할 수 있어 개발 경험을 높여줬죠.
애플은 계속해서 상호 운용에 대한 언어 기능을 확대하고 있습니다.
Swift 6에선 C++ 가상 메서드, 기본 인수, move-only 타입 및 중요한 C++ 표준 바이너리 타입을 Swift에 가져올 수 있습니다.
상응하는 Swift 버전에 매핑되어 있죠.
이런식으로 C++의 move-only 타입은 Swift의 noncopyable 타입에 매핑되어 있어, 사용을 할 수 있습니다.
실수로 값 복사를 하려면 이렇게 에러가 나죠 당연히ㅎㅎ
C++은 널리 사용되지만 안정성이 부족해 보안이 좋지 않기에 상호 운용을 통해 장점을 극대화할 수 있죠.
Typed throws
Swift는 예외 조건에서 코드를 실행할 때 오류를 전달하고 캐치하는 최고 수준의 오류 처리를 지원해줍니다.
이렇게 Error 프로토콜을 따르고 throws하여 do catch를 통해 사용하는 익숙하고 기본적인 에러 처리 과정입니다.
Swift 6에서는 여기에 typed throws를 도입했어요.
에러 유형과 함께 throws 키워드를 제공할 수 있습니다.
type erasure가 포함되지 않고 더 명확히 any Error가 아닌 구체적인 유형이 표시될 수 있죠.
Untyped throws는 any Error 유형의 typed throws와 같습니다.
에러가 없는 함수는 Never 유형의 typed throws 함수와 동일한 표현이 됩니다.
전달된 에러 유형을 변경하는 유연성을 유지하고 싶다면, Untyped throws를 계속 사용하면 되겠죠? 👍
지금까지 알아본 업데이트 말고도 아래와 같이 Swift 6 개선 사항이 더 있으며 성능에 대한 내부적인 개선도 이뤄졌습니다.
이제 Swift 6 사항의 꽃을 볼까요?
Data-race safety
이제는 데이터 레이스 안정성을 기본적으로 달성하게 됩니다.
데이터 레이스는 동시 프로그램 작성 시 흔히 발생하는 프로그래밍 오류죠.
여러 스레드가 데이터를 공유하는데 그 중 한 스레드에서 데이터를 변형하려고 할 때 발생합니다.
이는 예기치 않은 런타임 동작과 문제가 발생할 수 있어요 🥹
Swift가 도입된 이후로 데이터 레이스 안정성은 Swift Concurrency의 주요 목표였으며 점점 버전 업데이트를 거듭할수록 발전해 왔습니다.
Swift Concurrency는 데이터 격리 달성을 위해 메커니즘, 가변 상태를 보호하기 위한 Actor, 안전한 데이터 공유를 위한 Sendable을 기점으로 설계되었어요.
Swift 5.10에선 완전한 동시성 확인 플래그를 통해 데이터 레이스 안정성을 달성했습니다.
Swift 6 모드에서는 기본적으로 이제 데이터 레이스 안정성을 달성하고 앱의 모든 데이터 레이스 문제를 컴파일 타임에서의 오류로 캐치할 수 있도록 전환되었습니다.
이는 앱 보안을 향상시켜주고 디버깅 문제를 줄여주며 런타임에서 발생할 수 있는 예기치 못한 상황을 철저히 막아줄 수 있죠 👍
Swift 6 언어 모드를 사용해 코드를 조정해야 할 수도 있기에, 준비가 되면 모드를 채택할 수 있어요.
이를 모듈별로 도입하고 Swift 6 언어 모드로 마이그레이션 했거나, 아직 하지 않은 종속 부분과도 상호 운용할 수 있습니다.
이 데이터 레이스 안정성은 Swift 6 언어 모드 활성화를 통해 이뤄지는 유일한 업데이트입니다.
다른, 모든 업데이트 사항들은 Swift 6 컴파일러를 업데이트할 때 기본적으로 사용할 수 있는것과 차이죠.
기존 Swift 5.10은 안전을 위해 완전한 동시성 확인은 Actor 분리 경계에서 Sendable이 아닌 모든 값을 전달하는것 자체가 금지되었어요.
이제 Swift 6에선 원래 분리 경계에서 더 이상 참조될 수 없는 상황에서 Sendable이 아닌 값을 전달하는게 안전하다는걸 인식할 수 있습니다.
이젠 이렇게 해당 구문에서 데이터 레이스가 있을 수 없기에 워닝을 더 이상 표출하지 않아요.
물론, 아래처럼 전달 후 사용하게 되면 에러가 발생하죠.
이는 해당 포스팅을 참고해보시면 이해가 더 깊게 될겁니다 😃
Actor에서 제공되는 상위 수준의 동기화 모델 외에도 Swift 6에는 몇가지 새로운 하위 수준 프리미티브가 있습니다.
동기화 모듈(Synchronization)에 Atomic이 있습니다.
이는 플랫폼에서 효율적인 lock-free 구현을 제공하는 모든 타입에 대해 일반적입니다.
안전한 동시 접근을 위해 Atomic 값은 항상 let으로 저장되어야 하죠.
또한, 해당 프로퍼티에 대한 모든 연산은 명시적이여야하고 C및 C++ 메모리 모델과 유사한 메모리 순서 인수를 사용합니다.
또한, 동기화 모듈에는 Mutex도 있습니다.
저장소에 모든 접근은 Mutex가 withLock 메서드에 전달된 클로저를 통해 보호하며 이를 통해서 상호 배타적인 접근을 보장해줍니다.
마무리
이렇게 WWDC 2024의 What's new in Swift 세션을 봤습니다.
뭔가 Swift 6의 새로운 기능들을 딱 소개하는것보다 Swift 10주년을 맞이하여 역사와 노고를 더 알아보는것 같은 시간이였어요 😁
Swift 6 업데이트의 핵심은 뭐니뭐니해도 데이터 레이스 안정성에 있다고 생각되네요!