[ SWIFT ] Swift 4.2 Features

Swift 4.2는 Swift 5의 ABI 안정성을 위한 중간 단계로 설계됨. ABI 안정성은 다른 Swift버젼의 라이브러리들 간에 호환될 수 있는 것을 얘기한다.

Language Improvements

Generating Random Numbers

Random number생성 API에 Foundation 라이브러리 기반의 arc4random_uniform 대신 standard library에 random(in:), random(), randomElement() API 추가됨.

Swift 4.1

let digit = Int(arc4random_uniform(10))

Swift 4.2

// 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()

배열에서 random 값 추출

Swift 4.1

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.2

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

배열에 shuffle 기능 추가

Swift 4.1

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

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

Swift 4.2

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

Dynamic Member Lookup

@dynamicMemberLookup 키워드를 통해 dynamic member lookup 기능 제공.

Swift 4.1

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"

Swift 4.2

// 1
@dynamicMemberLookup //Swift 4.2에서 Dynamic Memeber Lookup을 위한 키워드
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] ?? ""
    }
  }
}


// 3
// Dynamic Memeber Lookup
me.info   // "Cosmin is 32 years old." 
me.title  // "Author"

Dynamic Memeber Lookup 적용 절차

  • class 에 @dynamicMemberLookup 지시어 적용
  • subscript(dynamicMember:) 메소드 구현
  • subscript를 dot syntax를 통해 호출

Derived classes에서 dynamic Member Lookup 사용

@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."

Dynamic Member Lookup는 부모클래스가 구현되어 있으면 자식에서도 동일하게 사용 가능함.

Dynamic Member Lookup의 Protocol로의 확장

// 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) //digit은 임의의 key. 
let noRandomDigit = String(number).filter { String($0) != randomDigit }

protocol에 dynamic member look을 구현함으로 인스턴스 변수 number에 dot syntax를 통해 통해 임의의 숫자를 얻을 수 있다. digit은 임의의 key이며 digit외에 다른 임의의 key가 올 수 있다.

Enumeration Cases Collections

Swift4.1 까지는 enum타입에의 모든 케이스에 대해 접근하기 위해선 케이스별로 배열로 만들어야 했지만 Swift 4.2에서 allCases property제공으로 문법이 간결해 졌다.

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

Swift4.2

// 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).")
}

New Sequence Methods

first(where:)와의 일관성을 맞추기 위해 index(where:), index(of:) 네이밍 변경

  • index(where:) => firstIndex(where:)
  • index(of:) => firstIndex(of:)

Swift 4.1에서는 Sequence를 역순으로 정렬 후 first(where:)를 호출하는 방식 사용하였으나 4.2부턴 Sequence를 뒤에서 부터 찾기 위한 간편한 API 추가됨.

  • last(where:)
  • lastIndex(where:)
  • lastIndex(of:)

Testing Sequence Elements

Swift4.1에선 Seqeunce의 모든 요소가 특정 조건을 만족하는지 확인하기 위한 메소드가 존재하지 않았기 때문 contains를 이용하였음. 4.2부턴 allSatisfy 메소드 지원을 통해 코드 가독성이 향상되었다. 샘플은 Sequence모든 요소가 짝수인지를 채크하는 예이다.

Swift4.1

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

Swift4.2

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

Conditional Conformance Updates

Swift4.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

Swift4.2

extension Screencast: Equatable where Tutorial: Equatable {}

Hashable Improvements

  • Swift 4.2에서 Hashable 프로토콜에 hashValue 대신에 간편히 해쉬 함수 구현을 위한 hash(into:) 제공

Swift4.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"]

  • hashValue의 구현이 이해하기 어렵고 충분히 효과적인 해쉬값인지 신뢰할 수 없음.

Swift4.2

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)
  }
}
  • hash(into:)의 hasher 제공으로 해쉬값을 이전보다 효과적인고 쉽게 구현하게 됨

Removing Elements From Collections

Sequecence에서 특정 요소 제거 위해 removeAll 추가

Swift4.1

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

Swift4.2

greetings.removeAll { $0.count > 3 }

Toggling Boolean States

Bool 타입에 toggle 메소드 추가됨

Swift4.1

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

var isOn = true
isOn.toggle()

Swift4.2

var isOn:Bool = true
isOn.toggle()

New Compiler Directives

  • #warning 컴파일 시 경고 노출
  • #error** 컴파일 시 에러 발생

Swift4.2

// 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.")
}     

New Pointer Functions

  • Swift4.1 withUnsafeBytes(of:_:) , withUnsafePointer(to:_:) 는 mutable variables에 대해서 동작하였음.
  • Swift4.2에서 immutable variables에 대해서도 동작

Swift4.1

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

Swift4.2

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

Memory Layout Updates

  • MemoryLayout의 offset 메소드가 추가 되었다.
  • offset 메소드는 keypath를 통해 stored, computed property에 접근할 수 있다.
  • offset을 통해 Stored property 각 사이즈가 메모리상에 위치를 찾을 수 있고 computed property에 대해선 nil를 반환한다.

Swift4.2

// 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.")
}

Inline Functions in Modules

  • Swift 4.2에서 inlie 함수 선언이 가능해짐
    • inline함수는 public으로 지정하며 지시어 @inlinable를 붙여줌.
    • inline함수 내부에서 사용하는 property는 internal로 설정하며 지시어 @usableFromInline 추가
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)
  }
}

Miscellaneous Bits and Pieces

Swift Package Manager Updates

  • Defining Swift language versions for packages
  • Declaring local dependencies for packages
  • Adding system library targets to packages

Removing Implicitly Unwrapped Optionals

  • Swift 4.1에선 nested type에서 implicitly unwrapped optionals 가 가능했으나 4.2에서 해당 기능 제거

Swift4.1

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)

Swift4.2

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)

Summary

  • Swift 4.2는 Swift5의 ABI안정성을 위한 중간 단계로 설계됨.
  • random(in:), random() API 가 Int, Double, CGFloat, Bool에 추가 됨.
  • Dynamic Member Lookup 기능추가됨.
  • enum type에 대해 allCase 접근자 추가됨.
  • Sequence API renaming, function added. firstIndex(where:), firstIndex(of:), last(where:), lastIndex(where:), lastIndex(of:)
  • Hashable protocol에 hasher 제공으로 hash값을 효과적으로 가독성있게 구현
  • Sequence에 특정 조건에 맞는 요소 제거 메소드 removeAll 추가
  • Bool 에 toggle메소드 추가
  • new compiler directive -> #warning, #error
  • 가독성을 위해 Sequence.contains => Sequence.allSatisfy 변경.
  • memory layout에 keypath로 접근할 수 있는 offset 함수 추가
  • 인라인 함수 지원
  • Nested type에서 암묵적 강제해제 타입 제거

참고사이트 : https://www.raywenderlich.com/5357-what-s-new-in-swift-4-2


© 2017. All rights reserved.

Powered by Hydejack v7.5.0