'Nested Generics'에 해당하는 글 1건

제목: What’s New in Swift 3.1?


멋진 소식: Xcode8.3과 스위프트3.1이 베타로 나왔다! 이 릴리즈는 오래 기다린 스위프트 패키지 매니저 기능들과 언어 자체의 증진을 담고있다.

Swift Evolution Process에 가깝게 팔로우하고 있지 않았다면, 이 글을 계속 읽어보자(이 글은 그런 여러분을 위한 글이다!).

이 글에서는, 스위프트3.1에서 여러분 코드에 주요하게 영향을 줄 수 있는 가장 중요한 변화들만 집어주겠다. 안으로 들어가보자! :]

시작하기
스위프트3.1은 스위프트 3.0과 소스-호환이 되므로, 이미 Xcode에서 여러분의 프로젝트를 Edit\Convert\To Current Swift 를 사용해서 스위프트 3.0으로 마이그래이션 했다면, 새 기능이 여러분의 코드를 망가뜨리지 않을 것이다. 그러나 애플은 Xcode 8.3에서 스위프트 2.3 지원을 멈췄다. 따라서 아직 스위프트 2.3에서 마이그레이션을 하지 않았다면 지금이 그 때이다!

아래 섹션에서는, [SE-0001] 같은 링크의 태그를 볼 수 있을 것이다. 이것들은 Swift Evolution 프로포절 번호이다. 각 프로퍼절에 링크를 넣었놨으니 각 특정 변경에대한 세부적인 사항들을 찾을 수 있을 것이다. 나는 우리가 이야기나는 기능들을 플레이그라운드에서 시도해보길 추천하며, 그렇게하여 여러분은 그 모든 변화를 더 잘 이해할 수 있다.

따라서 Xcode를 켜서, File\New\Playground...를 선택하고 플랫폼은 iOS를 선택한다. 여러분이 원하는 아무거나 호출하고, 원하는 아무거나 저장하자. 이 글을 읽는동안, 각 기능을 플레이그라운드에서 시도해보아라.

Note: 만약 스위프트 3.0의 짧고 간단한 하이라이트 리뷰가 필요하다면, What’s New in Swift 3를 확인해보자.

언어 개선
먼저, 숫자 타입의 실패할수있는 생성자, 새로운 시퀀스 함수등을 포함하여, 이번 릴리즈에서 언어 개선에대해 살펴보자.

실패할수있는 숫자 변환 생성자(Failable Numeric Conversion Initializers)
스위프트3.1은 모든 숫자 타입(Int, Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64, Float, Float80, Double)을위해 실패할수있는 생성자를 만들었다. 이것은 정보 손실없이 성공적으로 완료하거나 아니면 간단하게 nil을 반환한다 [SE-0080]

이 기능은 유용한데, 예를들어 세이프와 복구가능한 방법에서 외부 소스로부터 느슨한 타입 데이터 변환을 다룰때가 있다. 예를들어서, 한 Student JSON 배열을 어떻게 처리하는지보자.

class Student {
  let name: String
  let grade: Int
  
  init?(json: [String: Any]) {
    guard let name = json["name"] as? String,
          let gradeString = json["grade"] as? String,
          let gradeDouble = Double(gradeString),
          let grade = Int(exactly: gradeDouble)  // <-- 3.1 feature here
    else {
        return nil
    }
    self.name = name
    self.grade = grade
  }
}

func makeStudents(with data: Data) -> [Student] {
  guard let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments),
        let jsonArray = json as? [[String: Any]] else {
    return []
  }
  return jsonArray.flatMap(Student.init)
}

let rawStudents = "[{\"name\":\"Ray\", \"grade\":\"5.0\"}, {\"name\":\"Matt\", \"grade\":\"6\"},
                    {\"name\":\"Chris\", \"grade\":\"6.33\"}, {\"name\":\"Cosmin\", \"grade\":\"7\"}, 
                    {\"name\":\"Steven\", \"grade\":\"7.5\"}]"
let data = rawStudents.data(using: .utf8)!
let students = makeStudents(with: data)
dump(students) // [(name: "Ray", grade: 5), (name: "Matt", grade: 6), (name: "Cosmin", grade: 7)]

아래처럼 실패할수있는 생성자를 명시하여 Stuent 클래스 안에 Double에서 Intgrade 프로퍼티를 변환하는데 실패할수있는 생성자를 사용한다.

let grade = Int(exactly: gradeDouble)

gradeDouble6.33같은 분수 값이고, 이것은 실패할 것이다. 만약 6.0같은 정확한 Int로 표현할 수 있다면 이것은 성공할 것이다.

Note: 실패할수있는것 대신에 대안의 설계는 throwing 생성자를 사용하는 것이었다. 커뮤니티는 실패할수있는것이 더 낫고 더 인간환경공학적 설계라고 선택했다.

새로운 시퀀스 함수들
스위프트 3.1은 데이터 필터링을위한 표준 라이브러리의 Sequence 프로토콜의 두가지 함수를 추가했다. prefix(while:)drop(while:)이다. [SE-0045]

피보나치 무한 시퀀스를 생각해보자.

let fibonacci = sequence(state: (0, 1)) {
  (state: inout (Int, Int)) -> Int? in
  defer {state = (state.1, state.0 + state.1)}
  return state.0
}

스위프트 3.0에서 우리는 fibonacci sequence를 돌기위해 반복 카운터를 명세했었다.

// Swift 3.0
for number in fibonacci.prefix(10) {
  print(number)  // 0 1 1 2 3 5 8 13 21 34
}

스위프트 3.1은 주어진 두 값 사이에 시퀀스의 모든 엘리먼트를 뽑아내기위해 조건과함께 prefix(while:)drop(while:)을 사용할 수 있게 해준다.

// Swift 3.1
let interval = fibonacci.prefix(while: {$0 < 1000}).drop(while: {$0 < 100})
for element in interval {
  print(element) // 144 233 377 610 987
}

prefix(while:)은 특정 조건을 만족시키는 가장 긴 서브시퀀스를 반환한다. 시퀀스의 첫부분부터 시작해서 주어진 클로저가 false를 반환하는 첫번째 엘리먼트에서 멈춘다.

drop(while:)은 그 반대이다. 주어진 클로저에서 false를 반환하는 첫번째 엘리먼트에서 시작해서 스퀀스의 끝에서 멈춘다.

Note: 이 경우엔 트레일링 클로저 문법(trailing closure syntax)을 사용할 수 있을 것이다.
let interval = fibonacci.prefix{$0 < 1000}.drop{$0 < 100}

구체적인 제한 익스텐션(Concrete Constrained Extensions)
스위프트 3.1은 구체적인 타입 제한으로 제네릭 타입을 익스텐션할 수 있게 해준다. 이전에는, 프로토콜로 제한되어있어서 이런 타입을 익스텐션할 수 없었다. 예제를 살펴보자.

예를들어, 루비온레일즈는 사용자 입력을 확인하기위한 유용한 isBlank 메소드를 제공한다. 여기에 스위프트 3.0에서 String 데이터 타입 익스텐션에서 어떻게 계산된 프로프터(computed property)로 구현할 수 있는지 보자.

// Swift 3.0
extension String {
  var isBlank: Bool {
    return trimmingCharacters(in: .whitespaces).isEmpty
  }
}

let abc = " "
let def = "x"

abc.isBlank // true
def.isBlank // false
isBlank 계산된 프로퍼티가 옵셔널 문자열로 동작하기 원한다면, 스위프트 3.0에서 아래를 할 수있을것이다.
// Swift 3.0
protocol StringProvider {
  var string: String {get}
}

extension String: StringProvider {
  var string: String {
    return self
  }
}

extension Optional where Wrapped: StringProvider {
  var isBlank: Bool {
    return self?.string.isBlank ?? true
  }
}

let foo: String? = nil
let bar: String? = "  "
let baz: String? = "x"

foo.isBlank // true
bar.isBlank // true
baz.isBlank // false

이것은 String에 적용하기위해 StringProvider 프로토콜을 만들었다. isBlank 메소드를 추가하기위해 Wrapped 타입이 StringProvider일때 Optional을 확장하는데 사용한다.

스위프트 3.1은 프로토콜 대신에 아래처럼 구체적인 타입을 익스탠션하게 해준다.

// Swift 3.1
extension Optional where Wrapped == String {
  var isBlank: Bool {
    return self?.isBlank ?? true
  }
}

이것은 이전과 같은 기능을 하나, 아주 많이 코드를 줄일 수 있다!

감싸진 제네릭(Nested Generics)
스위프트 3.1은 제네릭으로 감싸진 타임을 섞을 수 있게 해준다. 예제로, 이것을 보자. raywenderlich.com에서 이끌고있는 어떤 팀이 블로그에 포스팅을 발행하고 싶을 때마다, 웹사이트의 높은 퀄리티 기준을 맞추기위해 이 발행에 참여한 개발자들의 팀을 넣는다.

class Team {
  enum TeamType {
    case swift
    case iOS
    case macOS
  }
  
  class BlogPost {
    enum BlogPostType {
      case tutorial
      case article
    }
    
    let title: T
    let type: BlogPostType
    let category: TeamType
    let publishDate: Date
    
    init(title: T, type: BlogPostType, category: TeamType, publishDate: Date) {
      self.title = title
      self.type = type
      self.category = category
      self.publishDate = publishDate
    }
  }
  
  let type: TeamType
  let author: T
  let teamLead: T
  let blogPost: BlogPost
  
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    self.type = type
    self.author = author
    self.teamLead = teamLead
    self.blogPost = blogPost
  }
}

Team이라는 외부 클래스안에 BlogPost라는 내부 클래스를 감싸고(nest) 두 클래스를 모두 제네릭 클래스로 만든다. 이것은 이 팀이 지금까지 웹사이트에 내가 발행한 튜토리얼과 글을 찾는 방법이다.
Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "Pattern Matching", type: .tutorial, 
     category: .swift, publishDate: Date()))

Team(type: .swift, author: "Cosmin Pupăză", teamLead: "Ray Fix", 
     blogPost: Team.BlogPost(title: "What's New in Swift 3.1?", type: .article, 
     category: .swift, publishDate: Date()))
그러나 실제로는, 이 경우 좀 더 심플하게 만들 수 있다. 감싸진 내부 타입은 제네릭 외부 타입을 사용하면 디폴트로 부모 타입을 상속한다. 따라서 이것을 정의할 필요가 없다.
class Team {
  // original code 
  
  class BlogPost {
    // original code
  }  
  
  // original code 
  let blogPost: BlogPost
  
  init(type: TeamType, author: T, teamLead: T, blogPost: BlogPost) {
    // original code   
  }
}
Note: 스위프트에서 제네릭에대해 더 배우고 싶다면, 우리가 최근에 업데이트한 getting started with Swift generics를 읽어보라.

스위프트 버전 사용 가능
여러분은 if swift(>= N) static construct를 사용해서 특정 스위프트 버전을 체크할 수 있다.
// Swift 3.0
#if swift(>=3.1)
  func intVersion(number: Double) -> Int? {
    return Int(exactly: number)
  }
#elseif swift(>=3.0)
  func intVersion(number: Double) -> Int {
    return Int(number)
  }
#endif
그러나 이런 방법은 스위프트 표준 라이브러리같은 곳에서 사용될때 중요한 결점을 가지고 있다. 이것은 각각 예전 언어 버전을 지원하기위해 컴파일하는 표준 라이브러리를 요구한다. 여러분이 스위프트 컴파일러를 예전의 호환성 모드로 실행시킬때(예를들어 스위프트 3.0 동작을 원한다고 말할때), 특정 호환 버전으로 컴파일된 표준 라이브러리 버전을 사용해야할것이다. 만약 3.1버전으로 컴파일한 것을 사용한다면, 간단하게 코드를 작성할 수 없을 것이다.

따라서 스위프트 3.1은 현재 플랫폼 버전 외에도 특정 스위프트 버전 숫자를 지원하는 @available 속성을 확장했다[SE-0141].
// Swift 3.1

@available(swift 3.1)
func intVersion(number: Double) -> Int? {
  return Int(exactly: number)
}

@available(swift, introduced: 3.0, obsoleted: 3.1)
func intVersion(number: Double) -> Int {
  return Int(number)
}
이 새로은 기능은 intVersion 메소드는 어떤 스위프트 버전하에 가능하다는 말과 같은 뜻이다. 그러나 표준 라이브러리같은 라이브러리들은 오직 한번만 컴파일되게 할 수 있다. 그러면 컴파일러는 간단하게 주어진 호환 버전을 선택하여 기능을 고른다.

Note : 스위프트에서 어베일러빌리티 속성(availibility attributes)에대해 너 배우고 싶으면, availability attributes in Swift라는 우리 튜토리얼을 확인해보자.

비-이스케이핑 클로저(Non-Escaping Closure)를 이스케이핑 클로저(Escaping Closures)로 변환하기
함수에 클로저 인자는 스위프트 3.0 디폴트에의해 non-escaping으로 만들어졌다[SE-0103]. 그러나 이 프로퍼절의 일부가 그때 구현되지 못했다. 스위프트 3.1에서는 withoutActuallyEscaping() 핼퍼 함수를 사용하여 임시로 비-이스케이핑 클로저를 이스케이핑 클로저로 변환할 수 있다.

왜 이런게 필요할까? 아마 자주 필요하진 않을것이지만, 제인에서 나온 아래 예제를 생각해보자.
func perform(_ f: () -> Void, simultaneouslyWith g: () -> Void,
             on queue: DispatchQueue) {
  withoutActuallyEscaping(f) { escapableF in     // 1
    withoutActuallyEscaping(g) { escapableG in
      queue.async(execute: escapableF)           // 2
      queue.async(execute: escapableG)     

      queue.sync(flags: .barrier) {}             // 3
    }                                            // 4
  }
}
이 함수는 동시에 두 클로저를 실행하고 두 클로저가 완료되면 반환한다.
  1. fg는 비-이스케이핑으로 들어와서 escapableFescapableG로 변환된다.
  2. async(excute:)은 요구 이스케이핑 클로저를 호출한다. 다행히도 이전 단계 덕에 이것을 가지고 있다.
  3. sync(flags: .barrier)를 실행하여, async(execute:)메소드가 완전히 완료되고 클로저가 다음에는 다시는 호출되지 않을거라 보장한다.
  4. 스코프는 escapableFecapableG 사용을 제한한다.
만약 임시의 이스케이핑 클로저를 어딘가에 넣어주려 한다면(즉, 실제로 이스케이프된 것들) 이것은 버그가 될 수 있다. 표준 라이브러리의 나중의 버전에서는 여러분이 이것을 호출하려할때, 이것을 감지하고 잡아낼 수 있게 될것이다.

스위프트 패키지 매니저 업데이트
아, 긴 시간동안 기다린 스위프트 패키지 매니저 업데이트가 도착했다!

수정가능한 패키지들(Editable Packages)
스위프트 3.1은 스위프트 패키지 매니저에 수정가능한 패키지라는 개념을 넣었다[editable-packages">SE-0082].

swift package edit 명령은 존재하는 패키지를 받아서 수정가능한 패키지로 변환한다. 수정가능한 패키지는 의존성 그래프에서 모든 일반적인 패키지의 발생을 대체한다. 패키지 매니저를 일반적인 해결된 패키지(canonical resolved package)로 돌리려면 --end-edit 명령을 사용하자.

버전 피닝(Version Pinning)
스위프트 3.1은 스위프트 패키지 매니저에 특정 버전으로 버전 피닝 패키지 의존성 개념을 추가했다[package-pinning">SE-0145]. pin이라는 명령은 하나 혹은 모든 의존성을 핀한다.
$ swift package pin --all      // pins all the dependencies
$ swift package pin Foo        // pins Foo at current resolved version
$ swift package pin Foo --version 1.2.3  // pins Foo at 1.2.3
unpin 명령으로 이전 패키지 버전으로 되돌릴 수 있다.
$ swift package unpin —all
$ swift package unpin Foo
이 패키지 매니저는 Package.pins에 각 패키지의 활성된 버전 핀 정보를 저장한다. 이 파일이 없다면, 패키지 매니저는 오토메틱 피닝(automatic pinning) 프로세스의 부분으로서 자동으로 패키지 메니패스트에서 특정 요구에따라 생성한다.

다른 것들
swift package reset 명령은 체크아웃된 의존성이 없거나 빌드 인공의 프레젠트가 없는 깨끗한 상태로 패키지를 되돌린다.

게다가, swift test --parallel 명령은 병렬로 테스트를 실행시킨다.

잡다한 것들
스위프트 3.1에는 어느 카테고리에도 맞지않는 짧은 소식들도 있다.

다중-리턴 함수(Multiple-Return Functions)
vforksetjmp같은 두번 리턴하는 C 함수는 이제 동작하지 않는다. 이것들은 관련된 방법으로 프로그램의 컨트롤 플로우를 바꿔버린다. 그래서 스위프트 커뮤니티는 이것들 사용을 금지시키고 이제 컴파일타임 에러를 내뱉도록 하기로 결정했다.

자동-링킹 끄기(Disable Auto-Linking)
스위프트 패키지 매니저는 C 언어 타겟을 위한 모듈 맵(module maps)의 자동-링킹 기능을 끌 수 있다.
// Swift 3.0
module MyCLib {
    header “foo.h"
    link “MyCLib"
    export *
}

// Swift 3.1
module MyCLib {
    header “foo.h”
    export *
}

여기서 어디로 가야할까?
스위프트 3.1은 스위프트 3.0 기능을 갈고 닦는 역할을 하는데, 올해(2017년 가을쯤) 스위프트 4.0 번들을 더 심사숙고하게 변경하기위한 준비이다. 이런것에는 제네릭, 정규식, 더 인간환경적인 문자열 설계 등에대한 거대한 개선을 포함한다.

여러분이 뭔가 새로운 것을 느끼고 있다면 Swift standard library diffs을 한번 보거나, 스위프트 변화에대한 모든 정보를 얻을 수 있는 공식 Swift CHANGELOG를 보자. 혹은 스위프트 4.0이 나올때까지 지켜보고 있어도 된다.

그리고 스위프트 4.0과 그 넘어에 무엇이 바뀔지 궁금하다면, 바로 지금 프로포절되고있는 것을 볼 수 있는 Swift Evolution proposals를 보면된다. 만일 여러분이 매우 열망하고있다면 현재 리뷰중인 프로포절에 피드백을 날리거나 여러분 스스로 프로포절을 할 수도 있다 ;].

지금까지 스위프트 3.1에서 여러분이 좋아하는점과  싫어하는점이 무엇인가? 이 포럼 아래 토론에서 알려달라!

이 블로그는 공부하고 공유하는 목적으로 운영되고 있습니다. 번역글에대한 피드백은 언제나 환영이며, 좋은글 추천도 함께 받고 있습니다. 피드백은 

으로 보내주시면 됩니다.



WRITTEN BY
tucan.dev
개인 iOS 개발, tucan9389

,