What's new in Swift

https://developer.apple.com/videos/play/wwdc2022/110354/
WWDC 2022의 What's new in Swift세션을 보고 정리한 글입니다.

Community update

문서생성도구 doc
swift.org 오픈소스화

Workgroup support

기존 swift on server / Diversity in Swift에 더해서 Swift Website / C++ Interoperability 워크그룹이 추가 됨

Cross-platform support

리눅스 지원 강화(Amazon Linux 2, CentOS7)

Swift Package

Swift의 지향점

높은 수준의 스크립트에서 저수준의 베어메탈영역까지 커버하는게 목표이므로 이를 위해 몇가지 개선을 함
독립 실행형 정적 링크 바이너리용 표준 라이브러리를 더 작게 만들기 위해 외부 유니코드 지원 라이브러리에 대한 의존성을 줄이고 더 빠른 구현으로 대체
외부 유니코드 및 지원라이브로

  • Statically linked standard library
    • 더 작고 빠른 라이브러리는 이벤트 드리븐 서버에서 작동할때 유리함
    • 제한된 프로세서에서도 동작할 수 있음

TOFU의 도입

패키지를 처음받을때 지문을 부여하고 이후 다운로드마다 지문의 유효성을 검사함. 지문이 다른 경우 오류를 보고한다.

Plugins

Command Tool Plugins

아래의 상황에서 사용되던 커맨드 플러그인

  • Generate documentation
  • Reformat source code
  • Generate test reports

shell script로 작성하던 것을 이제는 swift로도 사용 가능

커맨드 플러그인은 오픈소스 툴과 스위프트 패키지 매니저 사이에서 접착제와 같이 기능한다.

docC를 통해 문서를 코드에 통합하려할때, 아래와 같이 코드를 작성한다.

CommandPlugin 프로토콜을 준수하는 구조체를 생성하여 플러그인을 정의
이후에는 패키지 커맨드 라인과 Xcode 메뉴에서 사용이 가능하다.

Build tool plugins

빌드하는 동안 추가단계를 주입하도록 아래의 상황에서 사용되는 플러그인(앱 샌드박스에서 알아서 시작되므로 패키지의 내용을 변경할 수 있는 명시적 권한 부여 가능)

  • Source code generation
  • Resource processing

빌드 툴 플러그인을 사용하면 패키지 레이아웃에 해당 플러그인이 포함되어 스위프트 실행파일로써 동작하게 됨

Module aliasing
이름이 같으면, 모듈 충돌이 발생할 수 있다.

이를 해소하기 위해 모듈 명확화 라는 개념을 도입해서 충돌시에 사용될 다른 별칭을 부여할 수 있다.

Performance improvements in Swift 5.7

Build time

  1. New Swift Driver Setting
    Swift driver를 별도 실행파일이 아닌 Xcode Build 시스템에서 프레임워크로 사용할 수 있게하여 성능 향상
  2. Faster type checking of generics
    제네릭 시스템의 프로토콜과 where 절에서 함수의 signature 계산하는 부분을 다시 코딩하여 타입속도의 증대
    아래와 같이 제네릭, 프로토콜과 associated type, where절을 사용한 복잡한 타입정의의 경우 타입검사를 하는데 20초 걸리던것이 이제는 1초정도로 단축
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  
}

Runtime improvements

Optimized protocol conformance checking
기존에 앱 시작시 프로토콜 준수여부를 검사하여 4초정도까지 앱 실행시간이 길어지는 경우가 있었다. 이제는 캐시처리되어 일부 앱에서는 실행시간이 절반으로 단축

Concurrency updates

  • Back Deployed
    • 이전 운영체제(iOS13) 버전까지 배포를 지원(역배포)

Data race avoidance

swift의 정말 중요한 특징은 메모리 안전이다.

var numbers = [3, 2, 1]
numbers.removeAll(where: { number in
    number == numbers.count
})

위와 같은 코드를 실행하면 어떻게 될까? 3이 지워질지, 아니면 전체 요소가 지워질지 애매하다. 스위프트는 이런 경우, 즉 배열의 요소를 삭제하는 중에 배열 카운트에 접근하는것 자체가 안전하지 않으므로 동시접근 오류메시지와 함께 앱이 크래쉬된다.

이러한 측면에서, 애플은 swift의 쓰레드 안전성도 역시 극대화 하려고 노력한다. 그래서 아래와 같은 코드는 Swift 5.7에서는 더이상 동작하지 않고 만약 필요하다면 Actor를 사용해야한다. 자세한 내용은 해당 내용을 다룬 별도 세션을 참고(Eliminate data races using Swift Concurrency)

var numbers = [3, 2, 1]
Task { numbers.append(0) }
numbers.removeLast()

배열의 요소를 추가하는것과 삭제하는것의 실행순서가 보장되지 않으므로 이러한 작업 자체를 차단한다.

Swift 6의 목표가 궁극적인 쓰레드 안전성을 달성하는 것이라고 한다. 위에서 다룬 내용이 Robust Concurrency Model이고 별도로 빌드타임에 안전성 검사를 수행할 수 있게 하는 Opt-in Safety Checks도 지원

Distributed actors

Actor가 다른 시스템에도 나누어져 존재할 수 있는 개념
서버를 위한 Distributed Actors Package도 별도로 제공한다
Distributed actors 세션에서 자세히 다룬다.

Async algorithms

AsyncSequence와 관련된 알고리즘 패키지 - 플랫폼이나 다른 운영체제에 유연하게 쉽게 가능

Concurrency optimizations

동시성 최적화를 위한 노력

  • Actor prioritization: 행위자 우선순위제
  • Priority-inversion avoidance: 우선 순위 역전 방지 기능

이제 Instruments의 Actor의 Concurrency view에서 이러한 동시성 코드를 시각화하고 최적화하는 기능을 제공한다. 동시에 실행되는 작업 수, 생성된 총 작업등의 통계와 작업간의 상하관계를 시각화하여 확인이 가능하다.

Expressive Swift

언어는 도구일뿐이다. 다만, 그 언어는 우리가 작성하는 코드에 영향을 미칠 수 있다. 그렇기에 Swift를 발전시켜서 사용자가 원하는 바를 표현하는데 무리가 없게 해야한다.(애플의 생각)

Optional Unwrapping - if(guard) let 축약형

if let이나 guard let을 사용할 때 동일한 이름으로 짓는 경우가 많을 것이다. 이때 변수명이 길다면 코드가 불필요하게 길어지는 경우가 많았는데, 그러한 경우를 위해 축약형을 제공한다.

// 기존
// if let workingDirectoryMailmapURL = workingDirectoryMailmapURL { ...
if let workingDirectoryMailmapURL {

    mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")

}

guard let workingDirectoryMailmapURL else { return }

mailmapLines = try String(contentsOf: workingDirectoryMailmapURL).split(separator: "\n")

Clousre Type Inference

기존에 클로저 내부가 복잡해지면 클로저의 파라미터와 반환값을 계산해내는데 실패하는 경우가 많았는데 이를 개선했다. do-catch 나 제어흐름구문들을 사용해도 된다.

Permitted pointer conversions

Swift는 서로 다른 포인터 유형간 자동 변환을 지원하지 않는다. 특정 변환을 허용하는 C와는 매우 다른 점이다. 이러한 점은 Swift의 메모리 안전성을 우선하는 특성 때문이지만, C API를 Swift로 옮겨올때 문제가 발생한다. 이 때문에 메모리 안전성을 위해 C에 대한 액세스가 어려워지는 모순이 발생한다.(C는 다른 포인터로 변환해도 문제 없으므로)

그래서 이제 위와 같은 호출을 위한 별도의 규칙을 지원한다.

String process

아래 함수는 문자열에서 구문 분석을 하는 기능을 한다.

문자열을 쪼개고 자르고 다듬는 것 자체가 매우 힘든 일이었다. 일반적으로 Swift를 사용하는 사람들은 Index 시스템의 복잡함만을 이야기하지만, 그것은 큰 그림을 놓치는 것이다. 만약 인덱스를 사용하는 방식을 간결히 바꾸더라도 여전히 이 함수가 어떤일을 하는지 알아내기 위해선 코드와 대상 문자열을 오랫동안 들여다 보아야한다.

인덱스도 좀 바꿔줬으면...

그래서 애플은 이 모든 것을 뜯어내고 더 나은것으로 대체하려 한다. 명령적인 접근법이아니라 선언적인 접근법을 통하여야 하는데, 이를 위해 Swift는 5.7버전부터 정규표현식을 제공한다. 기존 복잡한 정규표현식 리터럴이 아닌 단어로 이루어진 쉽게 알아 볼 수 있는 정규표현식(Swift Regex)을 만들었다.

  • 이는 SwiftUI와 비슷한 모양을 띄고있다. 또한 기존 리터럴표현보다 더 나은 기능을 제공한다.
  • SwiftUI의 View계층과 유사하게, RegexComponent를 사용하여 조합, 반복하는것도 가능하다. - String 리터럴, Regex 리터럴과 섞어 표현하는것도 가능하다.
  • Swift Regex는 스위프트로 작성된 새로운 엔진을 사용하고, 더나은 유니코드 정확도를 제공
  • 이를 사용하기 위해서는 해당 기능이 내장된 macOS 13, iOS 16이 필요

Generic code Clarity

Any 키워드의 명시적 사용

아래와 같은 타입과 프로토콜이 있고, addEntry 메서드를 실구현 한다고 생각해보자.

struct HashedMailmap {
    var replacementNames: [String: String] = [:]
}

struct OrderedMailmap {
    var entries: [MailmapEntry] = []
}

protocol Mailmap {
    mutating func addEntry(_ entry: MailmapEntry)
}

extension HashedMailmap: Mailmap { … }
extension OrderedMailmap: Mailmap { … }

addEntries1 메서드는 Generic을 사용하여 Mailmap 프로토콜을 준수한 타입만을 파라미터로 받고있고, addEntries2 메서드는 파라미터의 타입선언부에 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 Mailmap) {
        for entry in entries {
            mailmap.addEntry(entry)
        }
    }

아래 표는 2개의 차이를 나타내고 있다. 왼쪽 열은 해당 프로토콜을 준수하는 인스턴스를 뜻하고, 오른쪽 열은 해당 프로토콜을 준수하는 컨텐츠를 가지고있는 박스를 나타낸다는 것이다. 첫번째 메서드는 왼쪽열의 2번째행에 해당하고 두번째 메서드는 오른쪽 열의 1번째에 해당한다.

일반적으로 박스가 더 많은 작업공간과 시간을 필요로 하며 내부에 인스턴스의 모든 기능이 포함되어 있지 않기 때문에 이 차이를 아는것이 중요하다. 하지만 기존에는 이 차이가 코드의 표현으로 드러나기 애매해서 어떤걸 사용하고 있는지 파악하기가 애매했다. 따라서 Swift 5.7부터는 아래와 같은 표현으로 이것을 구분한다.

바로 Any키워드를 사용하므로써 이 모호함을 없앨 수 있다.(아래 코드 참고)

아래와 같은 오류가 발생했을때도 어떤 현상이 일어나는지 파악이 쉬워진다.

mergeEntries 메서드가 필요로 하는것은 Generic MailMap인데, 전달하고 있는 것이 Any MailMap이라서 오류가 발생하는 것이다. 위에서 이야기 했듯 any 키워드를 붙이는 것은 Box형태이므로 Box를 열고 그 안의 내용물을 전달하지 않는 한 오류가 발생하는 것이다. 그래서 5.7버전에서는 이런 간단한 경우에는 Swift가 알아서 직접 내용물을 꺼내서 해당 파라미터에 전달하는 방식으로 해결하기 때문에 더이상 오류가 발생하지 않는다.

self, Associated type과 Protocol
(개인적으로 가장 기대하던 개선점이다!)

기존에 Self, Associated type을 사용한 프로토콜에서 아래와 같은 오류가 발생했었다.

5.7버전에서는 이러한 오류가 완전히 없어졌다!!!

Collection
이제 컬렉션 프로토콜도 구체타입처럼 쓸 수 있게 되었다.

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)
    }
}

기본 관련 유형이라는것이 도입되었는데, 꺽쇠를 사용하여 많은 연관 값들 중 Type Constraint을 적용하고 싶은 타입을 선택할 수 있다.

Type Erasing Wrapper
기존에 있던 AnyCollection 구조체도 계속 지원될 것이지만, any 키워드를 사용하는것을 권장한다고 한다.(이전 버전과의 호환성 고려 및 아직 any 키워드에서 지원하지 못하는 기능들이 있기 때문에)

만약 아래와 같이 커스텀 Type Erasing Wrapper를 사용했다면, 대체할 수 있는지 확인해보자.

정리하면,

  • Any 키워드의 도입으로 이제 Box가 사용중인 위치를 확인할 수 있게 되었다.
  • Generic 인자로 전달도 가능하다.
  • Self와 Associated Type도 지원한다.
  • Primary Assocaited Type 지원한다.

하지만 any 키워드의 한계도 있는데,

  • 등호 연산자를 사용할 수 없다.(구체타입이 같은지를 보장할 수 없기 때문에)
  • 가능하다면, Generic을 사용하는게 성능면을 포함해서 모든면에서 좋다.(부득이한 경우에만 사용하자)

근데 any 키워드가 복잡한 제네릭 꺽쇠 문법보다 쉬워서 사용하고싶은데 어떡하죠???

some 키워드를 사용하여 완벽히 동일한 코드를 작성할 수 있다고 한다.

'iOS > WWDC' 카테고리의 다른 글

[WWDC22] Bring your world into augmented reality(AR)  (0) 2022.06.09