[최종 수정일 : 2018.06.14]

원문 : https://www.raywenderlich.com/194066/whats-new-in-swift-4-2

What’s New in Swift 4.2?


Swift 4.2는 현재 Xcode 10 beta에서 사용가능합니다. 이 릴리즈는 중요한 Swift 4.1 기능을 업데이트하고 ABI(Application binary interface 안정성에 대비하여 언어를 개선합니다.

이 튜토리얼은 Swift 4.2에서 가장 중요한 변경사항을 다룹니다. Xcode 10이 필요하며, 시작하기 전에 Xcode 최신 베타 버젼을 다운로드하고 설치합니다.

시작하기(Getting Started)

Swift 4.2는 Swift 4.1과 소스가 호횐되지만, 다른 릴리즈와는 바이너리 호환이 되지 않습니다. 애플은 Swift 4.2를 Swift 5에서 다른 Swift 버젼으로 컴파일된 라이브러리와 앱 간의 바이너리 호환성을 가능하게 하는, ABI 안전성(ABI stability) 이루기 위한 중간 단계를 설계하였습니다. ABI(Application Binary Interface)기능은 최종 ABI로 통합되기 전에 커뮤니티로부터 피드백을 받을수 있는 충분한 시간을 제공합니다.

이 튜토리얼의 섹션에는 [SE-0001] 처럼 Swift Evolution 제안 번호가 포함되어 있습니다. 각각의 제안에 링크된 태그를 클릭하여 각 세부 변경사항을 살펴볼수 있습니다.

playground에서 변경사항을 적용해보면 튜토리얼을 최대한 활용할 수 있을 것입니다. Xcode 10을 시작하고 File > New > Playground로 이동합니다. 플랫폼을 iOS으로 선택하고 템플릿을 Blank로 선택합니다. 원하는 이름을 주고 원하는 곳에 저장합니다. 이제 시작할 준비가 됬습니다.

주의
Swift 4.1의 주요내용(highlights)이 필요하나요? Swift 4.1 튜토리얼을 보세요. : What’s New in Swift 4.1?

언어 개선(Language Improvements)

이번 릴리즈에서는 난수 발생기(random number generators), 동적 멤버 검색(dynamic member lookup), 등등 몇가지 언어 기능이 있습니다.

난수 발생기(Generating Random Numbers)

Swift 4.1은 아래와 같이 C API를 가져와서 난수를 생성합니다.

let digit = Int(arc4random_uniform(10))

arc4random_uniform(_:)은 0에서 9까지의 임의의 숫자를 반환하였습니다. Foundation을 가져와야(import) 하고 Linux에서는 동작하지 않았습니다. 한편 모든 리눅스 기반의 접근 방식은 등적 숫자보다 더 많이 생성했음을 의미하는 modulor bias를 도입하였습니다.

Swift 4.2는 표준 라이브러리에 random API를 추가하여 이러한 문제를 해결합니다. SE-0202

// 1  
let digit = Int.random(in: 0..<10)

// 2
if let anotherDigit = (0..<10).randomElement() {
  print(anotherDigit)
} else {
  print("Empty range.")
}

// 3
let double = Double.random(in: 0..<1)
let float = Float.random(in: 0..<1)
let cgFloat = CGFloat.random(in: 0..<1)
let bool = Bool.random()

이것들은 다음과 같은 일을 합니다.

  1. 범위(range)에서 임의의 숫자를 생성하기 위해 random(in:)을 사용합니다.
  2. 범위(range)가 비어 있는 경우에 randomElement()는 nil을 반환하며, if let으로 Int?을 언래핑합니다.
  3. Double, Float, CGFloat 임의의 숫자를 생성하기 위해 random(in:)을 사용하고 임의의 Bool을 반환하기 위해 random()을 사용합니다.


Swift 4.1은 또한 배열에 임의의 값을 생성하기 위해 C 함수를 사용하였습니다.

let playlist = ["Nothing Else Matters", "Stairway to Heaven", "I Want to Break Free", "Yesterday"]
let index = Int(arc4random_uniform(UInt32(playlist.count)))
let song = playlist[index]

Swift 4.1은 playlist에서 유요한 index를 생성하고 해당하는 song을 반환하기 위해 arc4random_uniform(_:)을 사용하였습니다. 이 해결책은 Int와 UInt32간에 형변환이 필요하고 앞에서 언급한 모든 문제를 가지고 있습니다.

Swift 4.2는 보다 직관적인 접근법을 사용합니다.

if let song = playlist.randomElement() {
  print(song)
} else {
  print("Empty playlist.")
}

playlist가 비어 있는 경우에 randomElement()는 nil을 반환하며, 반환된 String?을 언래핑합니다.

Swift 4.1은 컬렉션의 셔플(shuffling) 알고리즘을 포함하지 않았기에, 의도한 결과를 얻기 위해 원형 교차로(roundabout) 방식을 사용해야했습니다.

// 1
let shuffledPlaylist = playlist.sorted{ _, _ in arc4random_uniform(2) == 0 }

// 2
var names = ["Cosmin", "Oana", "Sclip", "Nori"]
names.sort { _, _ in arc4random_uniform(2) == 0 }

다음은 이 코드에서 하는 일입니다.

  1. playlist의 셔플 순서를 결정하기 위해 arc4random_uniform(_:)를 사용하고 sorted(_:_:)으로 shuffledPlaylist를 반환합니다.
  2. 그리고나서 이전 기술을 사용하여 sort(_:_:)가 있는 곳에서 names을 섞습니다.

Swift 4.2는 보다 효율적이고, 더 우아한, 셔플 알고리즘(shuffling algorithms)을 제공합니다.

let shuffledPlaylist = playlist.shuffled()
names.shuffle()

4.2에서는 섞인 재생목록을 생성하기 위해 간단하게 shuffled()를 사용하고 shuffle()로 names를 바로 섞어 줍니다.


동적 멤버 검색(Dynamic Member Lookup)

Swift 4.1은 사용자정의 서브스크립트(subscript) 호출을 위해 다음에 오는 대괄호([]) 구문을 사용하였습니다.

class Person {
  let name: String
  let age: Int
  private let details: [String: String]
  
  init(name: String, age: Int, details: [String: String]) {
    self.name = name
    self.age = age
    self.details = details
  }
  
  subscript(key: String) -> String {
    switch key {
      case "info":
        return "\(name) is \(age) years old."
      default:
        return details[key] ?? ""
    }
  }
}

let details = ["title": "Author", "instrument": "Guitar"]
let me = Person(name: "Cosmin", age: 32, details: details)
me["info"]   // "Cosmin is 32 years old."
me["title"]  // "Author"

이 경우에 서브스크립트(subscript)는 사람의 이름과 나이에 따라 개인 데이터 저장소 또는 사용자 정의 메시지 컨텐츠를 반환합니다.

Swift 4.2는 SE-0195에서 서브스크립트(subscripts) 대신에 점(dot) 문법을 제공하기 위해 동적 멤버 검색(dynamic memeber lookup)을 사용합니다.

@dynamicMemberLookup
class Person {
  let name: String
  let age: Int
  private let details: [String: String]
  
  init(name: String, age: Int, details: [String: String]) {
    self.name = name
    self.age = age
    self.details = details
  }
  
  // 2
  subscript(dynamicMember key: String) -> String {
    switch key {
      case "info":
        return "\(name) is \(age) years old."
      default:
        return details[key] ?? ""
    }
  }
}

주석에 대한 설명

  1. Parson에 사용자정의 서브스크립트(subscripts)에 대해 점(dot) 문법을 사용할수 있도록 @dynamicMemberLookup을 표시합니다.
  2. 이 클래스에 대해 subscript(dynamicMember:)를 구현하여 @dynamicMemberLookup을 준수합니다.
  3. 이전에 구현된 서브스크립트(subscript)는 점(dot) 구문을 사용하여 호출합니다.

컴파일러는 런타임시에 서브스크립트를 동적으로 호출하므로 Python이나 Ruby와 같은 스크립트 언어와 마찬가지로 타입에 안전한 코드를 작성할 수 있습니다.

동적 멤버 검색은 클래스 속성을 망가트리지 않습니다.

me.name // "Cosmin"
me.age // 32

이 경우에 name과 age를 호출하기 위해 서브스크립트(subscript) 대신에 점(dot) 구문을 사용합니다.

더욱이, 파생 클래스는 기본적으로 동적 멤버 검색을 상속받습니다.

@dynamicMemberLookup
class Vehicle {
  let brand: String
  let year: Int
  
  init(brand: String, year: Int) {
    self.brand = brand
    self.year = year
  }
  
  subscript(dynamicMember key: String) -> String {
    return "\(brand) made in \(year)."
  }
}

class Car: Vehicle {}

let car = Car(brand: "BMW", year: 2018)
car.info  // "BMW made in 2018."

어떤 car일지라도 Vehicle이고 Vehicle은 @dynamicMemberLookup을 구현했기에 car의 서브스크립트(subscript)를 호출하기 위해 점(dot) 구문을 사용할수 있습니다.

프로토콜 확장(protocol extensions)으로 기존 타입에 대해 동적 멤버 검색을 추가할수 있습니다.

// 1
@dynamicMemberLookup
protocol Random {}

// 2
extension Random {
  subscript(dynamicMember key: String) -> Int {
    return Int.random(in: 0..<10)
  }
}

// 3
extension Int: Random {}

// 4
let number = 10
let randomDigit = String(number.digit)
let noRandomDigit = String(number).filter { String($0) != randomDigit }

다음은 실제 상황입니다.

  1. 서브스크립트에 점(dot) 구문을 사용하기 위해 Random에 @dynamicMemberLookup을 표시합니다.
  2. 프로토콜을 확장하고 구현하여 @dynamicMemberLookup을 준수하도록 합니다. 서브스크립트는 0과 9사이의 임의의 숫자를 가져오기 위해 random(in:)를 사용합니다.
  3. Int를 확장하고 Random을 준수하도록 합니다.
  4. 임의의 숫자를 생성하기 위해 점(dot) 구문을 사용하고 number에서 필터링(filter)합니다.

열거형 케이스 컬렉션(Enumeration Cases Collections)

Swift 4.1은 기본적으로 열거형 케이스 컬렉션에 대한 접근을 제공하지 않습니다. 이로 인해 다음과 같이 좋지 않은 해결책이 남아있습니다.

enum Seasons: String {
  case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
}

enum SeasonType {
  case equinox
  case solstice
}

let seasons = [Seasons.spring, .summer, .autumn, .winter]
for (index, season) in seasons.enumerated() {
  let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
  print("\(season.rawValue) \(seasonType).")
}

여기에서 Seasons 케이스를 seasons에 추가하고 배열을 반복하여 각 season 이름과 타입을 가져옵니다. 하지만 Swift 4.2는 더 잘할수 있습니다.

Swift 4.2는 열거형에 열거형 케이스 배열을 추가합니다. SE-0194

// 1
enum Seasons: String, CaseIterable {
  case spring = "Spring", summer = "Summer", autumn = "Autumn", winter = "Winter"
}

enum SeasonType {
  case equinox
  case solstice
}

// 2
for (index, season) in Seasons.allCases.enumerated() {
  let seasonType = index % 2 == 0 ? SeasonType.equinox : .solstice
  print("\(season.rawValue) \(seasonType).")
}

다음은 Swift 4.2에서 동일한 작업을 수행하는 방법입니다.

  1. Seansons는 열거형 케이스의 배열을 생성하기 위해 CaseIterable를 준수합니다.
  2. allCases를 사용하여 반복하고 각 season 이름과 타입을 출력합니다.

열거형 케이스 배열에 특정 케이스를 추가하는 옵션이 있습니다.

enum Months: CaseIterable {
  case january, february, march, april, may, june, july, august, september, october, november, december          
  
  static var allCases: [Months] {
    return [.june, .july, .august]
  }
}

여기에서 일년중에 해가 가장 많은 여름에 해당하는 월만 allCases에 추가합니다.


배열에 열거형에 unavailable가 포함되어 있는 경우에, 모든 available 케이스를 수동으로 추가해야합니다.

enum Days: CaseIterable {
  case monday, tuesday, wednesday, thursday, friday
  
  @available(*, unavailable)
  case saturday, sunday
  
  static var allCases: [Days] {
    return [.monday, .tuesday, .wednesday, .thursday, .friday]
  }
}

모든 플랫폼의 모든 버젼에서 .saturday와 .sunday 모두 unavailable로 표시되었기 때문에, allCases에 평일(weekdays)만 추가합니다.

여러분은 또한 열거형 케이스 배열에 관련된 값을 가진 케이스를 추가할수 있습니다.

enum BlogPost: CaseIterable {
  case article
  case tutorial(updated: Bool)
  
  static var allCases: [BlogPost] {
    return [.article, .tutorial(updated: true), .tutorial(updated: false)]
  }
}

예제에서, 모든 타입의 블로그 게이물을 웹사이트의 allCases에 추가합니다: 기사, 새로운 튜토리얼과 업데이트 된것들.

새로운 시퀀스 메소드(New Sequence Methods)

Swift 4.1은 특정 조건을 만족하는 특정 요소의 첫번째 인덱스 또는 첫번째 요소를 결정하는 Sequence메소드들이 정의되어 있습니다.

let ages = ["ten", "twelve", "thirteen", "nineteen", "eighteen", "seventeen", "fourteen",  "eighteen", 
            "fifteen", "sixteen", "eleven"]

if let firstTeen = ages.first(where: { $0.hasSuffix("teen") }), 
   let firstIndex = ages.index(where: { $0.hasSuffix("teen") }), 
   let firstMajorIndex = ages.index(of: "eighteen") {
  print("Teenager number \(firstIndex + 1) is \(firstTeen) years old.")
  print("Teenager number \(firstMajorIndex + 1) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

Swift 4.1에서 처리하는 방식은 ages에서 첫번째 10대를 찾기 위해 first(where:), 첫번째 십대의 인덱스를 찾기 위해 index(where:)와 18살인 첫번째 십대의 인덱스를 찾기 위해 index(of:)를 사용합니다.

Swift 4.2는 일관성을 위해 이러한 메소드의 이름을 변경합니다 SE-0204

if let firstTeen = ages.first(where: { $0.hasSuffix("teen") }), 
   let firstIndex = ages.firstIndex(where: { $0.hasSuffix("teen") }), 
   let firstMajorIndex = ages.firstIndex(of:  "eighteen") {
  print("Teenager number \(firstIndex + 1) is \(firstTeen) years old.")
  print("Teenager number \(firstMajorIndex + 1) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

first(where:)과 일관성 있게 index(where:)는 firstIndex(where:)으로, index(of:)는 firstIndex(of:)이 됩니다.

Swift 4.1은 주어진 것과 일치하는 특정 요소의 마지막 인덱스 또는 마지막 요소를 찾는 어떠한 Collection메소드도 정의하지 않았습니다. 다음은 4.1에서 처리하는 방법입니다.

// 1
let reversedAges = ages.reversed()

// 2
if let lastTeen = reversedAges.first(where: { $0.hasSuffix("teen") }), 
   let lastIndex = reversedAges.index(where: { $0.hasSuffix("teen") })?.base, 
   let lastMajorIndex = reversedAges.index(of: "eighteen")?.base {
  print("Teenager number \(lastIndex) is \(lastTeen) years old.")
  print("Teenager number \(lastMajorIndex) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

섹션들을 살펴봅니다.

  1. reversed()으로 ages의 뒤집어진 버젼을 생성합니다.
  2. reversedAges에서 마지막 십대의 나이를 결정하기 위해 first(where:)를 사용하며, 마지막 십대의 인덱스를 위해 index(where:)를 그리고 18살인 마지막 십대의 인덱스를 결정하기 위해 index(of:)를 사용합니다.

Swift 4.2는 위에서 아래로 내려가는 해당 Sequence 메소드를을 추가하였습니다.

if let lastTeen = ages.last(where: { $0.hasSuffix("teen") }), 
   let lastIndex = ages.lastIndex(where: { $0.hasSuffix("teen") }), 
   let lastMajorIndex = ages.lastIndex(of: "eighteen") {
  print("Teenager number \(lastIndex + 1) is \(lastTeen) years old.")
  print("Teenager number \(lastMajorIndex + 1) isn't a minor anymore.")
} else {
  print("No teenagers around here.")
}

ages에서 이전 요소와 특정 인덱스를 찾기 위해 last(where:), lastIndex(where:), lastIndex(of:)`를 사용할수 있습니다.

시퀀스 요소들 테스트하기(Testing Sequence Elements)

Swift 4.1에는 없는 간단한 로직은 Sequece의 모든 요소가 특정 조건을 만족하는지 확인하는 방법입니다. 언제나 자신만의 접근 방법을 만들수 있지만, 여기에서는 모든 요수가 짝수(even)인지 여부를 결정해야 합니다.

let values = [10, 8, 12, 20]
let allEven = !values.contains { $0 % 2 == 1 }

불편(kludgey)하지 않나요? Swift 4.2는 Sequence에 빠진 메소드를 추가하였습니다. SE-0207

let allEven = values.allSatisfy { $0 % 2 == 0 }

훨씬 낫습니다. 이렇게 하면 코드가 단순해지고 가독성이 향상됩니다.

조건부 적합성 업데이트(Conditional Conformance Updates)

Swift 4.2는 확장과 표준 라이브러리에 몇가지 조건부 적합성을 향상하여 추가하였습니다. SE-0143

확장에서 조건부 적합성(Conditional conformance in extensions)

Swift 4.1은 확장에서 Equatable에 대해 조건부 적합성을 합성할 수 없었습니다. Swift 4.1 예제를 보겠습니다.

// 1
struct Tutorial : Equatable {
  let title: String
  let author: String
}

// 2
struct Screencast<Tutorial> {
  let author: String
  let tutorial: Tutorial
}

// 3
extension Screencast: Equatable where Tutorial: Equatable {
  static func ==(lhs: Screencast, rhs: Screencast) -> Bool {
    return lhs.author == rhs.author && lhs.tutorial == rhs.tutorial
  }
}

// 4
let swift41Tutorial = Tutorial(title: "What's New in Swift 4.1?", author: "Cosmin Pupăză")
let swift42Tutorial = Tutorial(title: "What's New In Swift 4.2?", author: "Cosmin Pupăză")
let swift41Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift41Tutorial)
let swift42Screencast = Screencast(author: "Jessy Catterwaul", tutorial: swift42Tutorial)
let sameScreencast = swift41Screencast == swift42Screencast
  1. Tutorial가 Equatable를 준수하도록 만듭니다.
  2. 웹 사이트 제작자는 게시된 튜토리얼을 기반으로 screencast를 생성하므로, Screencast을 제네릭(generic)으로 만듭니다.
  3. Screencast가 Toutorial인 경우에는 Equatable를 준수하도록 screencast에 대해==(lhs:rhs:)를 구현합니다.
  4. 여러분이 선언한 조건부 적합성때문에, screencast를 직접 비교합니다.

Swift 4.2는 확장에 대한 Equtable조건부 적합성의 기본 구현을 추가합니다.

extension Screencast: Equatable where Tutorial: Equatable {}

이 기능은 확장에서 Hashable과 Codable 적합성도 마찬가지로 적용됩니다.

// 1
struct Tutorial: Hashable, Codable {
  let title: String
  let author: String
}

struct Screencast<Tutorial> {
  let author: String
  let tutorial: Tutorial
}

// 2
extension Screencast: Hashable where Tutorial: Hashable {}
extension Screencast: Codable where Tutorial: Codable {}

// 3
let screencastsSet: Set = [swift41Screencast, swift42Screencast]
let screencastsDictionary = [swift41Screencast: "Swift 4.1", swift42Screencast: "Swift 4.2"]

let screencasts = [swift41Screencast, swift42Screencast]
let encoder = JSONEncoder()
do {
  try encoder.encode(screencasts)
} catch {
  print("\(error)")
}

이 블록에서

  1. Tutorial은 Hashable과 Codable 모두 준수 합니다.
  2. Tutorial 인 경우에 Hashable과 Codable을 준수하도록 Screencast를 제한합니다.
  3. set과 dictionaries에 screencasts를 추가하고 인코딩 합니다.

조건부 적합성 실시간 조회(Conditional conformance runtime queries)

Swift 4.2는 조건부 적합성을 동적으로 조회(dynamic queries)하는 것을 구현합니다. 다음에 오는 코드에서 이러한 동작을 볼수 있습니다.

// 1
class Instrument {
  let brand: String
  
  init(brand: String = "") {
    self.brand = brand
  }
}

// 2
protocol Tuneable {
  func tune()
}

// 3
class Keyboard: Instrument, Tuneable {
  func tune() {
    print("\(brand) keyboard tuning.")
  }
}

// 4
extension Array: Tuneable where Element: Tuneable {
  func tune() {
    forEach { $0.tune() }
  }
}

// 5
let instrument = Instrument()
let keyboard = Keyboard(brand: "Roland")
let instruments = [instrument, keyboard]

// 6
if let keyboards = instruments as? Tuneable {
  keyboards.tune()
} else {
  print("Can't tune instrument.")
}

다음은 위에서 무엇을하는지 입니다.

  1. 특정 브랜드로 Instrument를 정의합니다.
  2. 튜닝할 수 있는 모든 악기에 Tuneable를 선언합니다.
  3. 키보드 표준 튜닝을 반환하기 위해 Keyboard에서 tune()를 재정의(override)합니다.
  4. Element인 경우에는Tuneable를 준수하도록 Array로 제한하기 위해 where를 사용합니다.
  5. instruments에 Instrument와 Keyboard를 추가합니다.
  6. instruments가 Tuneable를 구현을 확인하고 테스트가 성공하면 튜닝합니다. 예제에서, Instrument타입이 tuneable이 아니기 때문에.배열은 Tuneable로 형변환 할수 없습니다. 키보드 배열 두개를 만드는 경우, 테스트가 통과되고 키보드는 튜닝됩니다.

표준 라이브러리에서 Hasable 조건부 적합성 향상( hashable conditional conformance improvements in the standard library)

Swift 4.2 에서 옵셔널, 배열, 딕셔너리(dictionaries)와 범위(ranges)는 그것들의 요소들도 마찬가지로 Hashable일때, Hashable입니다.

struct Chord: Hashable {
  let name: String
  let description: String?
  let notes: [String]
  let signature: [String: [String]?]
  let frequency: CountableClosedRange<Int>
}

let cMajor = Chord(name: "C", description: "C major", notes: ["C", "E",  "G"], 
                   signature: ["sharp": nil,  "flat": nil], frequency: 432...446)
let aMinor = Chord(name: "Am", description: "A minor", notes: ["A", "C", "E"], 
                   signature: ["sharp": nil, "flat": nil], frequency: 440...446)
let chords: Set = [cMajor, aMinor]
let versions = [cMajor: "major", aMinor: "minor"]

cMajor과 aMinor을 chords와 versions에 추가하였습니다. String?, [String], [String: [String]?], CountableClosedRange<Int>가 Hashable이 아니기이기 때문에, 4.2 이전에는 불가능했습니다.

Hashable 향상(Hashable Improvements)

다음은 클래스에 대해 사용자정의 해시 함수(hash functiions)를 구현한 Swift 4.1 예제입니다.

class Country: Hashable {
  let name: String
  let capital: String
  
  init(name: String, capital: String) {
    self.name = name
    self.capital = capital
  }
  
  static func ==(lhs: Country, rhs: Country) -> Bool {
    return lhs.name == rhs.name && lhs.capital == rhs.capital
  }
  
  var hashValue: Int {
    return name.hashValue ^ capital.hashValue &* 16777619
  }
}

let france = Country(name: "France", capital: "Paris")
let germany = Country(name: "Germany", capital: "Berlin")
let countries: Set = [france, germany]
let countryGreetings = [france: "Bonjour", germany: "Guten Tag"]

그것들이 Hashable인 경우에 set과 dictionaries에 계속 추가할 수 있습니다. hasValue 구현이 이해하기 어렵고 신뢰할수 없는 소스 값에 대해 충분히 효율적이지 않습니다.

Swift 4.2는 범용적인 해쉬 함수를 정의하여 이를 수정하였습니다 SE-0206

class Country: Hashable {
  let name: String
  let capital: String
  
  init(name: String, capital: String) {
    self.name = name
    self.capital = capital
  }
  
  static func ==(lhs: Country, rhs: Country) -> Bool {
    return lhs.name == rhs.name && lhs.capital == rhs.capital
  }

  func hash(into hasher: inout Hasher) {
    hasher.combine(name)
    hasher.combine(capital)
  }
}

여기에서 Country에서 hashValue를 hash(into:)로 교체했습니다. 그 함수는 hasher에서 combine()를 사용하여 클래스 속성을 hasher로 제공합니다. 구현하기 쉽고 이전 버젼보다 성능이 향상됩니다.


컬렉션에서 요소 제거하기(Removing Elements From Collections)

Collection에서 특정 요소들을 모두 제거하는 경우가 있습니다. 다음은 Swift 4.1 에서 filter(_:)로 처리하는 방법입니다.

var greetings = ["Hello", "Hi", "Goodbye", "Bye"]
greetings = greetings.filter { $0.count <= 3 }

짧은 인사말만 반환하기 위해 greetings를 필터링합니다. 이것은 원래 배열에 영향을 주지 않기 때문에, greetings에 다시 할당해야 합니다.

SE-0197에서 Swift 4.2는 removeAll(_:)를 추가하였습니다.

greetings.removeAll { $0.count > 3 }

그 자리에서 제거가 됩니다. 다시 말하지만, 코드를 단순화하고 효율성을 향상 시켰습니다.

Boolean 상태 토클하기(Toggling Boolean States)

Boolean을 토글하기! Swift 4.1 에서 다음과 같이 사용하였습니다.

extension Bool {
  mutating func toggle() {
    self = !self
  }
}

var isOn = true
isOn.toggle()

Swift 4.2에서 Bool에 대해 toogle()를 추가하였습니다. SE-0199

새로운 컴파일러 지시문(New Compiler Directives)

Swift 4.2는 코드의 문제점을 알리는 컴파일러 지시문(Compiler directives)를 정의하였습니다. SE-0196

// 1
#warning("There are shorter implementations out there.")

let numbers = [1, 2, 3, 4, 5]
var sum = 0
for number in numbers {
  sum += number
}
print(sum)

// 2
#error("Please fill in your credentials.")

let username = ""
let password = ""
switch (username.filter { $0 != " " }, password.filter { $0 != " " }) {
  case ("", ""):
    print("Invalid username and password.")
  case ("", _):
    print("Invalid username.")
  case (_, ""):
    print("Invalid password.")
  case (_, _):
    print("Logged in succesfully.")
}   

다음은 동작 방법입니다.

  1. numbers에 요소를 추가하는 함수의 사용하는 것이 필수적인 것보다 짧다는 것을 기억하기 위해 #warning을 사용합니다.
  2. 다른 개발자에게 로그인하기 전에 username과 password를 입력하도록 #error를 사용합니다.

새로운 포인터 함수들(New Pointer Functions)

Swift 4.1 에서 withUnsafeBaytes(of:_:)와 withUnsafePointer(to:_:)는 가변(mutable) 변수에서만 작업할수 있었습니다.

let value = 10
var copy = value
withUnsafeBytes(of: &copy) { pointer in print(pointer.count) }
withUnsafePointer(to: &copy) { pointer in print(pointer.hashValue) }

두 함수가 모두 동작하돌 value의 copy를 만들어야 했습니다. Swift 4.2는 상수에 대해 이러한 함수를 오버로드 하므로 더 이상 값을 저장하지 않아도 됩니다. SE-0205

withUnsafeBytes(of: value) { pointer in print(pointer.count) }
withUnsafePointer(to: value) { pointer in print(pointer.hashValue) }

메모리 레이아웃 업데이트(Memory Layout Updates)

Swift 4.2는 저장된 속성의 메모리 레이아웃(memory layout) 검색하기 위해 key paths를 사용합니다. SE-0210. 다음은 작업 방법입니다.

// 1
struct Point {
  var x, y: Double
}

// 2
struct Circle {
  var center: Point
  var radius: Double
  
  var circumference: Double {
    return 2 * .pi * radius
  }
  
  var area: Double {
    return .pi * radius * radius
  }
}

// 3
if let xOffset = MemoryLayout.offset(of: \Circle.center.x), 
   let yOffset = MemoryLayout.offset(of: \Circle.center.y), 
   let radiusOffset = MemoryLayout.offset(of: \Circle.radius) {
  print("\(xOffset) \(yOffset) \(radiusOffset)")
} else {
  print("Nil offset values.")
}

// 4
if let circumferenceOffset = MemoryLayout.offset(of: \Circle.circumference), 
   let areaOffset = MemoryLayout.offset(of: \Circle.area) {
  print("\(circumferenceOffset) \(areaOffset)")
} else {
  print("Nil offset values.")
}

단계별 설명

  1. 수평과 수직 좌표 위치를 정의합니다.
  2. 원의 center, circumference, area, radius를 선언합니다.
  3. 원의 저장 프로퍼티 offsets를 가져오기 위해 key paths를 사용합니다.
  4. 원이 인라인으로 저장되지 않았으므로, 원의 계산 프로퍼티의 offsets에 대해 nil을 반환합니다.

모듈에서의 인라인 함수(Inline Functions in Modules)

Swift 4.1 에서, 자신의 모듈에서 인라인 함수(inline functions)를 선언할 수 없었습니다. View > Navigators > Show Project Navigator로 가서 Sources 우츨 클릭하고 New File를 선택합니다. FactorialKit.swift파일 이름을 바꾸고 다음에 오는 코드 블럭으로 내용을 교체합니다.

public class CustomFactorial {
  private let customDecrement: Bool
  
  public init(_ customDecrement: Bool = false) {
    self.customDecrement = customDecrement
  }
  
  private var randomDecrement: Int {
    return arc4random_uniform(2) == 0 ? 2 : 3
  }
  
  public func factorial(_ n: Int) -> Int {
    guard n > 1 else {
      return 1
    }
    let decrement = customDecrement ? randomDecrement : 1
    return n * factorial(n - decrement)
  }
}

팩토리얼(factorial) 구현의 사용자정의 버젼을 만들었습니다. playground로 돌아가서 아래쪽에 다음 코드를 추가하세요.

let standard = CustomFactorial()
standard.factorial(5)
let custom = CustomFactorial(true)
custom.factorial(5)

여기에서, 기본 팩토리얼(factorial)과 무작위로 모두 생성합니다. Swift 4.2에서 인라인될때 모듈간의(Cross-module) 함수는 더 효율적입니다. SE-0193FactorialKit.swift로 돌아가거 CustomFactorial을 다음으로 교체하세요.

public class CustomFactorial {
  @usableFromInline let customDecrement: Bool
  
  public init(_ customDecrement: Bool = false) {
    self.customDecrement = customDecrement
  }
  
  @usableFromInline var randomDecrement: Int {
    return Bool.random() ? 2 : 3
  }
  
  @inlinable public func factorial(_ n: Int) -> Int {
    guard n > 1 else {
      return 1
    }
    let decrement = customDecrement ? randomDecrement : 1
    return n * factorial(n - decrement)
  }
}

하는 일은 다음과 같습니다.

  1. customDecrement와 randomDecrement 모두 internal로 설정하고 인라인된 팩토리얼 구현에서 사용하므로 @usableFromInline를 표시합니다.
  2. factorial(_:)을 인라인으로 만들기 위해@inlinable을 표기합니다. 함수를public 로 선언했기 때문에 가능합니다.

기타 잡동사니(Miscellaneous Bits and Pieces)

Swift 4.2에서 변경된 몇가지에 대해서 알아야할 것은 다음과 같습니다.

Swift Package Manager Updates

Swift 4.2는 Swift Package Manager에 대해 몇가지 개선사항이 추가되었습니다.

패키지에 대해 Swift 언어 버젼 정의하기(Defining Swift language versions for packages)

Swift 4.1 Package.swift에서 swiftLanguageVersions는 [Int]로 정의 되어 있어서, 패키지의 주요 릴리즈만 선언할수 있습니다.

let package = Package(name: "Package", swiftLanguageVersions: [4])

Swift 4.2는 SwiftVersion 케이스에 마이너 버젼도 정의 할 수 있습니다. SE-0209

let package = Package(name: "Package", swiftLanguageVersions: [.v4_2])

.version(_:)을 사용하여 향후 출시할 릴리즈를 사용할 수 있습니다.

let package = Package(name: "Package", swiftLanguageVersions: [.version("5")])

패키지에 대한 로컬 종속성 선언하기(Declaring local dependencies for packages)

Swift 4.1에서, 저장소 링크(repository links)를 사용해서 패키지에 대한 종속성을 선언하였습니다. 패키지들을 서로 연결한 경우에 추가적으로 오버헤드(overhead)가 되어서, Swift 4.2는 이경우에 로컬 경로를 대신 사용합니다. SE-0201

패키지에 시스템 라이브러리 타겟 추가하기(Adding system library targets)

Swift 4.1에서 시스템 모듈 패키지(System-module packages)는 별도의 저장소가 필요했습니다. 이로 인해 패키지 매니져를 사용하기 더 어려워졌기에, Swift 4.2는 시스템 라이브러리 타겟(system library targets)으로 교체했습니다. SE-0208

암시적으로 래핑되지 않은 옵셔널 제거 하기(Removing Implicitly Unwrapped Optionals)

Swift 4.1에서, 중첩된 타입에서 암시적으로 래핑되지 않은 옵셔널(implicitly unwrapped optionals)을 사용할 수 있습니다.

let favoriteNumbers: [Int!] = [10, nil, 7, nil]
let favoriteSongs: [String: [String]!] = ["Cosmin": ["Nothing Else Matters", "Stairway to Heaven"], 
                                          "Oana": nil] 
let credentials: (usermame: String!, password: String!) = ("Cosmin", nil)

Swift 4.2는 배열, 딕셔너리, 튜플에서 래핑되지 않은 옵셔널들을 제거합니다. SE-0054

let favoriteNumbers: [Int?] = [10, nil, 7, nil]
let favoriteSongs: [String: [String]?] = ["Cosmin": ["Nothing Else Matters", "Stairway to Heaven"], 
                                          "Oana": nil] 
let credentials: (usermame: String?, password: String?) = ("Cosmin", nil)

여기에서 어디로 가야하나요?(Where to Go From Here?)

최종 playground를 Download Materials링크에서 다운로드 받을 수 있습니다.

Swift 4.2는 많은 Swift 4.1의 기능을 개선하고, 2019년 초 Swift 5 에서의 ABI 안정성을 위해 준비합니다.

이 버젼의 변경사항에 대한 자세한 내용은 공식 Swift 변경기록(Swift CHANGELOG) 또는 Swift 표준 라이브러리 비교(Swift standard library diffs) 에서 확인할 수 있습니다.

Swift 5에서의 어떻게 변하는지 보려면 Swift 진화 제안(Swift Evolution proposals)에서 확인할수 있습니다. 여기에서 현재 검토중인 제안에 대한 리뷰를 남길수 있고, 직접 제안서를 제출할 수도 있습니다.

Posted by 까칠코더