스트리밍은 대부분의 브라우저와
Developer 앱에서 사용할 수 있습니다.
-
Swift의 새로운 기능
Swift에 관한 새로운 소식을 확인하세요. 성능 개선 사항에 대해 안내하고, 더 안전하고 확장 가능한 Swift 패키지를 살펴보며, Swift Concurrency의 향상된 기능을 소개합니다. 또한 Swift Regex, 더 우수한 제네릭, 그리고 보다 유연하고 표현력 있는 코드를 작성할 수 있도록 언어에 내장된 기타 도구를 소개합니다.
리소스
- Celebrating learning experiences from the 2021 Swift Mentorship Program
- Contribute to Swift
- Diversity in Swift
- Swift Mentorship Program
관련 비디오
WWDC22
-
다운로드
♪ 부드러운 힙합 음악 ♪ ♪ 안녕하세요, Angela입니다 저는 Becca입니다 Swift 새 소식에 오신 걸 환영합니다! 오늘 Swift 5.7의 모든 새로운 기능에 대해 말씀드리게 되어 정말 기쁩니다 오늘 이야기할 많은 내용은 개발자로서의 삶을 더 쉽게 만들겠다는 Swift의 목표를 보여줍니다 오늘은 워크플로를 사용자 정의할 수 있는 새로운 툴링과 놀라운 내부 개선 사항 몇 가지에 대해 알아보죠 이어서 Swift의 최신 동시성 모델 및 전체 스레드 안전성을 포함한 Swift 6으로의 전환 방법에 대해 알아보겠습니다 마지막으로 더 깔끔하고 간단한 제네릭과 강력한 새 문자열 처리 기능을 포함하여 Swift의 읽기 및 쓰기가 더 쉬워지게 만든 몇 가지 언어 개선 사항을 살펴보겠습니다 하지만 먼저 Swift를 매우 특별하게 만드는 한 가지부터 이야기해 보도록 하죠 바로 여러분입니다 여러분의 의견과 기여 덕분에 Swift는 빠르게 확장할 수 있었습니다 커뮤니티의 참여는 Swift의 핵심입니다 작년에 발표된 문서 생성 도구인 docC와 Swift.org 웹 사이트가 오픈 소스로 제공되면서 올해 Swift 프로젝트가 커뮤니티에 더 많이 제공되었습니다 오픈 소스는 적극적인 커뮤니티가 이끌 때 가장 효과적입니다 우리는 Swift on Server와 Diversity in Swift에 대한 워크그룹 모델을 사용하여 특정 분야에 관심이 있는 커뮤니티 멤버에게 관리 및 지원을 제공하고 있습니다 이 모델은 그동안 매우 효과적이어서 새로운 작업 그룹 두 개를 더 시작했죠 하나는 Swift 웹 사이트에서 반복하여 더 좋은 커뮤니티 리소스로 만드는 그룹이고 다른 하나는 C 상호 운용성을 목적으로 C 와 Swift 사이에서 모델의 설계를 구체화합니다 새로운 분야에 도전할 때 우리 모두는 커뮤니티 멤버들의 지원이 필요합니다 그 일환으로 Diversity in Swift 작업 그룹은 작년에 Swift 멘토십 프로그램을 도입했습니다 이 프로그램은 시작하는 방법을 모르거나 특정 분야에서 전문성을 심화하려는 사람들을 위해 모든 작업 그룹 분야에 기여할 수 있는 경로를 제공하죠 작년 프로그램은 큰 성공을 거두었습니다 많은 멘티들이 관심을 가졌고 덕분에 41쌍이나 멘토십을 짝지을 수 있었죠 이 성공 덕분에 이 프로그램이 2년차에 또 돌아왔습니다 이 프로그램에 관심 있는 모든 사람이 참여하기를 원하지만 그러려면 여러분이 필요합니다 다양한 지식을 공유하고 새로운 관계를 맺을 준비가 되어 있으며 활발하며 경험이 풍부하고 개발자 말이죠 멘토십 프로그램은 단지 코드만 중요한 게 아니라 커뮤니티 안에서 관계를 형성하는 게 중요하니까요 또 약간의 지도가 지속적인 효과를 부를 수 있죠 제 말만 들을 필요 없습니다 Amrit는 작년에 멘토십 프로그램에 참여했고 컴파일러와 언어 설계에 집중했습니다 Amrit는 처음에 취미로 시작했지만 점점 실질적인 기여로 바뀌기 시작했습니다 새로운 도메인으로 뛰어들기란 쉽지 않습니다 하지만 그녀는 성공을 찾고 더 많이 기여할 영감을 얻으려고 떠났죠 다른 많은 사람들처럼 이 경험은 Amrit에게 기회를 열어 줬습니다 컴파일러 및 언어 설계 외에도 작년에는 기술적 글쓰기 및 테스트에서 Swift 패키지에 기여하는 데 이르기까지 광범위한 초점 영역이 제공되었습니다 올해는 더 많이 추가할 예정이며 새로운 주제를 다룰 기회는 항상 있습니다 이 목록에 관심 있는 항목이 없는 경우에도 신청서에 이를 언급할 수 있습니다 또 다른 추가 사항으로 올해 프로그램은 참여 능력은 낮지만 여전히 참여하고자 하는 사람들이라면 누구나 참여할 수 있도록 돕기 위해 시작 버그 기여에 대한 멘토십을 연중 제공할 것이라는 사실입니다 신청에 관심이 있거나 자세한 내용을 듣고 싶다면 최신 Swift 블로그 게시물을 확인하세요 거기서 강조 표시된 멘티의 자세한 성찰 내용에 대한 링크를 찾을 수 있습니다 멘토십 프로그램은 Swift 우산의 다양성 아래 있는 하나의 이니셔티브에 불과합니다 멘토십 프로그램과 Swift의 다른 다양성 노력에 대해 자세히 알아보려면 Swiftorg/diversity를 방문하세요 더 많은 기회를 열기 위해 우리는 여러분이 가진 소스로 가능한 한 쉽게 Swift를 사용할 수 있게 하고 싶습니다 우리는 Linux 패키지 포맷에 대한 지원을 추가하여 Linux 플랫폼을 위한 Swift 도구 체인 배포 프로세스를 간소화했습니다 새로운 기본 도구 체인 설치 프로그램을 통해 이제 Swift.org에서 Amazon Linux 2용 RPM과 CentOS 7을 직접 다운로드할 수 있습니다 이런 도구 체인은 실험적이므로 Swift.org 포럼에서 피드백을 공유해 주세요 Swift는 주로 앱을 만드는 데 사용됩니다 하지만 뛰어난 확장성을 통해 높은 수준의 스크립트에서 베어메탈 환경에 이르기까지 모두 사용되도록 하는 것이 항상 Swift의 목표였습니다 Swift를 이전에 사용하지 않던 곳에서 사용하도록 장려하기 위해 Swift는 올해 몇 가지 중요한 변화를 거쳤습니다 독립 실행형 정적 링크 바이너리용 표준 라이브러리를 더 작게 만들기 위해 외부 유니코드 지원 라이브러리에 대한 의존성을 줄이고 더 빠른 기본 구현으로 대체했습니다 이벤트 기반 서버 솔루션에서 실행할 때 더 작고 빠른 바이너리는 큰 장점입니다 Linux에서는 기본값으로 정적 링크를 사용하여 서버에 대한 컨테이너형 배포를 더 잘 지원합니다 이번 크기 감소로 Swift는 제한된 환경에서도 사용할 수 있게 되어 Apple의 Secure Enclave Processor에서 사용할 수 있게 됐습니다 Swift는 앱에서 서버 제한된 프로세서에 이르기까지 매우 유용하며 이를 하나로 묶는 것이 패키지 생태계입니다 올해 Swift 패키지의 새로운 기능들은 여러분의 삶을 더 좋게 바꿀 겁니다 먼저 Swift Package Manager는 TOFU를 도입했습니다 아니요, 맛있는 간식이 아닙니다 Trust On First Use의 약자인 TOFU는 패키지가 처음 다운로드될 때 패키지의 지문이 기록되는 새로운 보안 프로토콜입니다 이후 다운로드에서는 이 지문의 유효성을 검사하고 지문이 다른 경우 오류를 보고합니다 이는 패키지 생태계의 핵심에 신뢰와 보안이 어떻게 구축됐는지 보여주는 한 가지 예로 여러분이 자신 있게 사용하도록 돕습니다 명령 플러그인은 Swift 개발자의 워크플로를 개선할 수 있는 좋은 방법입니다 이들은 보다 확장 가능하고 안전한 빌드 도구를 제공하는 첫 번째 단계입니다 명령 플러그인은 문서 생성, 소스 코드 재포맷 등에 사용할 수 있습니다 셸 스크립트로 자동화를 작성하고 별도의 워크플로를 유지 관리하는 대신 Swift를 사용할 수 있습니다! 오픈 소스 형식자 및 린터를 생각해 보세요 이제 이런 모든 오픈 소스 도구는 Xcode와 Swift Package Manager에서 사용할 수 있습니다 명령 플러그인은 오픈 소스 도구와 Swift Package Manager를 연결하는 접착제입니다 Swift 프로젝트는 자동화된 워크플로와의 원활한 통합을 제공하기 위해 오픈 소스 커뮤니티의 개발자 도구를 수용하고 있습니다 docC는 문서를 소스 코드에 통합하는 훌륭한 도구입니다 올해 Objective-C 및 C 지원으로 더욱 향상되었습니다 이제 docC로 플러그인을 만드는 데 어떤 작업이 필요한지 살펴보겠습니다 플러그인은 그저 간단한 Swift 코드입니다 CommandPlugin 프로토콜을 준수하는 구조체를 생성하여 플러그인을 정의할 수 있습니다 그런 다음 호출할 도구를 플러그인에 알려주는 함수를 추가하면 됩니다 이 함수 내에서 docC를 호출하려고 합니다 플러그인을 정의하고 나면 Swift PM 명령줄 인터페이스와 Xcode를 통해 메뉴 항목으로 사용할 수 있습니다 이제 Swift PM에게 문서를 생성하도록 하면 알아서 이 작업을 docC 실행 파일로 전달합니다 여기서 끝나지 않습니다 빌드 도구 플러그인이라는 두 번째 플러그인이 있습니다 이런 플러그인은 빌드하는 동안 추가 단계를 주입할 수 있게 해주는 패키지입니다 빌드 도구 플러그인을 구현하면 빌드 시스템에 대한 명령이 샌드박스에서 실행되도록 생성됩니다 여러분이 언제든지 직접 실행하는 명령 플러그인과는 다르며 패키지의 파일을 변경할 수 있는 명시적 권한을 부여할 수 있습니다 빌드 도구 플러그인은 소스 코드 생성 또는 특수 유형의 파일에 대한 사용자 정의 처리에 사용할 수 있죠 빌드 도구 플러그인을 사용하면 이는 패키지 레이아웃이 됩니다 이 예제에서 plugin.Swift는 패키지 플러그인 대상을 구현하는 Swift 스크립트입니다 플러그인은 Swift 실행 파일로 처리됩니다 또 Swift 실행 파일을 작성하는 것과 같은 방식으로 플러그인을 작성합니다 빌드 시스템에서 실행할 실행 가능 명령과 결과로 예상되는 출력을 알려주는 빌드 명령 집합을 정의하여 플러그인을 구현할 수 있습니다 패키지 플러그인은 패키지의 확장성을 제공하는 보안 솔루션입니다 플러그인의 작동 방식과 자체 플러그인을 구현하는 방법은 다음 두 개의 세션 'Swift 패키지 플러그인 만나기'와 'Swift 패키지 플러그인 만들기’에서 확인하세요 패키지 사용을 확장할 때 모듈 충돌이 발생했을 수 있습니다 두 개의 개별 패키지가 동일한 이름의 모듈을 정의할 때 발생하는 문제입니다 이 문제를 해결하기 위해 Swift 5.7에서는 모듈 명확화를 도입합니다 모듈 명확화는 모듈을 정의하는 패키지 외부에서 모듈 이름을 변경할 수 있는 기능입니다 우리가 여기 이 Stunning 응용 프로그램에서 로깅 모듈을 정의하는 두 개의 패키지를 가져오자 이 둘은 충돌합니다 Stunning 응용 프로그램에서 이 문제를 해결하려면 패키지 매니페스트의 종속성 섹션에 moduleAliases 키워드를 추가하기만 하면 됩니다 이렇게 하면 두 개의 다른 이름을 사용하여 이전에 이름이 같았던 모듈을 구분할 수 있습니다 Swift 5.7에서는 일부 성능이 크게 향상되었습니다 우선 빌드 시간부터 살펴보죠 작년에 우리는 Swift의 소스 코드 컴파일을 조정하는 프로그램인 Swift Driver를 어떻게 재작성했는지 말씀드렸습니다 작년의 재구축은 빌드 속도를 크게 높이는 매우 중요한 변경 사항을 몇 가지 도입했습니다 이제 드라이버를 별도의 실행 파일 대신 Xcode 빌드 시스템 내에서 직접 프레임워크로 사용할 수 있습니다 이를 통해 더욱 밀접하게 빌드 시스템과 빌드를 조정하여 병렬화 등의 작업을 수행할 수 있습니다 빠른 빌드라는 말을 좋아하는 사람이라면 ’Xcode 빌드에서의 병렬화 설명’ 세션에서 자세한 내용을 확인할 수 있습니다 빌드 속도가 얼마나 빠른지 보여드리기 위해 Swift로 작성되는 자주 사용하는 일부 도구를 빌드하는 데 얼마나 걸리는지 몇 가지 예를 살펴보겠습니다 10-core iMac의 경우 속도가 5%에서 최대 25%까지 개선되었습니다 다음으로 유형 검사의 속도를 개선했습니다 올해는 제네릭 시스템의 핵심 부분인 프로토콜과 'where' 절에서 함수 서명을 계산하는 부분을 다시 구현하여 유형 검사 성능을 개선했습니다 이전 구현에서는 더 많은 프로토콜이 포함될수록 시간과 메모리 사용량이 기하급수적으로 증가했습니다 예를 들어 여기에 좌표계를 정의하는 복잡한 프로토콜 집합을 가지고 있으며 많은 관련 유형에 대한 제네릭 요구 사항이 있죠 이 코드를 유형 검사하는 데 이전에는 17초가 걸렸습니다 하지만 이제 Swift 5.7에서는 이 예제가 1초 이내에 유형 검사를 상당히 빠르게 수행할 수 있죠 또한 런타임 개선 사항도 몇 가지 있습니다 Swift 5.7 이전 버전에서는 iOS에서 앱 시작 시 프로토콜 검사 시간이 4초까지 걸리기도 했습니다 앱을 실행할 때마다 프로토콜을 계산해야 해서 프로토콜을 추가할수록 실행 시간이 길어졌습니다 이제는 캐시 처리됩니다 따라서 앱의 작성 방법과 사용된 프로토콜의 양에 따라 일부 앱은 iOS 16에서 실행할 때 실행 시간이 절반으로 단축될 수 있습니다 '앱 크기 및 런타임 성능 향상' 세션에서는 여러분의 응용 프로그램에서 이런 개선 사항을 활용할 방법에 대해 자세히 설명합니다 자, 이제 많은 분들이 간절히 듣고 싶어 하실 내용을 소개하겠습니다 작년에 우리는 행위자와 async/await를 한데 모은 새로운 동시성 모델을 선보였습니다 이는 응용 프로그램의 동시성 아키텍처에 혁신적인 영향을 미쳤죠 Async/await 및 행위자는 콜백 및 수동 대기열 관리보다 더 안전하고 쉽습니다 올해는 데이터 레이스 안전을 최우선으로 삼아 모델을 더욱 구체화했습니다 동시성이 앱의 코드베이스를 근본적으로 개선하는 중요한 요소였기 때문에 이런 변경 사항을 iOS 13 및 macOS Catalina로 역배포할 수 있도록 만들었습니다 이전 운영 체제에 배포할 수 있도록 앱은 이전 OS용 Swift 5.5 동시성 런타임 복사본을 번들로 제공합니다 이는 ABI 안정성 이전에 운영 체제에 Swift를 역배포하는 것과 유사합니다 다음으로 우리는 이 모델을 새로운 방향으로 가져갔죠 언어 기능과 지원 패키지를 도입했습니다 먼저 데이터 레이스 회피부터 살펴보겠습니다 이 주제로 넘어가기 전에 잠시 뒤로 물러서서 Swift의 정말 중요한 특징 하나는 기본값으로 메모리 안전이라고 짚고 넘어가겠습니다 Swift 사용자는 값을 수정하는 동안 값을 읽는 것과 같이 예측할 수 없는 동작으로 작업을 수행할 수 없습니다 이 예제에서는 우리가 동일한 배열의 카운트와 일치하는 배열의 모든 숫자를 제거하고 있습니다 처음에는 배열의 수가 3이므로 배열에서 3을 제거합니다 하지만 그렇게 하고 나면 카운트는 2가 됩니다 배열에서 3과 2를 제거할까요? 아니면 3만 제거할까요? 둘 다 틀렸습니다 여러분이 수정하는 동안 배열 카운트에 액세스하는 건 안전하지 않으므로 Swift에서 이 작업을 수행할 수 없습니다 우리 목표는 스레드 안전을 위해 비슷한 노력을 하는 겁니다 기본값으로 낮은 수준의 데이터 레이스를 제거하는 언어를 구상합니다 즉, 예측 불가능한 동작을 유발할 수 있는 동시성 버그를 방지하고자 합니다 여기 또 다른 예제가 있습니다 같은 숫자의 배열을 사용하여 배열에 0을 추가하는 배경 작업을 만든 다음 배열의 마지막 요소를 제거합니다 잠깐만요 마지막 요소를 제거하는 게 0을 더하기 전인가요? 아니면 그 후인가요? 또 둘 다 틀렸습니다 Swift는 이 작업을 수행하지 못하게 차단합니다 액세스 권한을 행위자와 동기화하지 않고 배경 작업에서 배열을 수정하는 건 안전하지 않으니까요 행위자는 데이터 레이스를 없애기 위한 첫 번째 주요 단계였죠 올해 우리는 목표를 더 달성하기 위해 동시성 모델을 개선했습니다 여러분은 각 행위자가 동시성의 바다에 있는 다른 모든 것으로부터 격리된 독립적인 섬으로 생각할 수 있죠 하지만 서로 다른 스레드가 격리된 각 행위자가 저장한 정보를 쿼리하고자 할 때는 무슨 일이 일어날까요? 'Swift Concurrency를 사용하여 데이터 레이스 제거' 세션에서 이 비유를 자세히 살펴보겠습니다 기본값으로 메모리 안전부터 스레드 안전까지 보장하는 게 Swift 6의 목표입니다 이런= 목표를 달성하기 위해 우리는 먼저 작년의 동시성 모델을 방금 언급한 새로운 언어 기능으로 개선했습니다 아직 말씀드리지 않은 두 번째 개선 사항은 잠재적인 데이터 레이스를 식별하는 새로운 옵트인 안전 검사입니다 빌드 설정에서 이 검사를 활성화하여 보다 엄격하게 동시성 검사를 실험할 수 있습니다 행위자를 다시 살펴보겠습니다 우리는 이런 행위자 격리의 개념을 받아들여 분산된 행위자들로 더 나아갈 수 있습니다 분산된 행위자는 네트워크를 사이에 두고 섬들을 서로 다른 기계들에 놓습니다 이 새로운 언어 기능은 분산 시스템 개발을 훨씬 더 단순하게 만듭니다 여러분이 게임 앱을 만들고 싶다고 가정해 보죠 이제 Swift에서 백엔드를 쉽게 작성할 수 있습니다 여기서 분산된 행위자는 행위자와 비슷하지만 다른 기계에 있을 수 있습니다 이 예제에서는 사용자와의 게임 중에 상태를 유지하는 컴퓨터 플레이어를 살펴봅니다 분산 키워드는 원격 컴퓨터에 있을 수도 있는 행위자에 대해 호출해야 한다고 예상되는 함수에 추가할 수도 있습니다 endOfRound라는 다른 함수를 추가해 보면 플레이어를 루프하여 각 플레이어에 makeMove를 호출하죠 일부 플레이어는 로컬 또는 원격일 수 있지만 누가 어느 쪽인지 신경 쓸 필요가 없다는 장점을 누릴 수 있습니다 일반 행위자 호출과의 유일한 차이점은 네트워크 오류로 인해 분산 행위자 호출이 잠재적으로 실패할 수 있다는 점입니다 네트워크에 장애가 발생할 경우 행위자 메서드가 오류를 발생시킵니다 그래서 행위자 외부에서 함수를 호출할 때 필요한 일반적인 wait 키워드와 함께 try 키워드도 추가해야 합니다 또한 이런 핵심 언어 기본 요소를 기반으로 Swift에서 서버 측 클러스터형 분산 시스템을 구축하는 데 중점을 둔 오픈 소스 분산 행위자 패키지도 구축했습니다 패키지에는 Swift-IO를 사용하는 통합 네트워킹 계층이 포함되어 있고 SWIM 합의 프로토콜을 구현하여 클러스터 전체의 상태를 관리합니다 ’Swift 분산 행위자 만나기’ 세션에서 이 새로운 기능을 통해 분산 시스템을 구축하는 방법에 대해 자세히 설명합니다 또한 Swift 5.5와 함께 출시된 AsyncSequence를 처리할 때 일반적인 작업에 간편하며 즉시 사용할 수 있는 솔루션을 제공하기 위해 새로운 오픈 소스 알고리즘 집합을 출시했죠 이런 API를 패키지로 릴리스하면 개발자는 플랫폼 및 운영 체제 버전 전반에 걸쳐 유연하게 배포할 수 있죠 여러 비동기 시퀀스를 결합하고 값을 컬렉션으로 그룹화하는 방법은 여러 가지가 있습니다 이들은 패키지에 포함된 알고리즘의 일부에 불과합니다 이 새로운 강력한 API를 사용하는 방법을 알아보려면 'Swift 비동기 알고리즘’ 강연을 확인하세요 하지만 동시성의 또 다른 측면이 있으니 바로 성능입니다 올해는 행위자 우선 순위제를 통해 이제 행위자가 우선순위가 가장 높은 작업을 먼저 수행하죠 또한 운영 체제 스케줄러와의 긴밀한 통합을 지속하여 이 모델에는 우선 순위 역전 방지 기능이 기본으로 제공되므로 우선 순위가 높은 작업을 덜 중요한 작업이 방해할 수 없습니다 과거에는 앱에서 동시성이 성능에 미치는 영향을 시각화하기가 매우 어려웠지만 이제는 그걸 정확히 해낼 수 있는 훌륭한 새로운 도구를 갖게 됐죠 Instruments의 새로운 Swift Concurrency 뷰를 통해 성능 문제를 조사할 수 있습니다 Swift Tasks 및 Swift Actors Instruments는 동시성 코드를 시각화하고 최적화하도록 지원하는 전체 도구 제품군을 제공합니다 최상위 수준에서는 Swift Tasks Instrument가 동시에 실행되는 작업의 수와 해당 시점까지 생성된 총 작업을 포함하여 유용한 통계를 제공합니다 이 창의 아래쪽 절반에서 Task Forest라는 항목을 볼 수 있습니다 구조화된 동시성 코드에서 작업 간의 상하 관계를 그래픽으로 표현하여 제공합니다 이는 Swift Actor Instrument에 대한 상세 뷰 중 하나입니다 이 흥미로운 새 도구를 사용하는 방법을 알아보려면 ’Swift Concurrency 시각화 및 최적화’ 강연으로 넘어가 보시기 바랍니다 이 새로운 패키지를 잊지 말고 시도해 보세요 부끄러워하지 마시고 진행 상황을 포럼을 통해 우리에게 알려 주세요 이제 Becca에게 넘겨서 Swift 언어의 유용성에 대한 많은 개선점에 대해 이야기해 보도록 하죠 언어는 도구입니다 도구에는 흥미로운 점이 하나 있는데 바로 여러분이 도구로 구축하는 것에 영향을 미칠 수 있다는 사실입니다 여러분에게 망치만 있다면 나사 대신 못으로 무언가를 만들겠죠 또 설령 여러분이 모든 도구를 가지고 있어도 망치의 손잡이는 크고 쥐기 쉬운 반면 스크루드라이버는 플라스틱 같고 잡기 힘들다면 여러분은 여전히 못으로 마음이 기울지도 모릅니다 언어도 마찬가지입니다 Swift에 무언가를 표현하기 위한 좋은 도구가 있다면 사람들은 그 도구를 더 자주 사용하겠죠 올해는 여러분이 코드에 원하는 바를 표현하는 데 쓰이는 Swift의 도구들이 많은 면에서 개선됐습니다 이런 변경 사항 중 일부는 여러분이 자주 하는 작업을 더 편리하게 해줍니다 예를 들어 Swift에서는 등호 양쪽에 동일한 이름을 가진 If let을 사용하는 것이 매우 일반적입니다 결국 언래핑 값에 여러분이 옵션 이름을 지정한 이름보다 더 좋은 이름은 아마도 없을 것입니다 하지만 이름이 너무 길면 그 반복은 번거로워지기 시작합니다 이름을 줄여서 부르고 싶을 수도 있지만 그러면 여러분의 코드는 다소 암호처럼 변하겠죠 나중에 선택적 변수의 이름을 변경하면 약어의 동기화가 안 될 수도 있습니다 Swift 5.7은 이 공통 패턴에 대한 새로운 약칭을 도입했습니다 옵션을 언래핑하는 경우 언래핑 값이 동일한 이름을 가지도록 하려면 오른쪽만 빼 버리면 됩니다 Swift는 똑같다고 여길 겁니다 물론 이는 가드에도 똑같이 작용하며 심지어 그 과정에서도 마찬가지입니다 우리는 사소한 변경 작업을 수행할 때 기능이 갑자기 중지되는 위치도 살펴보았습니다 예를 들어, Swift는 항상 단일 구문 클로저 안에 작성된 코드를 기반으로 호출이 어떤 유형을 반환하는지 파악할 수 있었습니다 이 compactMap 호출에서 클로저는 parseLine의 값을 반환하고 parseLine 함수는 MailmapEntry를 반환하므로 Swift는 항목이 MailmapEntry의 배열이어야 함을 알 수 있습니다 이제 여러 구문 또는 제어 흐름 기능이 있는 더 복잡한 클로저에도 이것이 적용됩니다 따라서 클로저의 결과 유형을 수동으로 지정하지 않고도 do-catch 또는 if...else를 사용하거나 인쇄 호출을 추가할 수 있습니다 우리는 실제로 어떤 위험도 알리지 않는 위험 플래그도 살펴봤습니다 Swift는 유형과 메모리 안전성에 매우 관심이 많습니다 여러분의 실수를 방지하기 위해 Swift는 서로 다른 포인터 유형 간 또는 원시 포인터 및 입력된 포인터 간에 포인터를 자동으로 변환하지 않습니다 이는 특정 변환을 허용하는 C와 매우 다릅니다 예를 들어, C의 포인터 규칙을 위반하지 않고 포인티의 서명 상태를 변경하거나 char star에 포인터를 캐스팅하여 바이트로 액세스할 수 있습니다 그러나 이런 포인터 동작의 차이는 C API를 Swift로 가져올 때 때때로 문제를 일으킵니다 원래 개발자는 C의 자동 변환에 의해 처리되지만 Swift에서는 오류인 약간의 불일치가 포함된 API를 설계했을 수 있습니다 Swift에서 한 유형의 포인터가 다른 유형인 것처럼 액세스하는 것은 매우 위험하므로 여러분은 수행하는 작업을 매우 명시적으로 설명해야 합니다 하지만 만약 우리가 C에 직접 포인터를 넘겨준다면 이는 모두 무의미합니다 왜냐하면 C에서는 포인터 불일치가 완전히 합법이니까요! 그래서 이 경우 우리는 아주 단순한 무언가를 위험한 것처럼 다뤄 왔죠 이건 문제가 됩니다 Swift는 유형 안전도 중시하지만 쉬운 C 코드 액세스도 중시하니까요 그게 C와 Objective-C 상호 운용성이 매우 풍부하고 원활한 이유이며 Swift 프로젝트가 앞서 Angela가 언급한 C 작업 그룹을 구성하여 동등한 능력을 갖춘 C 상호 운용성을 구축하는 이유죠 우리는 이런 C 함수를 사용할 때 불필요하게 힘들기를 원하지 않습니다 이제 Swift는 가져온 함수 및 메서드에 대한 호출을 위한 별도의 규칙 집합을 가지고 있죠 일반적으로 Swift에서는 합법이 아니지만 C에서는 합법인 포인터 변환을 허용합니다 이렇게 하면 Swift 코드가 API를 원활하게 사용할 수 있죠 지금까지 여러분이 보유한 기존 도구에 적용된 작은 개선 사항에 대해 설명했습니다 하지만 올해 Swift는 문자열에서 정보를 추출하는 새로운 도구도 개발했습니다 여기 문자열에서 일부 정보를 구문 분석하는 함수가 있습니다 Swift에서 이런 종류의 작업은 항상 일종의 과제였습니다 원하는 것을 얻을 때까지 계속 찾고, 쪼개고, 자르고 반복해야 하죠 사람들은 이 사실을 알고 나면 문자열 인덱스를 조작하는 게 얼마나 장황한지와 같은 사소한 문제에 집중하는 경향이 있지만 저는 그러면 큰 그림을 놓친다고 생각합니다 왜냐하면 우리가 이 구문을 바꾸더라도 여러분이 이 코드를 볼 때 던지는 기본적인 질문에 대답하는데 도움이 되지 않기 때문입니다 ‘이 코드로 전달된 줄 변수는’ ‘실제로 어떻게 생겼을까요?’ ‘어떤 종류의 문자열을 분해하려고 하나요?’ 문자열을 충분히 오래 본다면 이게 단순화된 메일 버전을 구문 분석하고 있다는 걸 알겠죠 즉, 오래된 커밋에서 개발자의 이름을 수정하기 위해 git 저장소에 넣는 파일입니다 하지만 검색과 자르기를 통해 그 정보를 추출하는 것은 너무 복잡해서 알아내기가 어렵습니다 문자열을 자르는 방법에 너무 몰두하느라 그 문자열이 정확히 뭔지도 모르게 됩니다 문제는 이 두 표현이 아닙니다 문제는 바로 이 전체입니다 이 모든 것을 뜯어내고 더 나은 것으로 대체해야 합니다 다른 접근법이 필요합니다 코드가 일치시키고자 하는 문자열의 그림을 그리는 접근법이 필요하며 언어가 그 접근법을 알아내야 합니다 명령적인 접근법이 아니라 선언적인 접근법이 필요하죠 Swift 5.7에서 정규식을 작성하여 이를 수행할 수 있습니다 정규식은 문자열에서 패턴을 설명하는 방법이죠 50년 이상 동안 언어와 도구는 개발자들이 촘촘하고 정보가 가득한 구문을 통해 정규식을 쓸 수 있도록 허용해 왔습니다 Xcode 찾기 표시줄이나 grep 같은 명령줄 도구 Foundation의 NSRegular Expression 클래스 또는 다른 프로그래밍 언어에서 이미 사용하고 있는 사용자도 있습니다 이 구문은 이제 Swift의 정규식 리터럴에서 지원되며 다른 개발자 도구에서와 똑같이 작동합니다 하지만 여러분 중 일부는 정규식을 사용해 본 적이 없고 아마 이렇게 말씀하시겠죠 ‘그거 진짜 코드인가요?’ ‘고양이가 키보드 위를 지나간 흔적 아니고요?’ 여러분을 탓하진 않습니다 정규어 리터럴은 외워야만 읽을 수 있는 기호와 니모닉으로 쓰이니까요 언어를 아는 사람에게는 개발자의 이름과 일치하는 부분과 같은 이 정규식의 가장 괴상한 부분조차도 몇 가지 간단한 일치 규칙의 조합일 뿐입니다 하지만 11자로 짜넣기에는 동작이 너무 많습니다 정규식 리터럴은 너무 간결해서 경험이 많은 개발자라도 복잡한 경우는 이해하는 데 1분이 필요할 때가 있습니다 하지만 같은 종류의 일치 규칙을 기호 대신 단어로 작성할 수 있다면 어떨까요? 그게 이해하기 더 쉬울 것 같은데요 사실 이걸 모두 합치면 SwiftUI와 매우 비슷한 결과물이 나옵니다 정규식 리터럴의 좋은 대안이 될 수 있을 것 같지 않나요? 그러니 Swift가 이를 지원하는 건 멋진 일이죠! RegexBuilder 라이브러리는 기존 구문보다 더 사용하기 쉽고 읽기 쉬우며 완전히 새로운 정규식용 SwiftUI 스타일 언어를 제공합니다 이 언어는 정규식 리터럴과 똑같은 일을 해내지만 여러분이 외워야 하는 기호와 약어 대신 이해할 수 있거나 검색할 수 있는 단어로 동작을 묘사합니다 정규식 빌더는 초보자들에게도 좋지만 초보자만을 위한 기능과는 거리가 매우 멉니다 정규식 리터럴이 할 수 있는 것보다 훨씬 더 강력한 기능을 보유하고 있습니다 우선 정규식을 재사용 가능한 정규식 구성 요소로 바꿀 수 있습니다 SwiftUI 뷰 계층을 뷰로 바꿀 수 있는 것과 마찬가지로요 빌더 구문으로 작성된 다른 정규식에서 이러한 구성 요소를 사용할 수 있으며 그들을 되풀이시킬 수도 있습니다 정규식 빌더는 일부 Swift 유형을 정규식에 직접 드롭하는 것도 지원합니다 예를 들어, 문자열 리터럴은 안에 있는 텍스트와 정확히 일치하므로 특별한 회피가 필요하지 않습니다 정규식 빌더 중간에 정규식 리터럴을 사용할 수도 있습니다 따라서 정규식 빌더의 명확성과 정규식 리터럴의 간결성 사이에서 균형을 맞출 수 있습니다 또한 이 Foundation 날짜 형식 스타일 같은 다른 유형도 사용자 정의 구문 분석 로직을 정규식 빌더와 통합할 수 있고 데이터를 캡처하기 전에 더 풍부한 유형으로 변환할 수 있죠 마지막으로 어떤 구문을 사용하든 정규식은 사용하기 쉬운 여러 가지 유용한 일치 메서드와 강력한 형식의 캡처를 지원합니다 이제 자리에서 안달 내는 정규식 괴짜들을 위해 Swift 정규식은 최신 정규식 구현에 버금가는 기능 집합을 갖춘 완전히 새로운 오픈 소스 매칭 엔진을 사용합니다 리터럴 구문은 유니코드 정규식 표준과 호환되며 유니코드 정확도 수준도 차원이 다릅니다 예를 들어 점은 기본값으로 Unicode.Scalar 또는 UTF-8 byte가 아닌 전체 문자와 일치합니다 Swift 정규식을 사용하려면 MacOS 13이나 iOS 16처럼 Swift 정규식 엔진이 내장된 OS에서 앱을 실행해야 합니다 Swift 정규식은 전체 언어입니다 사실 두 개의 언어죠 따라서 할 말이 더 많습니다 다음 두 세션 ‘Swift 정규식 만나기’와 ’Swift 정규식: 기본을 넘어’에서 Swift 정규식 사용과 관련한 자세한 정보를 제공합니다 마지막으로 우리가 기존에 보유한 도구를 종합적으로 살펴본 다음 여러 변경 사항을 적용하여 개선한 부분이 있습니다 바로 제네릭과 프로토콜입니다 이 도구들이 어떻게 개선됐는지 보여드리려면 예제 프로토콜이 필요합니다 여러분이 Git 클라이언트를 작성하고 있으며 메일맵을 두 가지 다른 방식으로 표현해야 한다고 가정해 봅시다 커밋을 표시할 때 사전과 함께 유형을 사용하여 이름을 빠르게 검색합니다 그러나 사용자가 메일맵을 편집하도록 허용할 때는 배열이 있는 유형을 사용하여 항목을 원래 순서로 유지합니다 여러분에게는 두 가지 모두 준수하는 메일맵이라는 프로토콜이 있으므로 메일맵 파서는 두 유형 중 하나에 항목을 추가할 수 있습니다 그러나 파서가 메일맵 프로토콜을 사용할 수 있는 방법은 두 가지가 있습니다 이 방법들을 설명하려고 이 addEntries 함수의 두 가지 버전을 작성해 봤지만 사실은 두 방법이 어떻게 다른지 설명하기 어렵습니다 Swift가 두 가지 다른 방법에 같은 구문을 사용하고 있거든요 '메일맵'이라는 단어는 여기서 한 가지를 의미하지만 여기서는 미묘하게 다른 것을 의미합니다
상속 목록, 제네릭 매개 변수 목록 제네릭 준수 제약 조건 또는 불투명 결과 유형에서 프로토콜의 이름을 지정할 경우 이는 '이 프로토콜을 준수하는 인스턴스'를 의미합니다 그러나 변수 유형, 제네릭 인수 제네릭 동일 유형 제약 조건 또는 함수 매개 변수 또는 결과 유형에서는 실제로 ‘이 프로토콜을 준수하는 인스턴스가’ ’들어 있는 상자’를 의미합니다 이 구분은 일반적으로 상자가 더 많은 공간을 사용하고 작업하는 데 더 많은 시간이 걸리며 내부에 인스턴스의 모든 기능이 포함되어 있지 않기 때문에 중요합니다 하지만 상자를 사용하는 곳과 사용하지 않는 곳은 똑같아 보여서 이걸 사용하고 있는지 파악하기가 어렵습니다 Swift 5.7은 이런 실수를 해결합니다 여러분이 준수하는 유형이 들어 있는 이러한 상자 중 하나를 사용할 때는 Swift가 여러분이 이제 아무 키워드나 쓰기를 기대하죠 이것은 Swift 5.7 이전에 유효했던 코드에서 필수는 아니지만 권장되며 명시적으로 작성하지 않더라도 생성된 인터페이스와 오류 메시지에서 확인할 수 있습니다 따라서 오른쪽 열에 이런 모든 내용을 작성하는 가장 선호하는 방법은 ‘모든’ 키워드의 사용입니다 이렇게 하면 이 상자 중 하나를 언제 사용하는지 알 수 있습니다 이제 키워드를 사용하면 이 예제의 매개 변수 중 하나가 표시되므로 이 두 함수 간의 차이를 훨씬 쉽게 설명할 수 있습니다 addEntries1은 메일맵을 제네릭 유형으로 사용하고 addEntries2는 모든 유형으로 사용합니다 또한 오류 메시지가 모든 유형의 제한 중 하나에 도달했을 때 발생하는 현상을 설명하기도 더 쉽습니다 예를 들어 이 mergeMailmap 함수는 모든 메일맵을 제네릭 메일맵 매개 변수에 전달하려고 하죠 이러면 메일맵이 자기 자신을 따를 수 없다는 오류를 만들어내고는 했는데 항상 역설적으로 보였습니다 하지만 이제 모든 유형이라는 개념이 생겼으니 무슨 일이 일어나고 있는지 더 명확하게 설명할 수 있죠 문제는 메일맵이 들어 있는 상자인 메일맵이 메일맵 프로토콜에 따르지 않는다는 겁니다 그러나 여러분은 이 상자를 전달하려고 하고 이 상자는 제네릭 매개 변수에 맞지 않습니다 여기 상자 안에 있는 인스턴스를 전달하려면 여러분은 어떻게든 상자를 열고 안에 있는 메일맵을 꺼내 대신 전달해야 합니다 하지만 사실 이렇게 간단한 경우에는 Swift가 여러분 대신 이를 수행할 겁니다 상자를 열고 안에서 인스턴스를 꺼내 제네릭 매개 변수로 전달하죠 그래서 이 오류 메시지는 더 이상 자주 볼 일이 없을 겁니다 하지만 이 외에도 모든 유형의 개선 사항 중 흥미로운 개선 사항이 있죠 이전에는 자체 유형을 사용하거나 관련 유형을 가지고 있거나 심지어 등가 가능과 같은 프로토콜과 일치하는 경우 프로토콜을 모든 유형으로 사용할 수 없었습니다 하지만 이제 Swift 5.7에서는 이 오류가... 펑! 사라졌죠 많은 개발자가 이 문제로 고생을 많이 했기 때문에 이 문제를 완전히 뿌리 뽑게 되어 기쁩니다 메일맵 같은 프로토콜만으로도 충분히 흥미진진하지만 이 변화는 더욱 대단하죠 컬렉션처럼 매우 정교한 프로토콜도 모든 유형으로 사용할 수 있기 때문입니다 '기본 관련 유형'이라는 새로운 기능을 통해 요소 유형을 직접 지정할 수도 있습니다 많은 관련 유형은 사실상 구현 세부 사항에 불과합니다 일반적으로 컬렉션이 인덱스, 반복자 또는 시퀀스에 어떤 유형을 사용하든 상관 없습니다 그저 지원되는 유형을 사용하면 됩니다 하지만 컬렉션의 요소는 이야기가 다르죠 컬렉션에서 어떤 요소 유형을 사용하는지는 항상 신경 쓰지 않을 수 있지만 아마도 요소를 사용하여 작업을 수행하게 될 테니 요소를 제한하거나 반환해야 할 수 있습니다 거의 모든 프로토콜 사용자가 관심을 가질 요소 같은 관련 유형이 있는 경우 해당 유형 이름을 프로토콜 이름 뒤에 꺾쇠괄호 안에 넣으면 기본 관련 유형으로 만들 수 있습니다 이렇게 하면 모든 컬렉션을 포함하여 프로토콜의 이름을 쓸 수 있는 거의 모든 위치에서 꺾쇠괄호 구문을 사용하여 프로토콜의 기본 관련 유형을 제한할 수 있습니다 어떤 분들은 이 유형을 보며 이렇게 말하겠죠 ‘잠시만요’ ‘anyCollection이라는 게 이미 있지 않나요?’ ‘함께 실행되고 ‘모든’이 대문자인 거요’ 맞습니다, 이미 있어요! 이전 anyCollection은 모든 유형과 동일한 목적을 위해 손으로 작성한 구조체인 유형 소거 래퍼입니다 차이점이라면 anyCollection 구조체는 여러분이 살면서 본 가장 지루한 보일러플레이트 코드가 한 줄 한 줄 이어지는 반면 이 모든 유형은 기본적으로 동일한 일을 하는 기본 제공 언어 기능입니다 게다가 무료죠! anyCollection 구조체도 계속 사용될 겁니다 이전 버전과의 호환성을 유지해야 하고 모든 유형이 아직 따라잡지 못한 몇 가지 기능을 가지고 있거든요 그러나 코드에 자체 유형 삭제 래퍼가 있는 경우 상자 클래스나 클로저 대신 기본 제공 유형을 사용하여 다시 구현할 수 있는지 확인할 수 있습니다 아니면 아예 유형 별명으로 대체할 수도 있습니다 따라서 Swift는 모든 유형을 획기적으로 개선했습니다 모든 키워드가 도입되어 이제 그들이 사용 중인 위치를 확인할 수 있죠 따라서 제네릭 인수로 전달할 수도 있습니다 많은 프로토콜이 그들과 함께 사용되지 못하던 제한도 사라졌습니다 게다가 모든 유형의 기본 관련 유형을 제한할 수도 있습니다 그러나 이런 개선 사항에도 불구하고 모든 유형에는 여전히 한계가 있습니다 예를 들어, 메일맵이 등가 가능을 따를 때 이제 모든 메일맵을 쓸 수 있다고 해도 등가 연산자는 여전히 같이 사용할 수 없습니다 등가 연산자는 두 개의 메일맵에 동일한 구체적인 유형이 필요하지만 두 개의 메일맵을 사용하는 경우에는 이 조건이 보장되지 않기 때문입니다 따라서 Swift는 모든 유형을 많이 개선했지만 여전히 중요한 한계가 남아 있어서 기능과 성능에 모두 영향을 미치죠 그래서 여러분은 많은 경우 모든 유형보다는 제네릭을 대신 사용해야 합니다 두 가지 버전의 addEntries로 돌아가 이 지혜를 적용해 보죠 두 버전 모두 정확히 같은 기능을 하지만 위에 있는 버전은 제네릭 유형을 사용하고 아래 있는 버전은 모든 유형을 사용합니다 제네릭 버전이 더 효율이 높고 성능이 좋아 보이니 이걸 사용해야 합니다 물론 여러분은 모든 유형을 사용하고 싶을 겁니다 읽고 쓰기가 훨씬 더 쉬우니까요 제네릭 버전을 작성하려면 두 개의 제네릭 유형 이름을 선언하고 둘 다 제한한 다음 마지막으로 이런 제네릭 유형 이름을 매개 변수 유형으로 사용해야 합니다 '모든 컬렉션'과 '모든 메일맵'을 작성하는 것과 비교하면 매우 피곤한 작업이죠 따라서 여러 단점에도 불구하고 모든 유형을 사용하고 싶을 겁니다 아까 제가 했던 비유와 비슷합니다 망치 손잡이가 크고 쥐기도 편해서 스크루드라이버 대신 망치를 쓴다는 비유 말이죠 여러분은 그런 선택을 할 필요가 없어야겠죠 따라서 Swift는 제네릭을 모든 유형처럼 사용하기 쉽게 만들고 있습니다 제네릭 매개 변수가 한 곳에서만 사용되는 경우 이제 일부 키워드를 약칭으로 사용하여 작성할 수 있습니다 심지어 기본 관련 유형도 지원하므로 훨씬 더 이해하기 쉬운 코드를 통해 모든 메일맵 항목 컬렉션을 수락할 수 있습니다 이런 기능이 제공되니까 제네릭을 피할 이유가 이제 전혀 없겠죠 제네릭과 모든 유형 중 하나를 선택해야 한다면 제네릭도 마찬가지로 쉽게 사용할 수 있습니다 그냥 '모든' 대신 '일부'라고 쓰면 됩니다 그러니 이제 작업에 가장 좋은 도구를 사용하는 것이 낫습니다 오늘은 프로토콜과 제네릭의 변경 사항을 간략하게 살펴봤으니 Swift의 제네릭 기능을 더 심층적으로 살펴보고 자세히 검토하고 싶다면 이를 다루는 강연이 올해 두 개 더 있습니다 ’Swift 제네릭 수용’과 ’Swift 프로토콜 인터페이스 설계’입니다
Angela와 저는 Swift에 적용된 변경 사항을 거의 24가지나 다뤘지만 이 세션에서 다루지 못한 내용이 아직 정말 많습니다 이런 모든 변경 사항은 Swift 포럼의 Evolution 게시판에서 공개되고 제안되었으며 검토를 거쳐 공개적으로 수용됐습니다 이 모든 변경 사항은 Apple 외부 커뮤니티 멤버들의 도움으로 마련되고 실현되었습니다 여러분이 그중 하나라면 Swift 5.7 릴리스를 이렇게 멋지게 만들어 주셔서 감사합니다 다음에 무엇이 나올지 결정하는 일을 돕고 싶다면 Swift.org/contributing을 방문하여 참여 방법을 확인하세요 시간을 내주셔서 감사합니다 즐거운 코딩 되세요!
♪
-
-
7:19 - Command plugins
@main struct MyPlugin: CommandPlugin { func performCommand(context: PluginContext, arguments: [String]) throws { let process = try Process.run(doccExec, arguments: doccArgs) process.waitUntilExit() } }
-
8:34 - Build tool plugins
import PackagePlugin @main struct MyCoolPlugin: BuildToolPlugin { func createBuildCommands(context: TargetBuildContext) throws -> [Command] { // Run some command } }
-
8:39 - Implementing a build tool plugin
import PackagePlugin @main struct MyCoolPlugin: BuildToolPlugin { func createBuildCommands(context: TargetBuildContext) throws -> [Command] { let generatedSources = context.pluginWorkDirectory.appending("GeneratedSources") return [ .buildCommand( displayName: "Running MyTool", executable: try context.tool(named: "mycooltool").path, arguments: ["create"], outputFilesDirectory: generatedSources) ] } }
-
9:23 - Module disambiguation with module aliases
let package = Package( name: "MyStunningApp", dependencies: [ .package(url: "https://.../swift-metrics.git"), .package(url: "https://.../swift-log.git") ], products: [ .executable(name: "MyStunningApp", targets: ["MyStunningApp"]) ], targets: [ .executableTarget( name: "MyStunningApp", dependencies: [ .product(name: "Logging", package: "swift-log"), .product(name: "Metrics", package: "swift-metrics", moduleAliases: ["Logging": "MetricsLogging"]), ])])
-
9:42 - Distinguishing between modules with the same name
// MyStunningApp import Logging // from swift-log import MetricsLogging // from swift-metrics let swiftLogger = Logging.Logger() let metricsLogger = MetricsLogging.Logger()
-
11:09 - Example set of protocols
public protocol NonEmptyProtocol: Collection where Element == C.Element, Index == C.Index { associatedtype C: Collection } public protocol MultiPoint { associatedtype C: CoordinateSystem typealias P = Self.C.P associatedtype X: NonEmptyProtocol where X.C: NonEmptyProtocol, X.Element == Self.P } public protocol CoordinateSystem { associatedtype P: Point where Self.P.C == Self associatedtype S: Size where Self.S.C == Self associatedtype L: Line where Self.L.C == Self associatedtype B: BoundingBox where Self.B.C == Self } public protocol Line: MultiPoint {} public protocol Size { associatedtype C: CoordinateSystem where Self.C.S == Self } public protocol BoundingBox { associatedtype C: CoordinateSystem typealias P = Self.C.P typealias S = Self.C.S } public protocol Point { associatedtype C: CoordinateSystem where Self.C.P == Self }
-
13:14 - Memory safety in Swift
var numbers = [3, 2, 1] numbers.removeAll(where: { number in number == numbers.count })
-
14:10 - Thread safety in Swift
var numbers = [3, 2, 1] Task { numbers.append(0) } numbers.removeLast()
-
15:54 - A distributed actor player and a distributed function
distributed actor Player { var ai: PlayerBotAI? var gameState: GameState distributed func makeMove() -> GameMove { return ai.decideNextMove(given: &gameState) } }
-
16:20 - A distributed actor call
func endOfRound(players: [Player]) async throws { // Have each of the players make their move for player in players { let move = try await player.makeMove() } }
-
20:12 - Optional unwrapping
if let mailmapURL = mailmapURL { mailmapLines = try String(contentsOf: mailmapURL).split(separator: "\n") }
-
20:29 - Optional unwrapping with long variable names
if let workingDirectoryMailmapURL = workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n") }
-
20:35 - Cryptic abbreviated variable names
if let wdmu = workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: wdmu).split(separator: "\n") }
-
20:46 - Unwrapping optionals in Swift 5.7
if let workingDirectoryMailmapURL { mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n") } guard let workingDirectoryMailmapURL else { return } mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")
-
21:07 - Closure type inference
let entries = mailmapLines.compactMap { line in try? parseLine(line) } func parseLine(_ line: Substring) throws -> MailmapEntry { … }
-
21:33 - Type inference for complicated closures
let entries = mailmapLines.compactMap { line in do { return try parseLine(line) } catch { logger.warn("Mailmap error: \(error)") return nil } } func parseLine(_ line: Substring) throws -> MailmapEntry { … }
-
22:15 - Mismatches that are harmless in C...
// Mismatches that are harmless in C… int mailmap_get_size(mailmap_t *map); void mailmap_truncate(mailmap_t *map, unsigned *sizeInOut); void remove_duplicates(mailmap_t *map) { int size = mailmap_get_size(map); size -= move_duplicates_to_end(map); mailmap_truncate(map, &size); } // …cause problems in Swift. func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) { var size = mailmap_get_size(map) size -= moveDuplicatesToEnd(map) mailmap_truncate(map, &size) }
-
22:33 - Better interoperability with C-family code
func removeDuplicates(from map: UnsafeMutablePointer<mailmap_t>) { var size = mailmap_get_size(map) size -= moveDuplicatesToEnd(map) withUnsafeMutablePointer(to: &size) { signedSizePtr in signedSizePtr.withMemoryRebound(to: UInt32.self, capacity: 1) { unsignedSizePtr in mailmap_truncate(map, unsignedSizePtr) } } }
-
23:41 - String parsing is hard
func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:05 - String parsing is still hard with better indexing
func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[(emailEnd 1)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[(nameEnd 1)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:20 - What's the problem?
let line = "Becca Royal-Gordon <[email protected]> # Comment" func parseLine(_ line: Substring) throws -> MailmapEntry { func trim(_ str: Substring) -> Substring { String(str).trimmingCharacters(in: .whitespacesAndNewlines)[...] } let activeLine = trim(line[..<(line.firstIndex(of: "#") ?? line.endIndex)]) guard let nameEnd = activeLine.firstIndex(of: "<"), let emailEnd = activeLine[nameEnd...].firstIndex(of: ">"), trim(activeLine[activeLine.index(after: emailEnd)...]).isEmpty else { throw MailmapError.badLine } let name = nameEnd == activeLine.startIndex ? nil : trim(activeLine[..<nameEnd]) let email = activeLine[activeLine.index(after: nameEnd)..<emailEnd] return MailmapEntry(name: name, email: email) }
-
24:55 - Drawing a picture
"Becca Royal-Gordon <[email protected]> # Comment" / space name space < email > space # or EOL / / \h* ( [^<#] ? )?? \h* < ( [^>#] ) > \h* (?: #|\Z) /
-
25:10 - Swift Regex using a literal
func parseLine(_ line: Substring) throws -> MailmapEntry { let regex = /\h*([^<#] ?)??\h*<([^>#] )>\h*(?:#|\Z)/ guard let match = line.prefixMatch(of: regex) else { throw MailmapError.badLine } return MailmapEntry(name: match.1, email: match.2) }
-
25:46 - Did a cat walk across your keyboard?
/\h*([^<#] ?)??\h*<([^>#] )>\h*(?:#|\Z)/
-
26:34 - Regex builder
import RegexBuilder let regex = Regex { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) ChoiceOf { "#" Anchor.endOfSubjectBeforeNewline } }
-
27:05 - Turn a regex into a reusable component
struct MailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) ChoiceOf { "#" Anchor.endOfSubjectBeforeNewline } } }
-
27:30 - Use regex literals within a builder
struct MailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) /#|\Z/ } }
-
27:39 - Use Date parsers within Regex builders
struct DatedMailmapLine: RegexComponent { @RegexComponentBuilder var regex: Regex<(Substring, Substring?, Substring, Date)> { ZeroOrMore(.horizontalWhitespace) Optionally { Capture(OneOrMore(.noneOf("<#"))) } .repetitionBehavior(.reluctant) ZeroOrMore(.horizontalWhitespace) "<" Capture(OneOrMore(.noneOf(">#"))) ">" ZeroOrMore(.horizontalWhitespace) Capture(.iso8601.year().month().day()) ZeroOrMore(.horizontalWhitespace) /#|\Z/ } }
-
27:49 - Matching methods and strongly type captures in Regex
func parseLine(_ line: Substring) throws -> MailmapEntry { let regex = /\h*([^<#] ?)??\h*<([^>#] )>\h*(?:#|\Z)/ // or let regex = MailmapLine() guard let match = line.prefixMatch(of: regex) else { throw MailmapError.badLine } return MailmapEntry(name: match.1, email: match.2) }
-
29:02 - A use case for protocols
/// Used in the commit list UI struct HashedMailmap { var replacementNames: [String: String] = [:] } /// Used in the mailmap editor UI struct OrderedMailmap { var entries: [MailmapEntry] = [] } protocol Mailmap { mutating func addEntry(_ entry: MailmapEntry) } extension HashedMailmap: Mailmap { … } extension OrderedMailmap: Mailmap { … }
-
29:26 - Using the Mailmap protocol
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
31:05 - `Mailmap` and `any Mailmap`
func addEntries1<Map: Mailmap>(_ entries: Array<MailmapEntry>, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
31:17 - Improvements to `any` types
extension Mailmap { mutating func mergeEntries<Other: Mailmap>(from other: Other) { … } } func mergeMailmaps(_ a: any Mailmap, _ b: any Mailmap) -> any Mailmap { var copy = a copy.mergeEntries(from: b) return a }
-
32:21 - More improvements to `any` types
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func addEntries2(_ entries: Array<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
32:54 - Using Collection as an `any` type
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
33:04 - Primary associated types
protocol Collection<Element>: Sequence { associatedtype Index: Comparable associatedtype Iterator: IteratorProtocol<Element> associatedtype SubSequence: Collection<Element> where SubSequence.Index == Index, SubSequence.SubSequence == SubSequence associatedtype Element }
-
33:42 - Using primary associated types in Collection
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } } extension Collection<MailmapEntry> { … }
-
34:35 - Example of type erasing wrappers
struct AnySprocket: Sprocket { private class Base { … } private class Box<S: Sprocket>: Base { … } private var box: Base // …dozens of lines of code you hate // having to maintain… }
-
34:38 - Replace boxes with built-in `any` types
struct AnySprocket: Sprocket { private var box: any Sprocket // …fewer lines of code you hate // having to maintain… }
-
34:44 - Or try type aliases
typealias AnySprocket = any Sprocket
-
35:09 - `any` types have important limitations
protocol Mailmap: Equatable { mutating func addEntry(_ entry: MailmapEntry) } func areMailmapsIdentical(_ a: any Mailmap, _ b: any Mailmap) -> Bool { return a == b }
-
35:44 - Using generic types vs. `any` types
func addEntries1<Entries: Collection<MailmapEntry>, Map: Mailmap>(_ entries: Entries, to mailmap: inout Map) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
36:40 - `some Mailmap` and `any Mailmap`
func addEntries1<Entries: Collection<MailmapEntry>>(_ entries: Entries, to mailmap: inout some Mailmap) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
36:50 - `some Mailmap` and `any Mailmap` with Collection and primary associated types
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap) { for entry in entries { mailmap.addEntry(entry) } } func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) { for entry in entries { mailmap.addEntry(entry) } }
-
-
찾고 계신 콘텐츠가 있나요? 위에 주제를 입력하고 원하는 내용을 바로 검색해 보세요.
쿼리를 제출하는 중에 오류가 발생했습니다. 인터넷 연결을 확인하고 다시 시도해 주세요.