반응형

Hacking with Swift 사이트의 강좌 번역본입니다.

[원문 : https://www.hackingwithswift.com/articles/218/whats-new-in-swift-5-3]

What’s new in Swift 5.3

Swift 5.3은 Swift에 대한 또 다른 개선사항을 제공하고, 다중 패턴 catch 절과 다중 후행 클로져와 같은 강력한 몇가지 새로운 기능들과 Swift Package Manager의 몇가지 중요한 변경사항을 포함합니다.

이 글에서는 각각 중요한 변경사항을 살펴볼 것이며, 실습 코드 샘플을 제공해서 직접 사용해 볼수 있습니다. 자세한 정보는 Swift Evolution 제안(proposals) 링크를 따라가길 권장하고, 이전 what’s new in Swift 5.2 글을 놓친경우에, 확인하세요.

다중 패턴 catch 절(Multi-pattern catch clauses)

SE-0276은 단일 catch 블록에서 여러 오류 케이스를 잡을(catch) 수 있는 기능을 도입해서 오류 처리에서 일부 중복을 제거할 수 있습니다.

예를들어, 오류에 대해 2개의 열거형 케이스를 정의하는 코드가 있을수 있습니다.

enum TemperatureError: Error {
    case tooCold, tooHot
}

온도를 읽을때 오류를 던지거나 OK를 다시 보낼 수 있습니다.

func getReactorTemperature() -> Int {
    90
}

func checkReactorOperational() throws -> String {
    let temp = getReactorTemperature()

    if temp < 10 {
        throw TemperatureError.tooCold
    } else if temp > 90 {
        throw TemperatureError.tooHot
    } else {
        return "OK"
    }
}

던져진 오류를 잡을때(catching), SE-0276은 tooHot tooCold를 쉼표로 구분해서 같은 방법으로 처리할 수 있습니다.

do {
    let result = try checkReactorOperational()
    print("Result: \(result)")
} catch TemperatureError.tooHot, TemperatureError.tooCold {
    print("Shut down the reactor!")
} catch {
    print("An unknown error occurred.")
}

원하는 만큼의 많은 오류 케이스를 처리할 수 있고, 필요한 경우에 오류 값을 바인딩할 수 있습니다.

다중 후행 클로져(Multiple trailing closures)

SE-0279는 여러개의 클로져로 함수를 호출하는 간단한 방법을 제공하는, 다중 후행 클로져를 도입했습니다.

이는 다음 코드처럼, 특히 SwiftUI에서 환영받을 것입니다.

struct OldContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button(action: {
            self.showOptions.toggle()
        }) {
            Image(systemName: "gear")
        }
    }
}

이제 다음과 같이 작성할 수 있습니다.

struct NewContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button {
            self.showOptions.toggle()
        } label: {
            Image(systemName: "gear")
        }
    }
}

기술적으로 label: } 이전과 동일한 줄에 있어야 할 필요가 없으므로, 원하는 경우에 이를 작성할 수 있습니다.

struct BadContentView: View {
    @State private var showOptions = false

    var body: some View {
        Button {
            self.showOptions.toggle()
        }

        label: {
            Image(systemName: "gear")
        }
    }
}

하지만, 가독성(readability)을 위해하는 것을 주의해야합니다 - 이렇게 떨어져(floating) 있는 코드는 결코 즐겁지 않으며, Swift에서는 Button 초기화의 두번째 매개변수가 아닌 레이블이 지정된 블록처럼 보입니다.

주의: Swift 포럼에서 다중 후행 클로져(multiple trailing closures)에 대한 열띤 토론이 많았고, 이 기회를 이용해서 사람들이 우리 커뮤니티에 참여할때 예의 바르게 행동하는 것을 상기시키고 싶습니다.

열거형에 대한 합성된 비교가능한 적합성(Synthesized Comparable conformance for enums)

SE-0256는 연관된 값이 없거나 연관된 값 자체가 Comparable인 열거형에 대한 Comparable을 준수하는 것을 선택할 수 있습니다. <, >과 비슷한 것을 사용한 열거형의 2가지 케이스를 비교할 수 있게 합니다.

예를들어, 옷의 크기를 설명하는 열거형이 있다면 Swift에 다음과 같이 Comparable을 준수하는 것을 합성하도록 요청할 수 있습니다.

enum Size: Comparable {
    case small
    case medium
    case large
    case extraLarge
}

이제 2개의 열거형 인스턴스를 만들고 다음과 같이, 다음과 같이 <을 사용해 비교합니다.

let shirtSize = Size.small
let personSize = Size.large

if shirtSize < personSize {
    print("That shirt is too small")
}

합성된 적합성은 Comparable한 관련된 값과 잘 동작합니다. 예를들어, 축구 월드컵 우승팀을 설명하는 열거형이 있다면, 우리는 다음과 같이 작성할 수 있습니다.

enum WorldCupResult: Comparable {
    case neverWon
    case winner(stars: Int)
}

다양한 값을 가진 열거형 인스턴스 여러개를 생성하고나서 Swift가 정렬 할 수 있습니다.

let americanMen = WorldCupResult.neverWon
let americanWomen = WorldCupResult.winner(stars: 4)
let japaneseMen = WorldCupResult.neverWon
let japaneseWomen = WorldCupResult.winner(stars: 1)

let teams = [americanMen, americanWomen, japaneseMen, japaneseWomen]
let sortedByWins = teams.sorted()
print(sortedByWins)

월드컵에서 우승하지 못한 2개의 팀이 먼저오도록 정렬하고나서 일본 여자 팀, 미국 여자 팀으로 정렬합니다. - 2개의 winner 케이스가 neverWon 케이스보다 높은것으로 간주하고, winner(stars: 4)  winner(stars: 1) 보다 높은것으로 간주합니다.

self는 더 이상 많은 곳에서 필요하지 않음(self is no longer required in many places)

SE-0269는 필요하지 않는 많은 곳에서 self 사용을 멈출수 있습니다. 이러한 변경 전에는, self를 참조하는 모든 클로져에서 캡처 의미를 명시적으로 self. 작성이 필요했었습니다. 하지만 종종 클로져가 참조 주기를 가져올 수 없을 수가 있었는데, self가 어수선하다(clutter)는 것을 의미합니다.

예를 들어, 변경하기 전에는 다음과 같이 코드를 작성했습니다.

struct OldContentView: View {
    var body: some View {
        List(1..<5) { number in
            self.cell(for: number)
        }
    }

    func cell(for number: Int) -> some View {
        Text("Cell \(number)")
    }
}

self.cell(for:) 호출은 구조체 내부에서 사용되고 있기 때문에, 참조 주기를 발생할 수 없습니다. SE-0269 덕분에, 이제 다음과 같은 코드를 작성할 수 있습니다.

struct NewContentView: View {
    var body: some View {
        List(1..<5) { number in
            cell(for: number)
        }
    }

    func cell(for number: Int) -> some View {
        Text("Cell \(number)")
    }
}

이는 SwiftUI와 Combine을 포함해서 클로져를 많이 사용하는 모든 프레임워크에서 매우 인기가 있씁니다.

타입 기반 프로그램 진입점(Type-Based Program Entry Points)

SE-0281 프로그램의 진입 점을 선언하기 위해 새로운 @main 속성을 도입했습니다. 이를 통해서 코드의 어느 부분이 실행을 시작할지 정확하게 제어할 수 있으며, 특히 명령 줄 프로그램에서 유용합니다.

예를들어, 이전에 터미널(terminal) 앱을 만들때 부트스크랩(bootstrap: 스스로 동작하는)이 가능한 main.swift 파일을 만들어야 했습니다.

struct OldApp {
    func run() {
        print("Running!")
    }
}

let app = OldApp()
app.run()

Swift는 자동으로 main.swift의 코드를 최상위 코드로 간주했으므로, App인스턴스를 만들고 실행합니다. SE-0281 이후에도 여전히 유지 하지만, 이제는 원하는 경우에 main.swift를 제거하고 대신 @main 속성을 사용해서 프로그램의 진입 점으로 사용할 정적 main() 메소드를 포함하는 구조체 또는 클래스를 표시할 수 있습니다.

@main
struct NewApp {
    static func main() {
        print("Running!")
    }
}

실행할때, Swift는 자동으로 NewApp.main()을 호출해서 코드를 시작합니다.

새로운 @main 속성은 UIKit과 AppKit 개발자들에게 익숙할 것이며, @UIapplicationMain @NSApplicationMain을 사용해서 앱 델리게이터(delegates)를 표시합니다.

하지만, @main을 사용할때 주의해야할 몇가지가 있습니다.

  • 이미 main.swift 파일이 있는 앱에서는 이 속성을 사용할 수 없습니다.
  • @main 속성이 하나 이상일 수 없습니다.
  • @main 속성은 기본 클래스에만 적용할 수 있습니다 - 하위 클래스에 상속되지 않습니다.

문맥상 제네릭 선언에서의 where 절(where clauses on contextually generic declarations)

SE-0267 는 제네릭(generic) 타입과 확장 내의 함수에 where절을 붙이는 기능을 도입했습니다.

예를들어, 비공개 배열로부터 값을 넣고(push) 뺄(pop) 수 있는 간단한 Stack 구조체로 시작할 수 있습니다.

struct Stack<Element> {
    private var array = [Element]()

    mutating func push(_ obj: Element) {
        array.append(obj)
    }

    mutating func pop() -> Element? {
        array.popLast()
    }
}

SE-0267을 사용해서, 스택에 새로운 sorted() 메소드를 추가할수 있지만, 스택 내부의 요소가 Comparable을 준수할때만 가능합니다.

extension Stack {
    func sorted() -> [Element] where Element: Comparable {
        array.sorted()
    }
}

프로토콜을 살펴보는 열거형 케이스(Enum cases as protocol witnesses)

SE-0280 은 열거형이 프로토콜과 일치하는 것을 허용하며, 이제 프로토콜의 요구사항을 보다 쉽게 일치할 수 있는 기술적인 방법입니다.

예를들어, 다양한 타입의 데이터를 처리하는 코드를 작성할 수 있지만, 데이터가 없으면 어떻게 될까요? 물론, 언제나 기본값을 제공하기 위해 nil 결합(coalescing)과 같은 것을 사용할 수 있지만, 기본 값이 필요한 프로토콜을 만들수 있으며, 그리고나서 원하는 기본값으로 다양한 타입에 따르도록(conform) 합니다.

protocol Defaultable {
    static var defaultValue: Self { get }
}

// make integers have a default value of 0
extension Int: Defaultable {
    static var defaultValue: Int { 0 }
}

// make arrays have a default of an empty array
extension Array: Defaultable {
    static var defaultValue: Array { [] }
}

// make dictionaries have a default of an empty dictionary
extension Dictionary: Defaultable {
    static var defaultValue: Dictionary { [:] }
}

SE-0280이 허용하는 것은 열거형과 똑같습니다. 예를들어, 몇 픽셀, 몇 센치미터 또는 시스템에 의해 결정된 기본 값을 사용할 수 있는 padding 열거형을 만들고자 합니다.

enum Padding: Defaultable {
    case pixels(Int)
    case cm(Int)
    case defaultValue
}

SE-0280 이전에는 이런 종류의 코드가 가능하지 않았습니다 - Swift는 Padding 프로토콜을 만족하지 않는다고 했을 것입니다. 하지만 그 프로토콜을 통해 만족한다 생각한다면: Self를 반환하는 정적 defaultValue가 필요하다고 말했습니다. 예를들어, 프로토콜을 따르는 구체적인 타입이 무엇이든간에 Padding.defaultValue가 바로 그 역할을 하는 것입니다.

개선된 didSet 의미(Refined didSet Semantics)

SE-0268 didSet 프로퍼티 옵져버가 동작하는 방법을 조정하는 것이 더 효율적입니다. 이전 버그 동작에 의존하지 않는한 코드를 변경할 필요는 없습니다. 무료로 약간의 성능 향상을 얻을 수 있습니다.

내부적으로, 이 변경사항으로 Swift는 이전 값을 사용하지 않는 경우에 새로운 값을 설정할때 이전 값을 검색(retrieve)하지 않았고, oldValue 를 참조하지 않고 willSet이 없는 경우에 Swift는 데이터를 즉시 변경할 것입니다.

이전 동작에 의존하는 경우에, 다음과 같이, 사용자정의 getter를 트리거하기 위해 oldValue를 참조해서 간단하게 처리할 수 있습니다.

didSet {
    _ = oldValue
}

새로운 Float16 타입(A new Float16 type)

SE-0277은 그래픽 프로그래밍과 머신러닝에 일반적으로 사용되는 Float16 이라는 새로운 반-정밀도 부동소수점 타입을 도입했습니다.

새로운 부동소수점 타입은 Swift의 다른 유사한 타입과 함께 적합합니다.

let first: Float16 = 5
let second: Float32 = 11
let third: Float64 = 7
let fourth: Float80 = 13

Swift Package Manager는 바이너리 종속성, 리소스등을 얻기(Swift Package Manager gains binary dependencies, resources, and more)

Swift 5.3은 Swift Package Manager(SPM)에 대한 많은 개선이 있었습니다. 비록 여기에서 실습 예제를 제공할수는 없지만, 최소한 무엇이 바뀌었고 왜 바뀌었는지 논의할 수 있습니다.

첫번째, SE-0271 (Package Manager Resources) SPM은 이미지, 오디오, JSON, 등과 같은 리소스를 포함할수 있습니다. 이는 완성된 앱 번들로 파일을 복사하하는것 이상입니다 - 예를들어, iOS에 대한 이미지 최적화와 같은, assets에 사용자정의 처리 단계를 적용할 수 있습니다. 또한 런타임에 이러한 asserts에 접근하기 위한 새로운 Bundle.module 프로퍼티를 추가합니다. SE-0278 (Package Manager Localized Resources)는 프랑스어로된 이미지와 같은, 리소스의 현지화된(localized) 버젼을 허용합니다.

두번째, SE-0272 (Package Manager Binary Dependencies) SPM은 소스 패키지에 대한 기존 지원과 함께 바이너리 패키지를 사용할 수 있습니다. 이는 Firebase와 같은 일반적인 비공개 소스 SDK를 SPM을 사용해서 통합할 수 있다는 것을 의미합니다.

세번째, SE-0273 (Package Manager Conditional Target Dependencies)는 특정 플랫폼과 구성에 대해서만 종속성을 가지도록 대상(targets)을 구성할 수 있습니다. 예를들어, Linux용 컴파일 할때 특정 추가 프레임워크가 필요하거나 로컬 테스트를 위해서 컴파일할때 디버그 코드를 빌드해야 한다고 할 수 있습니다.

SE-0271의 Future Directions 섹션에 개별 리소스 파일에 대한 타입에 안전한 접근 가능성이 언급되어 있습니다 - SPM이 Swift 코드로 리소스 파일에 대한 특정 선언을 생성할수 있으므로, Image(avatar)와 같은 것이 Image(module.avatar)과 같은 것이 됩니다.

반응형

'Swift > Tip' 카테고리의 다른 글

What’s new in Swift 5.6  (0) 2022.03.21
Swift version과 Xcode version  (0) 2022.02.09
What’s new in Swift 5.5  (0) 2022.01.25
What’s new in Swift 5.4  (0) 2021.04.15
What’s new in Swift 5.2  (0) 2021.04.15
How to use opaque return types in Swift 5.1  (0) 2019.12.18
What’s new in Swift 5.1  (0) 2019.12.17
What’s New in Swift 5.0  (0) 2019.03.07
Posted by 까칠코더
,