• Swift의 새로운 기능

    Swift 관련 업데이트를 확인해 보세요. 일상적인 인체공학, 향상된 동시성, 더 안전한 고성능 코드를 위한 업데이트 등 언어 관련 최신 혁신 기술을 알아보세요. Embedded Swift의 워크플로와 언어 상호 운용성 개선 사항 및 업데이트를 살펴보세요.

    챕터

    리소스

    관련 비디오

    WWDC26

  • 안녕하세요, Swift 팀의 Becca입니다 제 동료 Evan과 저는 몇 가지 개선 사항에 대해 소개하려 합니다 Swift 6.3과 6.4 개발 과정에서 이루어진 것들입니다 먼저 언어에 대한 몇 가지 변경 사항을 보여드리겠습니다 일상적인 코딩을 간소화해 줄 것들입니다 그런 다음 Evan이 중요한 라이브러리의 업데이트와 Xcode 및 Apple 플랫폼 외에서의 지원 발전도 다룰 예정입니다 그 후 저는 성능 조율을 위한 특화된 기능들을 안내해 드리겠습니다 안전성을 유지하면서 성능에 민감한 코드를 최적화하는 것들입니다 마지막으로 Evan이 마무리하며 Swift 개발에 참여하거나 기여하는 방법을 소개합니다 그럼 매일 사용할 수 있는 간단한 개선 사항부터 시작하겠습니다 일부는 작은 불편함이 사라지는 것 외에는 거의 느끼지 못할 수도 있습니다 먼저 작은 변경 사항들부터 살펴봅시다 예를 들어 이전에는 옵셔널 타입과 함께 some이나 any를 사용하려면 괄호로 묶어야 했습니다 이 동작은 Swift의 연산자 우선순위 규칙에서 비롯된 것이지만 다소 불필요하게 까다로웠습니다 Swift 6.4에서는 그 괄호를 간단히 없애도 되며 컴파일러가 가장 합리적인 의미로 해석합니다 이제 오류를 조용히 무시하면 경고가 표시됩니다 Swift Concurrency 작업에서 발생한 오류를요 작업 내에서 오류를 처리하거나 작업을 저장하고 나중에 오류를 확인하도록 알려줍니다 defer 블록에서 async 함수를 호출하던 기존 제한도 이제 없어졌습니다 @unchecked Sendable을 사용해야 하는 클래스가 있다면 weak var 프로퍼티가 있기 때문일 것입니다 이제 그 프로퍼티를 weak let으로 변경해 불변으로 만들면 Sendable 검사를 사용하는 데 문제가 없습니다 타입이 Sendable이 아니어야 한다면 새로운 tilde Sendable 구문으로 명시적으로 나타낼 수 있습니다 추가 이점으로 서브클래스가 Sendable이 되는 것도 막지 않습니다 internal과 private 프로퍼티가 혼합된 구조체는 이제 두 번째 memberwise 이니셜라이저를 갖게 되어 프로젝트의 다른 파일에서도 사용할 수 있습니다 하지만 반드시 알아챌 수 있는 변경 사항도 있습니다 Apple 생태계는 지난 20여 년간 성장해 왔고 Swift 코드의 가용성 속성도 함께 늘어났습니다 지난해 Apple은 이 문제를 해결하기 시작했습니다 OS 릴리스의 버전 번호를 통일하는 방식으로요 이제 Swift가 그것을 한 단계 더 발전시켜 모든 플랫폼 이름을 "Any Apple OS" 하나로 줄일 수 있게 했습니다 관심 있는 모든 플랫폼에서 가용성이 일치한다면 모든 OS를 한 번에 지정할 수 있습니다 예외가 있다면 anyAppleOS로 기본값을 설정하고 예외 항목에 더 구체적인 속성을 추가하면 됩니다 #if os 조건에서도 동작하여 코드의 특정 부분을 완전히 제외하고 컴파일할 수 있습니다 세월이 지나며 API도 변합니다 진화하면서 이전 API가 deprecated로 표시되어 최신 버전으로 이동하라고 알려줍니다 하지만 즉시 그렇게 하지 못하는 경우도 있습니다 새 API가 너무 달라서 적응할 시간이 필요할 수 있으니까요 이 경고를 일시적으로 끌 수 있다면 좋지 않을까요 나머지 프로젝트에는 영향을 주지 않으면서요 이제 그렇게 할 수 있습니다 @diagnose 속성으로 특정 경고의 동작을 변경할 수 있습니다 특정 선언 내에서요 deprecated declaration 경고 그룹을 무시하도록 Swift에 지시해 해당 위치에서만 그 경고를 없앨 수 있습니다 기본적으로 꺼진 경고를 선택적으로 활성화하는 데도 사용됩니다 예를 들어 엄격한 메모리 안전성을 켜서 보안에 중요한 함수에서 unsafe API 사용을 감사했는지 확인할 수 있습니다 특정 경고를 오류로 처리하는 것도 가능합니다 여기서는 미래에 오류가 될 경고를 지금 바로 오류로 업그레이드했습니다 바로 지금 오류로요 이 모든 것이 많은 유연성을 제공합니다 Swift 6.3에는 두 모듈에 동일한 이름의 API가 있을 때를 더 잘 처리하는 기능도 있습니다 동일한 이름의 API가 있는 상황을 더 잘 처리합니다 예를 들어 Rocket 모듈에 어떤 타입이 있을 수 있습니다 실제 100미터 높이의 SaturnV 로켓을 나타내는 타입이요 GiftShopToys 모듈도 있을 수 있고 아이들 크기의 SaturnV 로켓 모형을 나타내는 타입도 있습니다 같은 파일에 두 모듈을 모두 임포트했다면 Swift는 어느 것을 원하는지 알 수 없어 오류를 발생시킵니다 이 경우 항상 코드를 명확하게 하는 방법이 있었습니다 점 문법을 사용하는 것입니다 Rocket.SaturnV는 먼저 Rocket 모듈을 찾고 그 안에서 SaturnV 타입을 찾습니다 다른 곳에 또 다른 SaturnV가 있는지는 확인하지 않습니다 대부분 동작하지만 잘 안 되는 까다로운 상황도 있습니다 예를 들어 Rocket 모듈에 Rocket이라는 타입도 있다면 어떻게 될까요 어떻게 되는지 말씀드릴게요 Swift는 모듈 이름보다 타입 이름을 우선합니다 그래서 Rocket 타입을 의미한다고 결정하고 그 안에서 SaturnV라는 멤버를 찾습니다 찾지 못하면 오류가 발생합니다 Swift 6.3에는 이 문제의 해결책이 있습니다 점을 이중 콜론으로 바꾸면 됩니다 이 새 문법은 모듈 선택자라고 합니다 왼쪽의 이름은 항상 모듈 이름으로 처리됩니다 Swift는 Rocket 타입을 무시하고 Rocket 모듈로 바로 이동해 원하는 타입을 찾는 데 문제가 없습니다 이 문법은 메서드나 프로퍼티 이름에도 사용할 수 있습니다 타입이 동일한 이름의 메서드를 가질 때 유용합니다 두 모듈의 익스텐션에서 받은 것일 때요 잘못된 메서드를 호출하면 누군가의 하루를 망칠 수 있으니까요 모듈 선택자는 정말 유용합니다 제어할 수 없는 두 모듈 사이에 충돌이 있을 때요 SwiftUI와 사용 중인 데이터베이스 패키지 모두에 View라는 타입이 있을 때처럼요 모듈 선택자가 큰 도움이 됩니다 방어적으로 사용하는 것도 좋은 아이디어입니다 매크로 확장 및 기타 자동 생성 코드에서요 프로젝트에 어떤 것이 임포트됐는지 알 수 없으니까요 하지만 이름 충돌이 생기도록 API를 의도적으로 설계한 뒤 모듈 선택자로 구분하는 것은 권장하지 않습니다 코드가 모호하지 않더라도 일부 오류 메시지와 문서가 여전히 혼란스러울 수 있습니다 그러니 사용하지 마세요 언어의 새로운 기능을 살펴봤으니 중요한 라이브러리의 업데이트를 Evan이 소개합니다 이 모든 언어 개선 사항은 같은 목표를 공유합니다 불필요한 노이즈 없이 정확하게 의도를 표현하는 것입니다 같은 목표가 매일 사용하는 라이브러리의 업데이트에도 이어집니다 표준 라이브러리, Swift Testing, Subprocess, Foundation입니다표준 라이브러리의 업데이트부터 시작하겠습니다 특히 작업 취소를 위한 새로운 도구들이 있습니다 딕셔너리 변환과 파일 경로 조작 도구도요 비용이 많이 드는 작업을 시작하기 전에 작업 취소 상태를 확인하는 것이 중요합니다 하지만 작업이 취소된 후에도 실제로 작업을 수행해야 할 때가 있습니다 파일을 손상시키지 않도록 디스크에 데이터 쓰기를 마치는 것처럼요 이제 작업 취소 실드를 사용할 수 있습니다 실드 내부에서는 작업 취소 확인이 항상 false를 반환합니다 이 영역을 짧게 유지하는 것이 중요합니다 이미 시작한 작업을 완료하거나 롤백하는 데 집중해야 합니다 딕셔너리에서 값을 매핑하는 기능도 업데이트됐습니다 mapValues는 이전 값만 매핑 클로저에 전달했기 때문에 새 값을 계산하는 데 키가 필요하다면 새 딕셔너리를 직접 구성해야 했습니다 이제 mapKeyedValues를 호출하면 키와 이전 값을 모두 매핑 클로저에 전달해 새 값을 계산할 수 있습니다 프로그램은 종종 파일 경로를 조작해야 합니다 플랫폼마다 파일 경로를 표현하는 방식에 미묘한 차이가 있어 올바르게 처리하기 까다로울 수 있습니다 올해 Swift System의 타입을 기반으로 표준 라이브러리에 새로운 filepath 타입을 Swift System의 타입을 기반으로 추가하여 올바르게 사용하기 쉬워졌습니다 Swift 6.4의 테스팅은 테스트 동작에 대한 더 많은 제어권을 제공합니다 치명적이지 않은 이슈를 표시하고 테스트 케이스를 동적으로 건너뛸 수 있습니다 Issue.record로 기록되는 이슈의 심각도 수준을 설정할 수 있습니다 경고로 설정하면 조사할 가치가 있는 이슈를 테스트 케이스에서 표시할 수 있습니다 CI 워크플로를 차단할 정도는 아닌 이슈를요 Test.cancel API를 호출해 테스트를 동적으로 취소할 수 있습니다 이는 매개변수화된 테스트에서 특히 강력합니다 실행하면 안 되는 개별 인수를 취소할 수 있습니다 완료까지 실행하거나 테스트를 실패시키는 대신에요 불안정한 테스트를 다루고 있을 때가 있습니다 swift test 명령에 테스트를 반복하는 새 기능이 추가됩니다 통과하거나 실패할 때까지요 최대 반복 횟수도 제어할 수 있습니다 테스트가 통과할 때까지 반복하도록 지정하면 실패한 테스트만 재실행합니다 이미 통과한 테스트는 재실행하지 않아 시간을 절약합니다 많은 프로젝트에 XCTest API를 기반으로 구축된 유틸리티가 있는 대규모 테스트 스위트가 있습니다 Swift 6.4에서는 XCTest의 assertion 실패가 Swift Testing에서 호출될 경우 테스트 이슈로 보고됩니다 이는 Swift Testing으로 마이그레이션할 때 도중에 테스트 커버리지를 잃을 걱정 없이 진행할 수 있음을 의미합니다 그리고 상호 운용성은 반대 방향으로도 동작합니다 #expect 매크로 같은 Swift Testing API가 XCTestCase에서 호출될 때도 동작합니다 Swift Testing으로 헬퍼 API를 구축하면 일관된 동작을 갖게 됩니다 XCTest에서 호출하든 Swift Testing에서 호출하든 관계없이요 기존 프로젝트에서 작업 중이라면 이미 Swift Testing에서 XCTest 어설션을 호출하고 있을 수 있습니다 반대 경우도 마찬가지입니다 이 전환을 더 쉽게 만들기 위해 이러한 이슈는 기본적으로 경고로 보고됩니다 Xcode 빌드 설정에서 테스트 실패로 승격하도록 선택할 수 있습니다 Swift Testing 상호 운용성과 마이그레이션 전략에 대한 자세한 내용은 "Migrate to Swift Testing"을 확인하세요 지난해 Subprocess 패키지를 발표했습니다 서브프로세스를 실행하기 위한 현대적인 API를 포함하는 패키지입니다 올해는 Subprocess 1.0을 출시합니다 실제 사용에서 받은 피드백을 반영했습니다 API 개선 사항에는 간소화된 실행 타입이 포함됩니다 향상된 오류 처리 및 프로세스 출력을 쉽게 스트리밍하는 편의 API도 있습니다 크로스 플랫폼 지원도 크게 개선됐습니다 플랫폼별 프로세스 파일 디스크립터와 종료 상태를 포함해 서로 다른 플랫폼의 시맨틱을 더 정확하게 반영합니다 Subprocess 1.0의 개선된 API 예시를 살펴보겠습니다 서브프로세스를 실행하기 위해 run 메서드를 호출할 때 표준 출력과 표준 오류 스트림을 AsyncBufferSequence로 실행 객체에 포함할 수 있습니다 이 모델은 각 스트림이 단 한 번만 생성됨을 보장합니다 AsyncBufferSequence의 새 strings() 메서드로 서브프로세스의 출력을 줄 단위로 쉽게 읽을 수 있습니다 이 API는 자소 클러스터 경계를 존중합니다 멀티바이트 문자가 분리될 걱정을 하지 않아도 됩니다 마지막으로 Foundation의 개선 사항에 대해 이야기하겠습니다 ProgressManager는 진행 상황 보고를 위한 Foundation의 새 타입입니다 async/await 스타일의 동시성과 잘 동작하도록 설계됐습니다 진행 구성과 진행 보고를 깔끔하게 분리합니다 구조화되고 타입 안전한 추가 메타데이터 첨부 메커니즘을 제공합니다 2년 전에 Swift-Foundation을 발표했습니다 Foundation을 안전하고 일관된 Swift로 작성된 크로스 플랫폼 코드베이스로 마이그레이션하는 작업입니다 올해도 그 작업을 계속해 수십 년된 Objective-C를 현대적인 Swift로 교체했습니다 올해는 Data의 더 많은 부분을 현대화해 전반적인 개선을 이루었습니다 더 빠른 span 접근, 동등성 검사, 반복, 변형을 포함해서요 Apple 플랫폼에서 Data와 NSData 사이의 브리징도 더 빨라졌습니다 NSURL과 CFURL이 단일 Swift 구현으로 통합됐습니다 Swift를 활용해 이 타입들이 더 빠르게 실행되고 메모리도 적게 사용합니다 Foundation처럼 크고 성숙한 라이브러리를 마이그레이션하는 것은 Swift의 언어 상호 운용성 덕분에 가능합니다 Swift에서 완전히 새로운 API를 추가하고 마이그레이션할 수 있습니다 API 표면을 변경하지 않고 기존 API의 구현을 Swift로 마이그레이션할 수 있습니다 언어 경계를 넘는 것 외에도 많은 실제 프로젝트가 서비스, 기기, 웹 컴포넌트로 확장됩니다 크로스 플랫폼 클라이언트 등으로도요 Apple에서는 OS의 모든 부분에 Swift를 사용합니다 날씨 앱, 실시간 전화 스팸 감지 서비스 등 커널 자체, 그리고 펌웨어의 최하위 레이어까지요 Swift는 소프트웨어 스택의 모든 레이어에서 활용할 수 있는 언어로 설계됐습니다 기존 소프트웨어 시스템에서 더 쉽게 사용할 수 있도록 Swift 6.4는 언어 상호 운용성을 확장합니다 크로스 플랫폼 IDE 지원을 개선하고 더 쉽고 안전하게 웹이나 임베디드 기기 같은 다른 환경에 Swift 코드를 가져갈 수 있게 합니다 C를 Swift로 쉽게 임포트하는 것은 항상 가능했습니다 Swift 6.4는 Swift로 작성된 함수를 다시 C에 노출할 수 있게 합니다 앱을 Objective-C에서 Swift로 마이그레이션할 때 @objc 속성을 사용해 보셨을 것입니다 @C 속성은 같은 방식으로 동작하지만 C를 위한 것입니다 @C 속성은 C 호환 타입에서 동작하는 함수에 적용됩니다 C에서 Swift로 임포트할 수 있는 모든 타입은 Swift에서 C로 내보내는 함수에서도 사용할 수 있습니다, 정수처럼요 포인터, 임포트된 C 구조체, 그리고 정수 원시 값 타입을 가진 열거형도요 컴파일러는 C와 호환되지 않는 타입을 실수로 전달하는 것을 방지합니다 C와 호환되지 않는 타입을요 @C 속성이 동작하는 것을 살펴봅시다 로켓 발사 일정을 예약하는 앱을 작업하고 있습니다 발사 창을 추적하는 코드에 집중하겠습니다 현재 C로 작성되어 있습니다 메모리 안전성과 개발자 편의성 향상을 위해 Swift로 마이그레이션하고 있습니다 Swift 6.4의 새 기능을 활용해 애플리케이션의 일부를 C에서 재작성하겠습니다 현재 헤더에 두 가지 함수가 선언되어 있습니다 하나는 발사 창의 길이를 구하는 것이고 다른 하나는 발사 창 컬렉션에서 총 소요 시간을 계산하는 것입니다 발사대 활용도를 계산할 수 있게 해줍니다 발사 창의 길이를 계산하는 함수의 C 구현을 교체하는 것부터 시작하겠습니다 @c와 @implementation 특성을 함께 사용하면 별도의 C 선언 없이도 C 함수를 구현할 수 있습니다 원래 헤더 파일에 이미 선언이 있기 때문입니다 다음으로 여러 실행 구간에 걸친 총 실행 시간을 계산하는 함수를 구현하겠습니다 Swift에서 구현할 때 함수는 그대로 임포트됩니다 타입을 네이티브 Swift 타입으로 변환해 Swift의 편의성을 활용해 C 함수를 재구현할 수 있습니다 안전한 상호 운용 기능은 함수를 호출할 때 안전한 래퍼를 제공합니다 배열과 카운트를 따로 전달하는 대신 span을 전달할 수 있습니다 작성하기 쉽고 안전합니다 작업하는 동안 평균 발사 창 길이를 구하는 새 함수를 추가하고 싶습니다 여기서는 @implementation 속성을 사용하지 않습니다 원래 C 헤더에 선언되지 않은 함수이기 때문입니다 Swift 컴파일러는 생성된 C 상호 운용 헤더에 적절한 함수 선언을 내보냅니다 그래서 C 코드에서 새 함수를 호출할 수 있습니다 Swift는 Swift Span을 C로 자동으로 브리징합니다 이제 Swift C++ 상호 운용성도 Swift와 C++ 20 span 사이의 브리징을 지원합니다 이 선언은 span과 함께 Swift로 임포트되므로 span을 직접 전달할 수 있습니다 상호 운용성은 다른 언어로 작성된 기존 코드베이스에 Swift를 도입하는 데 핵심입니다.Swift-Java는 Swift와 Java 사이의 상호 운용성을 가능하게 하는 패키지입니다 이제 Java에서 async와 throwing Swift 함수를 호출하는 것을 지원합니다 제네릭 시스템의 더 많은 기능을 캡처합니다 제약이 있는 익스텐션을 포함해서요 Java 클래스를 Swift 프로토콜에 적합하게 만드는 것도요 이 모든 개선 사항으로 Java와 Kotlin에서 Swift 코드를 호출하는 것이 Android에서 자연스럽게 느껴집니다 이제 swift.org에서 Android용 공식 Swift SDK를 다운로드할 수 있습니다 VSCode용 Swift 익스텐션 최신 버전이 Swiftly와의 새로운 통합을 추가합니다 에디터에서 바로 swift.org의 툴체인을 쉽게 설치할 수 있습니다 이제 어떤 플랫폼에서든 항상 올바른 툴체인을 바로 사용할 수 있습니다 지난해 Visual Studio 마켓플레이스에 Swift 익스텐션을 추가했습니다올해는 OpenVSX 마켓플레이스에도 추가했습니다 통합 Swift 환경을 새로운 에디터에서도 사용할 수 있게 됩니다 VSCodium, Cursor, Kiro, Antigravity 포함해서요 플러그인 최신 버전은 Swift 설치 체크리스트를 제공해 Swift를 더 쉽게 시작할 수 있게 해줍니다 새 프로젝트 생성, 코드 실행 테스트 설정, 문서 생성도 포함합니다 Swiftly가 설치되어 있다면 툴체인 관리를 도와줍니다 나이틀리 툴체인을 설치하겠습니다 Web Assembly를 대상으로 하는 새 언어 기능을 사용하기 위해서입니다 임베디드 플랫폼도요 방금 설치한 오픈 소스 툴체인으로 Web Assembly로 컴파일할 수 있습니다 Swift의 Wasm 지원은 동일한 언어로 네이티브 앱을 작성하고 백엔드 웹서버와 프론트엔드도 작성할 수 있음을 의미합니다 오픈 소스 프로젝트 JavascriptKit의 JavaScript 상호 운용성 개선 사항을 살펴봅시다 오픈 소스 프로젝트 JavascriptKit에서 나온 것들입니다 Swift와 Javascript 사이의 브리징은 이전에 많은 동적 조회를 포함했습니다 타입이 일치하기를 바라면서요 JavascriptKit의 최근 작업들이 언어 간 브리징을 더 안전하고 빠르게 만들었습니다 코드가 네이티브 Swift 코드처럼 보이지만 Javascript를 통해 WebGL을 호출하고 있습니다인기 있는 노트 앱 Goodnotes는 네이티브 iOS 앱에 더해 최근 웹 기반 인터페이스를 구현했습니다 핵심 앱을 다른 언어로 이전하는 비용 버그를 해결하고 두 코드베이스를 유지하는 비용이 너무 높았습니다 그들은 기존의 검증된 Swift 코드를 가져다가 Wasm을 사용해 웹용으로 컴파일했습니다 JavaScriptKit의 개선으로 벤치마크 결과 안전한 브리징이 동적 브리징보다 35~40배 빠른 것으로 나타났습니다 Swift의 Wasm 지원은 네이티브 iOS 애플리케이션에서 실행 중인 Swift 코드를 가져다가 웹앱의 일부로 실행할 수 있다는 것을 의미합니다 하지만 네이티브 애플리케이션과 달리 사용자가 사이트를 방문할 때마다 컴파일된 코드를 각 기기로 보내야 합니다 CDN이 도움이 되지만 큰 바이너리는 개발자와 고객의 데이터를 빠르게 소모할 수 있습니다 그래서 크기가 그 어느 때보다 중요한 문제입니다 임베디드 Swift를 사용하면 더 제한된 환경에서도 Swift의 편의성을 활용할 수 있습니다 언어를 축소해서 맞췄습니다 이러한 환경에서 사람들이 Swift를 어떻게 사용하는지 배우면서 사용 가능한 언어 하위 집합을 늘려가고 있습니다 임베디드 Swift는 이제 existential 타입을 지원합니다 프로토콜을 준수하는 여러 타입을 사용할 수 있게 됐습니다 배열에 저장하거나 함수에 전달하는 방식으로요 typed throws는 오류 타입을 알 때 유용하지만 특정 오류 타입으로만 제한됩니다 existential 타입을 처리하는 동일한 내부 메커니즘을 사용해 임베디드 Swift가 이제 untyped throws를 지원합니다 디버거는 변수를 표시하기 위해 타입 레이아웃에 대한 추가 메타데이터가 필요합니다 바이너리 크기를 줄이기 위해 임베디드 Swift는 해당 데이터를 바이너리 자체에 포함하지 않습니다 또한 많은 임베디드 시스템은 프로그램이 충돌했을 때의 메모리 상태 코어 덤프만 남겨둡니다 라이브 프로세스를 디버깅하는 것도 아닙니다 Swift 6.4는 필요한 모든 메타데이터를 DWARF 디버그 정보에 저장합니다 바이너리 크기는 유지하면서 임베디드 Swift 코어 덤프 디버깅 경험을 크게 개선합니다 이것은 올해 이루어진 개선 사항 중 일부에 불과합니다 완전한 Swift와 임베디드 Swift 사이의 간격을 좁히는 것들입니다 임베디드 플랫폼에서 동작하는 Swift 하위 집합이 커지고 있지만 여전히 언어의 하위 집합입니다 EmbeddedRestrictions 경고 그룹의 진단은 임베디드 환경에서 사용할 수 없는 언어 기능을 식별합니다 임베디드와 완전한 Swift를 모두 지원하는 라이브러리를 다룬다면 임베디드 컨텍스트에서 사용할 수 없는 기능을 사용하는 함수를 노출해야 할 수 있습니다 앞서 Becca가 보여준 @diagnose 속성으로 이 진단을 제어할 수 있습니다 임베디드 코드는 종종 매우 제한된 하드웨어 환경에서 실행됩니다 그래서 모든 클록 사이클을 최대한 활용하는 것이 중요합니다 Becca에게 돌아가서 Swift 코드에서 성능을 끌어내는 방법을 알아봅시다 Swift는 뛰어난 성능을 발휘하도록 설계됐습니다 가장 간결하고 표현력 있는 로직 구현을 작성했을 때도요 하지만 많은 연산을 수행하거나 임베디드 시스템처럼 제한된 환경에서 실행할 때는 때로는 코드를 더 복잡하게 만드는 것이 중요한 순간에 성능을 조금 더 끌어내기 위해 가치 있을 수 있습니다 대부분의 경우 이런 고급 성능 기능은 필요 없겠지만 필요할 때 갖고 있다면 다행일 것입니다올해 작업한 두 가지 영역에 집중하겠습니다 옵티마이저 결정을 명시적으로 제어하는 것과 불필요한 복사를 안전하게 방지하기 위해 소유권 시스템을 확장하는 것입니다 Swift 컴파일러의 옵티마이저는 코드를 더 빠르게 만들기 위해 수십 가지 기법을 적용합니다 하지만 가장 강력한 기법 중 일부는 잘못 적용되면 오히려 역효과를 낼 수 있습니다 특정 상황에 맞게 코드를 복제해 커스터마이징하는 것들입니다 커스터마이징이 효과를 내지 못하면 프로그램이 오히려 더 크고 느려질 수 있습니다 예를 들어 컴파일러가 수행하는 가장 중요한 최적화 중 하나는 인라이닝입니다 함수 호출을 구현으로 대체하고 해당 구현을 특정 호출 위치에 맞게 최적화하는 것입니다 인라이닝이 효과를 낼 때는 같은 결과를 위해 더 적은 작업을 수행하지만 그렇지 않을 때는 더 빠르게 만들지 않고 바이너리만 크게 만듭니다 그런 일이 발생하지 않도록 옵티마이저는 인라이닝이 효과적일지 판단하기 위해서 함수와 호출 위치를 분석합니다 하지만 때로는 잘못된 결정을 내리기도 해서 강제로 적용할 방법이 필요할 수 있습니다 Swift에는 오래전부터 @inline(never) 속성이 있어 인라이닝을 완전히 금지합니다 함수를 인라이닝하는 것이 절대 이득이 되지 않을 것을 알 때 유용합니다 Swift 6.4에서는 이에 대응하는 @inline(always) 속성이 생겼습니다 컴파일러가 강제로 인라이닝하도록 합니다 옵티마이저가 좋은 아이디어인지 확신하지 못할 때도요 때로는 여전히 불가능할 수 있습니다 재정의될 수 있는 객체 메서드를 호출할 때처럼요 그래서 클래스 메서드에 @inline(always)와 함께 final 사용을 고려하세요 또 다른 중요한 최적화는 특수화입니다 특정 구체적인 타입을 위해 제네릭 함수를 복제하는 것으로 제네릭 오버헤드를 없애고 추가 최적화를 가능하게 합니다 함수를 특수화하는 것은 옵티마이저가 해당 타입으로 사용될 것을 알 때만 도움이 됩니다 하지만 때로는 특히 라이브러리에서 컴파일러가 함수가 어떻게 사용될지 알 수 없습니다 Swift 6.3은 이를 직접 제어하기 위한 @specialized 속성을 도입합니다 이를 직접 제어하기 위한 것입니다 속성 내부에서 일부 또는 모든 제네릭 매개변수를 제약하는 where 절을 작성합니다 Swift는 해당 제약 조건으로 함수의 특수화 버전을 생성합니다 느린 제네릭 코드가 있는데 한두 가지 특정 타입으로 많이 사용된다면 Swift에 그 타입들을 우선시해야 한다고 알릴 수 있습니다 하지만 성능 조율을 위해 이룬 가장 큰 개선은 소유권 시스템입니다 무엇을 했는지 이해하기 위해 이미 가진 것을 먼저 살펴봅시다 Swift의 많은 성능 문제는 불필요한 데이터 복사로 귀결됩니다 한 곳에 데이터가 있고 다른 곳에 필요하면 새 저장소에 데이터를 복사합니다 때로는 이 컴포넌트들이 큽니다 앱의 모델과 뷰 레이어처럼요 때로는 작기도 합니다 for 루프 in 키워드 양쪽의 두 변수처럼요 하지만 기본 패턴은 같습니다 복사로 인한 속도 저하를 해결하는 방법은 특정 상황에서 복사가 불필요하다는 것을 인식하는 것입니다 저장소가 할당된 상태로 유지될 것을 알고 두 컴포넌트 모두 Swift의 배타성 규칙을 따른다면 둘 다 접근 가능한 데이터를 변형하지 않으므로 데이터를 복사할 필요가 없습니다 기존 저장소에 대한 접근 권한만 부여하면 됩니다 복사를 피하는 가장 간단한 방법은 데이터를 객체에 넣어 그 객체를 다른 컴포넌트에 전달하는 것입니다 대부분 충분하지만 문제를 완전히 없애지는 못합니다 결국 객체는 참조 카운트가 있어서 객체를 전달하면 참조 카운트가 바뀌기 때문입니다 객체를 해제하고 보유하는 것은 큰 값을 복사하는 것보다 오버헤드가 적지만 성능에 가장 민감한 코드에서는 여전히 너무 느릴 수 있습니다 객체가 옵션이 아닐 때는 전통적으로 저장소에 대한 UnsafePointer를 전달해야 했습니다 그 문제는 이름에 있습니다 UnsafePointer는 안전하지 않습니다 이렇게 저장소를 공유하는 것은 두 컴포넌트 모두 특정 규칙을 따르기 때문에만 안전합니다 하지만 컴파일러는 그 규칙을 모르고 확인할 수도 없습니다 각 측이 약속을 지킬지 보장할 수 없습니다 컴포넌트 1이 컴포넌트 2가 변경되지 않기를 기대하는 데이터를 변형할 수 있습니다 또는 컴포넌트 2가 아직 사용 중인데 저장소를 해제할 수 있습니다 C 언어처럼 메모리 안전성이 없는 세계로 돌아가는 것입니다그래서 몇 년 전부터 더 나은 해결책을 연구했습니다 저장소를 안전하게 공유하기 위한 보장 집합을 borrow로 정의했습니다 컴포넌트 2가 저장소를 빌리는 동안은 두 컴포넌트 모두 읽기만 가능하고 쓸 수 없습니다 컴포넌트 2가 먼저 저장소 사용을 완료해야 합니다 완료되면 컴포넌트 1이 완전한 제어권을 되찾습니다 변형은 비슷하지만 다른 컴포넌트가 저장소에 접근하는 것이 완전히 차단됩니다 반쯤 업데이트된 데이터를 읽거나 다른 컴포넌트가 변경 사항을 쓰는 시점에 따라 달라지는 동작을 방지합니다 다른 컴포넌트가 변경 사항을 쓰는 시점에 따라서요 빌리든 변형하든 Swift는 컴파일 시간에 두 컴포넌트 모두 규칙을 따르는지 검증할 수 있습니다하지만 우리의 목표는 항상 이런 고급 사용 사례를 지원하는 것이었습니다 일반 Swift 코드 작성 방식에는 영향을 주지 않으면서요 그러려면 소유권 기능을 신중하고 점진적으로 설계해야 했습니다 수년에 걸친 과정이었고 아직 끝나지 않았지만 올해 몇 걸음 더 나아갔습니다 예를 들어 Equatable, Comparable, Hashable 프로토콜이 이제 복사 불가능한 타입에서도 사용할 수 있습니다 Equatable과 Comparable은 탈출 불가능한 타입에서도 사용할 수 있습니다 이로써 성능과 안전성을 위해 조율된 타입들이 일반 Swift 타입이 가진 가장 보편적인 기능 중 일부를 활용할 수 있습니다 연관 타입도 이제 복사 불가능하거나 탈출 불가능할 수 있습니다 이는 고성능 프로토콜 기반 개발을 위한 강력한 새로운 기능을 열어줍니다 여기에 보이는 Iterable 프로토콜처럼요 요소 타입은 복사 불가능하고 이터레이터 타입은 복사 불가능하면서 탈출 불가능하기도 합니다 물론 복사 가능하거나 탈출 가능한 타입을 사용할 수 없다는 것이 아닙니다 프로토콜이 그것을 요구하지 않는다는 의미일 뿐입니다 Iterable 타입이 정말 유용해 보이는군요 실제로 있으면 좋겠다고 생각하지 않으신가요? 좋은 소식이 있습니다, 실제로 있습니다!Swift 6.4의 for 루프가 새로운 Iterable 프로토콜을 지원합니다 우리 모두 알고 좋아하는 Sequence 프로토콜은 시퀀스에서 요소를 복사해 내는 방식으로 동작합니다 Iterable 프로토콜은 for 루프가 대신 빌릴 수 있게 합니다 복사 불가능한 요소와도 동작한다는 의미이고 또한 객체나 Copy-on-Write 타입을 다룰 때 참조 카운팅을 수행할 필요가 없습니다 또한 루프 중에 선택적으로 오류를 던질 수 있습니다 ASYNC Sequence처럼요 하지만 모든 borrow와 마찬가지로 배타성 검사로 인해 루프를 도는 동안 Iterable을 변형할 수 없습니다 반드시 나쁜 것은 아닙니다 Sequence에서 종종 성능 함정이었으니까요 이 동작 차이로 인해 for 루프는 Sequence 프로토콜이 있다면 이를 우선시하고 그렇지 않으면 Iterable로 대체합니다 Sequence처럼 Iterable은 이터레이터를 생성해 for 루프가 요소를 가져오게 합니다 for 루프가 요소를 가져올 이터레이터를요 하지만 시퀀스와 달리 요소를 배치로 가져옵니다 for 루프는 이터레이터에게 요소들의 span을 요청해 하나씩 처리합니다 그런 다음 또 다른 span을 요청해 그것들도 처리합니다 요소가 없어지면 빈 span을 반환해 for 루프가 종료됩니다 이 배치 구조는 루프를 훨씬 더 효율적으로 만들며, 특히 한 번에 모든 것을 큰 span으로 반환할 수 있는 타입에서 효과적입니다 Swift 6.4는 접근자에도 중요한 개선을 이루었습니다 왜 중요한지 이해하기 위해 이 UniqueBox 구조체를 살펴봅시다 큰 값에 대한 포인터를 자동으로 관리하고 접근하기 위한 계산 프로퍼티를 제공합니다 안타깝게도 지금 작성된 방식으로는 이 프로퍼티에 심각한 성능 문제가 있습니다 왜 그런지 살펴봅시다 UniqueBox에 256개의 Int로 구성된 InlineArray를 넣는다고 가정합시다 InlineArray는 인라인이므로 64비트 기기에서 2킬로바이트 구조체가 됩니다 항상 복사하고 싶지 않은 크기입니다! get과 set이 데이터를 복사해 동작하므로 문제입니다 배열에서 Int 하나를 바꾸려면 전체를 꺼냈다가 다시 넣어야 합니다 성능에 그다지 도움이 되지 않습니다 다행히 이제 더 나은 옵션이 있습니다 get과 set 대신 borrow와 mutate로 전환할 수 있습니다 새 borrow 접근자는 공유 저장소에 대해 읽기 전용 접근을 제공합니다 복사 없이요 mutate 접근자는 독점적 접근을 제공해 제자리에서 수정할 수 있습니다 전환하면 Swift는 아무것도 복사하지 않고 원본 배열의 요소를 직접 변형할 수 있습니다 그리고 추가 이점으로 새 접근자로 UniqueBox가 복사 불가능한 값도 처리할 수 있습니다 borrow와 mutate 접근자는 성능과 표현력 모두에 큰 도움이 됩니다 모든 분이 사용할 수 있게 되어 기쁩니다 UniqueBox 타입이 정말 유용해 보이는군요 있으면 좋겠다고...알겠습니다, 어떻게 끝나는지 아시죠 다시 하지 않겠습니다 UniqueBox는 이제 표준 라이브러리에 실제로 있는 타입입니다 안전하지 않은 코드를 작성하지 않아도 되는 다른 새 API도 있습니다 "Unique Array"는 일반 Swift Array와 매우 유사하지만 복사 불가능하다는 점이 다릅니다 복사 불가능한 요소를 저장하고 고정 크기에 제한받지 않고 참조 카운팅 오버헤드를 피할 수 있습니다 withTemporaryAllocation 함수는 UnsafeMutableBufferPointer 대신 OutputSpan을 사용해 임시 메모리가 안전하게 처리되도록 합니다 Continuation 타입은 한 번만 재개할 수 있음을 컴파일 시간에 검사합니다 CheckedContinuation보다 더 안전하게 만듭니다 UnsafeContinuation만큼 효율적이면서도요 소개하고 싶은 타입이 한 쌍 더 있습니다 기존 API의 개선된 버전이 아니라 언어에 새로운 기능을 가져다주는 것들입니다 Ref는 Span과 비슷하지만 여럿이 아닌 하나의 값을 위한 것입니다 borrow나 변형을 위한 컨테이너 같은 것으로 변수에 저장하고 함수에서 전달하고 반환하며 제네릭 타입에서도 사용할 수 있습니다 저장소를 빌림으로써 읽기 접근에서 Ref 타입 인스턴스를 만들 수 있습니다 또는 쓰기 접근에서 MutableRef 타입 인스턴스를 접두사 앰퍼샌드를 사용해 만들 수 있습니다 이 타입들로 이전에는 작성할 수 없었던 새 API를 만들 수 있습니다 프로퍼티 중 하나에 대한 MutableRef를 반환하는 메서드처럼요 성능 문제를 해결하는 데도 사용할 수 있습니다 예를 들어 이 updateCount 함수를 살펴봅시다 증가시킬 때마다 딕셔너리 키를 조회합니다 키가 항상 같음에도 불구하고요 보통 이런 반복되는 작업은 루프 밖으로 빼서 한 번만 수행하도록 하는 것이 좋지만 지금까지는 딕셔너리 조회를 하고 잠시 열어두는 유일한 방법은 루프를 함수로 옮기고 조회를 inout 매개변수로 전달하는 것이었습니다 꽤 난해한 트릭이었죠!

    MutableRef는 더 나은 방법을 제공합니다 이제 루프 시작 전에 한 번 딕셔너리 조회에서 MutableRef를 만들 수 있습니다 루프가 시작되기 전에요 딕셔너리 항목을 변형할 때 그것을 사용하면 됩니다 Ref는 탈출 불가능하므로 Swift는 접근이 끝나는 시점을 알 수 있습니다 변수가 범위를 벗어날 때요 이 새로운 소유권 기능들이 결합되어 성능에 가장 민감한 코드의 속도를 높이는 것이 그 어느 때보다 안전하고 쉬워졌습니다 소유권 시스템만이 진행 중인 작업이 아닙니다 Evan에게 돌아가서 Swift의 미래에 대해 알아봅시다 오늘 다룬 기능들은 오픈 소스로 개발됐으며 전반적인 경험을 개선했습니다 Apple OS들, Linux, Windows, 그 이상에서요 오픈 소스 커뮤니티에서 일어나고 있는 미래 개발 사항들이 더 있습니다 여러분들이 참여하실 수 있습니다 지난해 Xcode의 빌드 시스템인 Swift Build를 오픈 소스로 공개한다고 발표했습니다 Swift Build는 이제 Swift Package Manager의 기본 빌드 시스템 백엔드입니다 Swift Package 빌드와 Xcode에서 보는 것 사이의 일관성을 개선합니다 늘어나는 워크그룹 목록에 빌드 및 패키징 워크그룹이 합류했습니다 Swift 커뮤니티의 빌드 및 패키징 요구 사항을 다루는 그룹입니다 네트워킹 워크그룹은 차세대 크로스 플랫폼 네트워킹 API를 설계합니다 그리고 Windows 워크그룹은 Windows에서의 Swift 경험을 개선합니다 올해 Android 워크그룹이 Swift 6.3의 일부로 Android용 첫 번째 Swift SDK를 출시했습니다 이제 Android와 iOS 앱 사이에서 Swift 코드를 공유할 수 있게 됐습니다 Swift 생태계 개발에 참여하고 싶다면 forums.swift.org의 Swift 포럼에 참여해 주세요 여러분만의 소중한 피드백을 기다리겠습니다 오늘 Swift의 새로운 기능에 대해 함께 알아봐 주셔서 감사합니다 버그 리포트를 제출하셨든 풀 리퀘스트를 올리셨든 이벤트에 참여하셨든 포럼에 참여하셨든 여러분의 기여가 Swift의 미래를 만들어 갑니다 모든 사람이 더 안전하고 쉽게 프로그래밍할 수 있도록 만들어 줍니다 감사합니다!