Hacking with Swift 사이트의 강좌 번역본입니다.
[원문 : https://www.hackingwithswift.com/articles/212/whats-new-in-swift-5-2]
What’s new in Swift 5.2
Swift 5.2는 Xcode 11.4와 함께 출시되었고, 코드 크기와 메모리 사용량 감소와 더불어 몇가지 언어적인 변경 사항을 포함하고 있고, 오류를 더 빨리 이해하고 해결하는데 도움을 줄 진단 아키텍쳐를 포함하고 있습니다.
이 글에서는 몇가지 실습(hands-on) 예제를 통해서 변경된 사항을 살펴보고 어떻게 진화되었는지를 직접 확인할 수 있습니다. 더 많은 정보를 얻고자한다면 Swift Evolution proposals에 대한 링크를 따라가길 권장하고, 만약에 What’s new in Swift 5.1 글을 놓친 경우에 이를 확인하세요.
팁 : 이 글에 대한 Xcode playground를 다운로드 할 수 있습니다. https://github.com/twostraws/whats-new-in-swift-5-2
함수로서의 Key Path 표현식(Key Path Expressions as Functions)
SE-0249는 몇가지(handful) 특정 상황에서 keypaths를 사용할 수 있는 놀라운 단축(shortcut)방법을 도입했습니다.
Evolution 제안(proposal)은 (Root) -> Value의 함수가 허용되는 어디에서든 \Root.value을 사용할 수 있다고 설명하지만, 이전에 메소드로 Car 를 보내고 번호판을 돌려 받았지만, 이제는 Car.licensePlate을 대신 사용할 수 있다는 것을 의미합니다.
이는 예제로 가장 잘 이해가되므로, 다음은 4가지 프로퍼티를 정의하는 User 타입입니다.
struct User {
let name: String
let age: Int
let bestFriend: String?
var canVote: Bool {
age >= 18
}
}
다음과 같이 구조체의 인스턴스를 생성하고 배열에 넣을 수 있습니다.
let eric = User(name: "Eric Effiong", age: 18, bestFriend: "Otis Milburn")
let maeve = User(name: "Maeve Wiley", age: 19, bestFriend: nil)
let otis = User(name: "Otis Milburn", age: 17, bestFriend: "Eric Effiong")
let users = [eric, maeve, otis]
이제 중요한 부분입니다 : 모든 사용자 이름을 얻으려는 경우에, 다음과 같이 key-path를 사용하면 됩니다.
let userNames = users.map(\.name)
print(userNames)
이전에는 다음과 같이 이름을 직접 검색하기 위해서 클로져를 작성해야 했습니다.
let oldUserNames = users.map { $0.name }
이 접근법은 다른곳에서도 동일합니다 - 이전에는 값을 받고 프로퍼티 중 하나를 다시 전달했으며, 이제는 대신 key path를 사용할 수 있습니다. 예를들어, 투표할 수 있는 사용자들을 모두 반환할 것입니다.
let voters = users.filter(\.canVote)
그리고 모든 사용자에 대한 가장 친한 친구를 반환 할 것입니다.
let bestFriends = users.compactMap(\.bestFriend)
사용자가 정의한 이름뿐인 타입의 호출 가능한 값(Callable values of user-defined nominal types)
SE-0253은 Swift에서 정적으로 호출 가능한 값을 도입했으며, 타입이 callAsFunction()이라는 메소드를 구현하는 경우에 값을 직접 호출 할 수 있다는 멋진 방법입니다. 이러한 행동(behavior)을 동작하기 위해서 특별한 프로토콜을 준수할 필요는 없습니다. 해당 타입에 메소드를 추가만 하면 됩니다.
예를들어, lowerBound와 upperBound에 대한 프로퍼티가 있는 Dice 구조체를 만들고나서 callAsFunction을 추가해서 주사위 값을 호출할때마다 임의로(random) 굴립니다.
struct Dice {
var lowerBound: Int
var upperBound: Int
func callAsFunction() -> Int {
(lowerBound...upperBound).randomElement()!
}
}
let d6 = Dice(lowerBound: 1, upperBound: 6)
let roll1 = d6()
print(roll1)
1에서 6까지 임의의 숫자를 출력하고, callAsFunction()을 바로 사용하는것과 동일합니다. 예를들어, 다음과 같이 호출할 수 있습니다.
let d12 = Dice(lowerBound: 1, upperBound: 12)
let roll2 = d12.callAsFunction()
print(roll2)
Swift는 callAsFunction()이 정의된 방법을 기반으로 호출 사이트(sites)를 자동으로 조정합니다. 예를들어, 원하는 만큼의 매개변수를 추가할 수 있으며, 반환 값을 조절할 수 있고, 필요한 경우에 메소드를 mutating으로 표시 할 수도 있습니다.
예를 들어, 얼마나 멀리 걸었는지 추적하고 10,000보 목표에 도달했는지를 보고하는 StepCounter 구조체를 만듭니다.
struct StepCounter {
var steps = 0
mutating func callAsFunction(count: Int) -> Bool {
steps += count
print(steps)
return steps > 10_000
}
}
var steps = StepCounter()
let targetReached = steps(count: 10)
더 고급 사용을 위해, callAsFunction()은 throws와 rethrows 모두 지원하고, 단일 타입에서 여러개의 callAsFunction() 메소드를 정의할수 있습니다 - Swift는 일반 오버로딩(overloading)과 마찬가지로 호출 사이트(site)에따라 올바른 것을 선택할 것입니다.
첨자에 기본 인자를 선언할 수 있음(Subscripts can now declare default arguments)
타입에 사용자정의 첨자(subscripts)를 추가할때, 이제 모든 매개변수에 기본 인자(arguments)를 사용할 수 있습니다. 예를들어, 경찰조직(Police force)에서 경찰관(officers)을 읽는 사용자정의 첨자(subscript)를 가진 PoliceForce 구조체가 있는 경우에, 배열의 경계를 벗어난 인덱스를 읽으려고 할때 돌려보내는 default 매개변수를 추가할 수 있습니다.
struct PoliceForce {
var officers: [String]
subscript(index: Int, default default: String = "Unknown") -> String {
if index >= 0 && index < officers.count {
return officers[index]
} else {
return `default`
}
}
}
let force = PoliceForce(officers: ["Amy", "Jake", "Rosa", "Terry"])
print(force[0])
print(force[5])
Amy가 출력되고나서 Unknown이 출력될 것이며, 후자는 인덱스 5에 경찰관이 없기 때문에 발생합니다. 첨자(subscripts)는 매개변수 레이블(label) 외에는 사용하지 않기 때문에, 매개변수 레이블을 2번 작성해야 합니다.
따라서, 첨자(subscript)에서 default default를 사용하기 때문에, 다음과 같이 사용자정의 값을 사용할 수 있습니다.
print(force[-1, default: "The Vulture"])
지연 필터링 순서가 이제 반전이 됨(Lazy filtering order is now reversed)
Swift 5.2에는 기능이 중단될 가능성이 있는 작은 변경사항이 있습니다. 배열과 같은 지연 시퀀스(lazy sequence)를 사용하고, 여러개의 필터를 적용하는 경우에, 이러한 필터들은 이제 역순으로 실행됩니다.
예를들어, 아래 코드에 S로 시작하는 이름을 선택하는 필터가 있고, 이름을 출력하고나서 true를 반환하는 두번째 필터가 있습니다.
let people = ["Arya", "Cersei", "Samwell", "Stannis"]
.lazy
.filter { $0.hasPrefix("S") }
.filter { print($0); return true }
_ = people.count
Swift 5.2 이후 부터는 첫번째 필터가 실행된 후에 두번째 필터로 이동하기 위해 유일하게 남은 이름들이기 때문에, Samwell과 “Stannis가 출력될것입니다. 하지만 Swift 5.2 이전(before)에는 두번째 필터가 첫번째 필터보다 먼저 실행되었기 때문에, 4개의 이름 모두 반환했을 것입니다. 이는 lazy가 제거된 경우에, 해당 코드는 Swift 버젼과 상관없이 항상 Samwell과 Stannis를 반환하기 때문에 혼란스러웠습니다.
이는 코드가 실행되는 위치에 따라 동작이 다르기 때문에 특히 문제가 됩니다: iOS 13.3 이전 또는 macOS 10.15.3 이전에서 Swift 5.2 코드를 실행하면, 예전과 같은 동작을 하지만, 최신 운영 체제에서 실행되는 동일한 코드는 새롭고 올바른 동작을 합니다.
따라서 이는 코드에서 갑작스러운 손장을 일으킬수 있는 변경이지만, 잠깐 동안의 불편사항(inconvenience)이길 바랍니다.
새롭고 개선된 진단(New and improved diagnostics)
Swift 5.2는 코딩 오류가 발생했을때 Xcode가 보여주는 오류 메시지의 품질(quality)과 정밀도(precision)를 개선하고자하는 목표(aims)로 새로운 진단 아키텍쳐를 도입했습니다. 이는 SwiftUI 코드로 작업할때 특히 뚜렷하며, Swift는 종종 잘못된 오류 메시지를 생성합니다.
예를들어 다음과 같은 코드를 고려하세요.
struct ContentView: View {
@State private var name = 0
var body: some View {
VStack {
Text("What is your name?")
TextField("Name", text: $name)
.frame(maxWidth: 300)
}
}
}
TextField 뷰를 유효하지 않는 정수 @State프로퍼티와 바인딩을 시도합니다. Swift 5.1 에서는 ‘Int'를 'CGFloat'로 변환할 수 없음을 말하는 frame() 수정자 오류가 발생했지만, Swift 5.2 이후부터는 오류가 $name 바인딩임을 정확하게 식별합니다 : Binding 타입의 값을 Binding으로 변환할 수 없음().
swift.org 블로그에서 새로운 진단 아키텍쳐에 대해서 자세히 살펴볼 수 있습니다.
'Swift > Tip' 카테고리의 다른 글
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.3 (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 |
What’s New in Swift 4.2? (0) | 2018.06.14 |