본문 바로가기

Swift 5.3에서는 뭐가 바뀌었을까?

기술적인 이야기/애플 플랫폼 개발 2020. 9. 28.
반응형

Xcode 12.0이 정식 릴리즈 되면서 이제 Swift 5.3도 현역이 되었습니다. 이미 많은 업데이트 내역을 정리한 글들이 있지만, 개인적으로 공부 삼아서 이번에도 늦었지만 정리해 보고자 합니다. 다만 개인적으로 내부 구현이 바뀌는 것은 딱히 관심이 없고 공부라는 의미도 있는 만큼 이 글은 문법적으로 변경되는 사항만을 정리합니다.

제안의 내용만으론 이해가 안 되는 내용도 있어서 여기저기 찾아보면서 정리했기 때문에 일부 틀린 내용이 있을 수도 있습니다.

여러 예외를 처리할 수 있는 catch 절

SE-0276 Multi-Pattern Catch Clauses 제안으로 구현된 기능입니다.

기존에는 예외 처리를 위한 do - catch 구문에서 catch는 하나의 예외만 잡을 수 있었습니다. 5.3부터는 여러 예외를 하나의 catch를 통해 잡을 수도 있습니다.

do {
  try doWhat()
}
catch MyBrainError.stupid,
      MyBrainError.idiot {
  print("Anyway, he is stupid")
}

위 예제에서 하나의 catch에서 여러 에러를 콤마를 이용해 명시하고 있는 모습을 볼 수 있습니다.

반응형

여러 개의 꼬리 클로저

SE-0279 Multiple Trailing Closures 제안으로 구현된 기능입니다.

꼬리 클로저, 정확히 Trailing Closure는 함수나 메서드의 마지막 매개변수로 클로저를 구현할 경우 매개변수 인터페이스를 생략할 수 있는 그걸 말합니다. 클로저 구현을 축약해서 할 수 있어서 매우 유용한 문법입니다.

기존의 경우 이런 꼬리 클로저는 마지막 매개변수에서만 쓸 수 있었습니다. 예를 들어 UIView에서 제공되는 한 animate 메서드의 경우를 봅시다.

class func animate(withDuration duration: TimeInterval,
                   animations: @escaping () -> Void,
                   completion: ((Bool) -> Void)? = nil)

animate 메서드의 원형에서 위와 같이 animations와 completion이라는 두 개의 클로저 매개변수를 볼 수 있습니다. 이 메서드를 호출할 때 5.3 이전 버전의 경우 아래처럼 구현이 가능했습니다.

UIView.animate(withDuration: 1.0, animations: {
  // animations
  self.view.alpha = 0
}) { _ in
  // completion (trailing closures)
  self.view.removeFromSuperview()
}

보다시피 마지막의 completion 매개변수만 꼬리 클로저 형태로 축약 구현하는 것이 가능했습니다.

Swift 5.3 부터는 아래처럼 단축 구현을 매개변수 위치에 상관없이 사용할 수 있습니다.

UIView.animate(withDuration: 0.3) {
  // animations (trailing closures)
  self.view.alpha = 0
} completion: { _ in
  // completion (trailing closures)
  self.view.removeFromSuperview()
}

꼬리 클로저는 코드 양을 줄일 수 있다는 점에서 좋기도 하고 읽기도 좀 더 편해지는 점이 있어서 유용합니다. 다만 예제 코드는 그 길이 자체를 그다지 줄이지는 못하는 것처럼 보이네요. [...]

728x90

enum 타입 비교 지원

SE-0266 Synthesized Comparable conformance for enum types 제안으로 구현된 기능입니다.

enum 타입의 경우 날값(raw value)을 가질 수 있으며 이를 이용한 정렬 또한 가능할 것입니다. 하지만 순차적으로 정의한 enum 타입의 값들을 굳이 귀찮게 날값을 지정하는 것은 비효율적인 행위죠.

Swift 5.3부터는 enum으로 정의한 타입에서 이런 값을 명시하지 않아도 비교가 가능하게 됩니다. 심지어 단순히 Comparable를 따르는 것만으로 가능해집니다. 거기다 비교가 가능해지니까 정렬도 가능해지죠.

enum Membership: Comparable {
  case premium(Int)
  case preferred
  case general
}

([.preferred, .premium(1), .general, .premium(0)] as [Membership]).sorted()

위 코드는 5.3부터는 오류 없이 빌드되고 실행됩니다. 결과는 아래와 같습니다.

[.premium(0), .premium(1), preferred, .general]

결과적으로 봤을 때 enum의 각 값은 순차적으로 크기가 메겨진다고 볼 수 있으며 따라서 기본적으로는 순차적으로 정렬이 됩니다. 그리고 같은 값일 경우 연관 값을 통해 줄 세우는 것도 알아서 해주는 것 같습니다.

약간 인간적인 면이 있어서 정감이 가는 업데이트 같습니다.

탈출한(Escaping) 클로저에서 self 생략 가능

SE-0269 Increase availability of implicit =self= in =@escaping= closures when reference cycles are unlikely to occur 제안으로 구현된 기능입니다.

저도 잘 모르고 있었는데, 언제부턴가 클로저에서 self 사용이 필수가 아니게 바뀌었더군요. 하지만 @escaping으로 표기된 클로저의 경우는 그 특수성 때문에 self 표기가 강제되고 있었습니다.

참고로 @escaping 클로저는 비동기 호출을 지원하는 클로저를 정의할 수 있는 키워드입니다. 예를 들어 특정 함수에 클로저가 매개변수로 전달된 경우 해당 클로저는 함수의 삶 내에서만 존재할 수 있습니다. 하지만 @escaping 클로저는 해당 함수가 종료되고 나서도 호출이 가능한 형태로 구현할 수 있는 매우 특수하면서도 유연한 기능을 제공하는 클로저라 볼 수 있습니다.

이제는 self를 납치(?)하는 방법으로 표기해서 self를 생략할 수 있게 됩니다.

class Foo {
  var x = 0
  func execute(_ work: @escaping () -> Void) {
    work()
  }

  func run() {
    execute { [self] in
      x += 1    // self 생략
    }
  }
}

당연하겠지만 self 납치를 어떻게 취급(?)할 건지에 대해선 명확하게 해야 나중에 메모리 릭 문제가 없는 코드를 짤 수 있겠지요.

다만 위의 경우는 class인지라 참조 문제가 크게 걸리지만, 만약 struct로 작성했다면 애초에 참조 문제가 발생할 이유가 없으므로 납치 방법을 명시하지 않아도 됩니다. 위의 예제를 써먹긴 힘들겠지만 SwiftUI 코드들에서 상당수 self 표기를 생략해도 되는 경우가 많을 것으로 유추됩니다.

자유로워진 진입점(Entrypoint) 설정

SE-0281 @main: Type-Based Program Entry Points 제안으로 구현된 기능입니다.

기존 Swift의 진입점(Entrypoint)은 함수나 메서드 등에 선언되는 것이 아니라 그냥 최상단에 적힌 코드가 동작하거나, UIKit 혹은 AppKit에서 사용하는 @UIApplicationMain 혹은 @NSApplicationMain이 명시된 클래스가 진입점으로 선택됩니다.

Swift 5.3 부터는 @main 키워드를 통해 프로그램 진입점(entry point)을 지정할 수 있게 됩니다.

@main
class SomeMainClass {
  static func main() {
    // entrypoint
  }
}

클래스나 구조체에 @main이 명시된 경우라면 static func main() 메서드가 실제 진입점이 됩니다.

이런 변화에도 Xcode 12.0에서 새로 생성한 프로젝트에서는 아직 @main 대신 NSApplicationMain 혹은 UIApplicationMain 키워드를 사용하고 있네요. 뭐... 언젠간 바뀌겠지요.

where 사용 가능한 곳 추가

SE-0267 where clauses on contextually generic declarations의 제안으로 추가된 기능으로 where 절을 제너릭이나 확장(extension) 메서드 등에서도 사용할 수 있게 됩니다.

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

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

어... 이게 원래 안 되었던 것이었나요? 개인적으로는 제너릭을 잘 사용하지 않아서 몰랐던 것 같습니다.

RangeSet

SE-0270 Add Collection Operations on Noncontiguous Elements 제안으로 구현된 기능으로 비연속적 범위를 여러개 가질 수 있는 RangeSet 타입이 추가됩니다.

var ranges = RangeSet([0..<5, 10..<15])

왜인지 모르겠지만 위 코드가 Xcode 12.0.1에서는 오류가 발생하네요. 뭔가의 착오가 있는 것인지 찾아보고 있습니다만 잘 모르겠네요.

어쨌거나 이와 함께 컬렉션(Collections)에 RangeSet 관련 API들이 추가됩니다. 자세한 것은 제안의 내용을 살펴봅시다.

Float16

SE-0277 Float16 제안으로 구현된 기능입니다. 이름처럼 16비트 부동소수점 타입이 추가되었습니다. 굳이 코드 예제는 필요 없겠네요.

바이트 스트림에서 뭔가를 해야 할 일이 없다면 굳이 쓸 일이 있을까 싶기도 하네요.

Swift Package Manager 개선

문법적인 변화는 아닙니다만, SPM(Swift Package Manager)에 여러 기능이 추가되었습니다.

특히 SE-0272의 바이너리 의존성 지원으로 인해 이전보다 SPM의 사용처가 더 늘어나게 될 것 같습니다. 기존에는 소스에서 빌드하는 패키지만 SPM으로 관리할 수 있었지만 그래서 상업용 라이브러리 등을 사용하기 좀 힘든 부분이 존재하긴 했지만 그 문턱이 이제는 사라지는 것이겠네요.

개인적으론 SPM이 빨리 제1의 패키지 매니저가 되길 바라고 있습니다. 변화가 좀 더딘 느낌이 없지는 않지만 바뀌고 있다는 점에선 안도가 느껴지네요.

728x90
반응형

댓글