- 
                Notifications
    You must be signed in to change notification settings 
- Fork 0
Generics
- 
대부분의 현대 언어와 마찬가지로 스위프트는 제네릭 프로그래밍으로 모든 그룹화 할 수 있는 많은 기능 제공. 
- 
제네릭 코드를 사용하면 정의한 제약 조건과 일치하는 모든 타입에서 작동 할 수 있는 재사용 가능한 함수 및 데이터 타입을 작성 가능. 
- 
func identity<A>(input: A) -> A는 자리 표시자A로 식별되는 모든 타입에서 작동하는 함수를 정의.
- 
제네릭 프로토콜은 연관된 타입을 통해 특정 구현을 추상화하고 IteratorProtocol은 제네릭 프로토콜이며 생성하는 요소 타입에 대해 일반적?
- 
제네릭 프로그래밍의 목표는 알고리즘 또는 데이터 구조에 필요한 필수 인터페이스를 표현하는 것. 
- 
Built-in Collections장에서 작성한last(where :)메소드는Array의extension으로 쓰는 것이 확실한 선택이지만,
- 
실제 Array에는last(where :)가 필요 없는 많은 기능을 제공.
- 
필수 인터페이스가 무엇인지, 즉 원하는 기능을 구현하는 데 필요한 최소한의 기능 집합을 스스로에게 물어봄으로써 더 광범위한 타입의 기능을 사용할 수 있도록 함. 
- 
last(where :)는 단 하나의 요구 사항. 역순으로 요소 시퀀스를 순회하는 것이기 때문에,Sequence의extension이 이 알고리즘의 올바른 위치.
- 
이 장에서는 제네릭 코드를 작성하는 방법을 살펴 보고, 제네릭과 밀접하게 관련되어 있으므로 Overloading에 대해 먼저 설명.
- 
우리는 알고리즘의 여러 구현을 제공하기 위해 제네릭 프로그래밍을 사용하는데 각각 다른 가정 집합을 사용. 
- 
또한 Collections을 위한 일반 알고리즘을 작성할 때 발생할 수 있는 몇 가지 공통적인 어려움에 대해서도 설명.
- 
그런 다음 제네릭 데이터 타입을 사용하여 코드를 리팩터링하여 테스트하고 유연하게 만드는 방법. 
- 
마지막으로 컴파일러가 제네릭 코드를 처리하는 방법과 자체 코드에 대해 최상의 성능을 얻기 위해 수행할 수있는 방법을 알아봄. 
- 오버로드된 함수, 즉 이름은 같지만 인수 또는 반환 형식이 다른 여러 함수는 그 자체로는 제네릭 함수가 아님.
- 하지만 제네릭과 마찬가지로 여러 타입의 인터페이스를 하나의 인터페이스로 사용 가능.
- 지수화를 수행하고 Double및Float인수에 별도의 오버로드를 제공하려면raise(_: to:)라는 함수 정의하고 타입에 따라 컴파일러에서 올바른 오버로드 선택.
func raise(_ base: Double, to exponent: Double) -> Double {
  return pow(base, exponent)
}
func raise(_ base: Float, to exponent: Float) -> Float {
  return powf(base, exponent)
}
let double = raise(2.0, to: 3.0) // 8.0
type(of: double) // Double
let float: Float = raise(2.0, to: 3.0) // 8.0
type(of: float) // Float- 구현을 위해 Swift의 Darwin 모듈(또는 Linux의 Glibc)에서 선언한 pow및powf함수 사용.
- Swift는 기능이 일반인지 여부와 어떤 종류의 타입이 전달되는지에 따라 선택하는 오버로드된 함수에 대한 복잡한 규칙 집합을 가짐.
- 규칙이 너무 길어서 요약하면 가장 구체적인 것을 선택하라.
- 즉, 제네릭 이외의 함수는 일반적인 함수보다 많이 선택됨.
- UIView의 특정 특성을 기록하는 다음 함수의 경우 뷰의 클래스 이름과 프레임을 기록하는 UIView와 레이블의 텍스트를 출력하는 UILabel에 대한 특정 오버로드를 제공.
func log<View: UIView>(_ view: View) {
  print("It's a \(type(of: view)), frame: \(view.frame)")
}
func log(_ view: UILabel) {
  let text = view.text ?? "(empty)"
  print("It's a label, text: \(text)")
}- UILabel을 전달하면 UILabel 오버로드가 호출되는 반면 다른 뷰를 전달하면 일반 오버로드가 사용됨.
let label = UILabel(frame: CGRect(x: 20, y: 20, width: 200, height: 32))
label.text = "Password"
log(label) // It's a label, text: Password
let button = UIButton(frame: CGRect(x: 0, y: 0, width: 100, height: 50))
log(button) // It's a UIButton, frame: (0.0, 0.0, 100.0, 50.0)- 오버로드는 컴파일 타임에 정적으로 해결.
- 컴파일러가 오버로드를 결정할 때 런타임에 값의 동적 타입이 아니라 관련된 변수의 정적 타입을 호출하도록 결정한다는 것을 의미.
- 따라서 레이블과 버튼을 배열에 넣은 다음 배열의 요소에 대한 루프에서 log 함수를 호출하면 두 뷰 모두에 대해 로그의 일반적인 오버로드가 사용됨.
let views = [label, button] // Type of views is [UIView]
for view in views {
  log(view)
}
/*
It's a UILabel, frame: (20.0, 20.0, 200.0, 32.0)
It's a UIButton, frame: (0.0, 0.0, 100.0, 50.0)
*/- 이는 뷰 변수의 정적 타입이 UIView이기 때문.
- 동적 타입인 UILabel이 런타임에 다른 오버로드를 발생시키는 것은 중요하지 않음.
- 런타임 다형성이 필요하다면, 즉 변수의 타입이 아닌 변수가 가리키는 점을 기준으로 함수를 선택하려면 함수가 아닌 메소드를 사용해야 함.
- 즉, UIView 및 UILabel에서 log 메소드를 정의.
- 컴파일러는 오버로드된 연산자의 해결과 관련하여 몇 가지 놀라운 동작을 나타냄.
- Matt Gallagher가 지적했듯이,
- 형식 검사기는 제네릭 버전이 더 나은 선택일 때도 (제네릭 함수에 대해 말하면 선택되는) 제네릭변형보다비 제네릭오버로드를 항상 선호.
- 위의 지수 예제로 돌아가서 동일한 연산에 **라는 이름의 사용자 정의 연산자를 정의.
// Exponentiation has higher precedence than multiplication
precedencegroup ExponentiationPrecedence {
  associativity: left
  higherThan: MultiplicationPrecedence
}
infix operator **: ExponentiationPrecedence
func **(lhs: Double, rhs: Double) -> Double {
  return pow(lhs, rhs)
}
func **(lhs: Float, rhs: Float) -> Float {
  return powf(lhs, rhs)
}
2.0 ** 3.0 // 8.0- 위의 코드는 이전 섹션에서 구현한 raise함수와 동일.
- 다음에 정수에 또 다른 오버로드를 추가.
- 모든 정수형에 대해 지수 연산을 사용하기를 원하므로 SignedInteger를 준수하는 모든 타입에 대해 일반적인 오버로드를 정의.
- 여기에는 UnsignedInteger에 대한 또 다른 오버로드가 필요.
func **<I: SignedInteger>(lhs: I, rhs: I) -> I {
  // Cast to IntMax, use Double overload to compute result,
  // then numericCast to return type.
  let result = Double(lhs.toIntMax()) ** Double(rhs.toIntMax())
  return numericCast(IntMax(result))
}- 이것은 정상적으로 작동하는 것처럼 보이지만 두 개의 정수 리터럴을 사용하여 **를 호출하면 컴파일러**연산자의 모호한 사용에 대해 불평.
2 ** 3 // Error: Ambiguous use of operator '**'- 이 문제가 발생하는 이유에 대한 설명은 이 섹션의 시작 부분에서 언급한 내용으로 다시 돌아감.
- 오버로드된 연산자를 해결할 때 형식 검사기는 항상 제네릭 오버로드에 비해비 제네릭을 선호함.
- 분명히 컴파일러는 정수에 대한 일반적인 오버로드를 무시하면서 Double에 대한 오버로드 또는 Float에 대한 오버로드를 호출해야하는지 여부를 결정할 수 없어서 오류 발생.
- Double에 대한 오버로드 또는 Float에 대한 오버로드 둘 다 두 개의 정수 리터럴 인수에 대해 똑같이 유효한 선택이 됨.
- 둘 중에 올바른 오버로드를 선택하도록 컴파일러에 확신시키려면 적어도 하나의 인수를 정수 타입으로 캐스트하거나 명시적인 결과 타입을 제공.
let intResult: Int = 2 ** 3 // 8- 컴파일러는 연산자에 대해서만 이 방식으로 동작.
- 
SignedInteger의raise함수의 일반적인 오버로드는 모호함을 주지 않고 잘 작동.
- 이러한 불일치의 원인은 성능에 영향을 줌.
- 스위프트 팀은 유형 검사기가 이 단순하지만 때때로 연산자에 대한 잘못된 오버로드 해결 모델의 사용을 보증하기에 충분히 중요하게 다루는 복잡성의 감소를 고려?
- 동일한 연산이 일반 매개 변수에 대해 서로 다른 제약 조건을 필요로 하는 여러 알고리즘으로 표현될 수 있는 경우 일반적으로 범용 코드와 함께 오버로드가 발생.
- 한 배열의 모든 항목이 다른 배열에도 포함되어 있는지, 첫 번째 배열이 두 번째 배열의 하위 집합인지 여부를 확인하고자 하는 경우(요소 순서가 중요하지 않음),
- 표준 라이브러리는 이 동작을 하는 isSubset(of :)라는 메소드를 제공하지만Set과 같은SetAlgebra프로토콜을 따르는 타입에 대해서만 제공.
- 더 넓은 범위의 타입에 대해 작동하는 isSubset(of :)을 쓰고 싶다면, 다음과 같이 보일 것.
extension Sequence where Iterator.Element: Equatable {
  /// Returns true if all elements in `self` are also in `other`.
  func isSubset(of other: [Iterator.Element]) -> Bool {
    for element in self {
      guard other.contains(element) else {
        return false
      }
    }
    return true
  }
}
let oneToThree = [1,2,3]
let fiveToOne = [5,4,3,2,1]
oneToThree.isSubset(of: fiveToOne) // true- 
isSubset는 시퀀스의 요소가Equatable인Sequence프로토콜의extension으로 정의됨.
- 
검사할 동일한 타입의 배열을 사용하며 받는 쪽의 모든 단일 요소가 인수 배열의 멤버이기도 하면 true를 반환.
- 
이 메소드는 요소가 무엇인지는 신경 쓰지 않음. 
- 
Equatable에 부합되는 한 동등한 타입만contains와 함께 사용할 수 있기 때문.
- 
요소 타입은 Int,String또는Equatable에 부합되는 사용자 정의 클래스를 사용할 수 있음.
- 
이 버전의 isSubset은 큰 단점은 바로 성능.
- 
알고리즘의 성능 특성은 O(n * m).
- 
여기서 n과m은 두 배열의 요소 수.
- 
즉, 입력 크기가 커짐에 따라 함수가 실행되는 최악의 경우 시간이 2차원적으로 증가. 
- 
이는 contains가 배열에서 선형 시간(예:O(m))으로 실행되기 때문.
- 
소스 시퀀스의 내용을 반복하여 해당 요소와 일치하는지 확인하는데 알고리즘 호출은 다른 루프 내부에 포함. 
- 
받는 쪽의 요소 위에 있는 루프. 
- 
비슷한 방식으로 선형 시간으로 실행. 
- 
O(n)루프 내에서O(m)루프를 실행하면 결국O(n * m).
- 
작은 입력에 대해서는 문제가되지 않지만, 수천 또는 수백만 개의 요소가 포함된 배열로 호출하면 you'll be sorry.
- 
시퀀스의 요소 타입에 대한 제약 조건을 강화하여 보다 우수한 버전을 작성. 
- 
요소가 Hashable을 준수하도록 요구하는 경우,Set가 일정 시간에 조회를 수행한다는 사실을 이용하여 다른 배열을Set으로 변환.
extension Sequence where Iterator.Element: Hashable {
  /// Returns true iff all elements in `self` are also in `other`.
  func isSubset(of other: [Iterator.Element]) -> Bool {
    let otherSet = Set(other)
    for element in self {
      guard otherSet.contains(element) else {
      return false
      }
    }
    return true
  }
}- 
contains체크가O(1)시간(해시의 균등 분포를 가정 할 때)을 가지고,for루프 전체는 이제O(n).
- 
필요한 시간은 받는 쪽의 요소 수에 따라 선형적. 
- 
다른 컬렉션을 Set으로 변환하는 데 드는 추가 비용은O(m)이지만 루프 외부에서 한 번만 발생.
- 
따라서 총 비용 O(n + m)은Equatable버전의O(n * m)보다 훨씬 우수함.
- 
두 배열의 요소가 모두 1,000인 경우 2,000에서 100만 반복 사이의 차이. 
- 
이제 알고리즘의 두 가지 버전이 있는데 하나는 더 빠르며 다른 하나는 더 넓은 타입의 타입으로 작동. 
- 
좋은 소식은 이러한 옵션 중 하나를 선택하지 않아도 된다는 것. 
- 
isSubset의 두 오버로드를 구현하고 컴파일러가 인수 타입에 따라 가장 적합한 것을 선택하도록 할 수 있음.
- 
Swift는 오버로드에 대해 매우 유연함. 
- 
이 예제에서 볼 수 있듯이 입력 타입이나 리턴 타입뿐만 아니라 일반적인 자리 표시자에 대한 다른 제한 조건에 따라 오버로드. 
- 
타입 검사기가 찾을 수 있는 가장 구체적인 오버로드를 선택한다는 일반적인 규칙이 여기에도 적용. 
- 
isSubset의 두 가지 버전 모두 제네릭이므로 비 제네릭이 제네릭보다 선호되는 규칙은 도움이 안됨.
- 
하지만 Hashable이Equatable을 확장하기 때문에Hashable요소가 필요한 버전은 더 구체적이므로 더 많은 제약이 부과됨.
- 
이러한 제약이 아마도 isSubset의 경우와 같이 알고리즘을 보다 효율적으로 만들 수 있다고 가정하면 더 구체적인 함수가 더 나은 선택일 수 있음.
- 
isSubset을 좀 더 일반적으로 만들 수 있는 또 다른 방법.
- 
지금까지 검사 대상 요소 배열만 취했지만 Array는 명확한 타입.
- 
isSubset은 타입을 특정 지을 필요는 없음.
- 
두 버전의 함수 호출은 contains와Hashable버전의Set.init.
- 
두 경우 모두 함수는 Sequence프로토콜을 준수하는 입력 타입만 필요.
extension Sequence where Iterator.Element: Equatable {
  /// Returns a Boolean value indicating whether the sequence contains the
  /// given element.
  func contains(_ element: Iterator.Element) -> Bool
}
struct Set<Element: Hashable>:
  SetAlgebra, Hashable, Collection, ExpressibleByArrayLiteral
{
  /// Creates a new set from a  nite sequence of items.
  init<Source: Sequence>(_ sequence: Source)
    where Source.Iterator.Element == Element
}
- 이 점을 감안할 때, isSubset의 유일한 요구는 other가Sequence를 준수하는 어떤 유형이어야 한다는 것.
- 더 중요한 것은 두 시퀀스 타입(self 및 other)이 동일할 필요는 없다는 것.
- 그들은 단지 같은 원소의 시퀀스일 필요가 있음.
- 여기 Hashable버전은 두 가지 종류의 시퀀스에서 작동하도록 다시 작성.
extension Sequence where Iterator.Element: Hashable {
  /// Returns true iff all elements in `self` are also in `other`.
  func isSubset<S: Sequence>(of other: S) -> Bool
    where S.Iterator.Element == Iterator.Element
  {
    let otherSet = Set(other)
    for element in self {
      guard otherSet.contains(element) else {
        return false
      }
    }
    return true
  }
}- 이제 두 시퀀스가 같은 타입일 필요가 없으므로 훨씬 더 많은 가능성이 열림.
- 예를 들어, Countable범위의 숫자를 전달하여 확인.
[5,4,3].isSubset(of: 1...10) // true- 요소가 동등해야 하는 버전에도 비슷한 변경 가능.
extension Sequence where Iterator.Element: Equatable {
  func isSubset<S: Sequence>(of other: S) -> Bool
    where S.Iterator.Element == Iterator.Element
  {
    for element in self {
      guard other.contains(element) else {
        return false
      }
    }
    return true
  }
}- 
isSubset메서드는 여전히 일반적이지는 않음.
- 요소가 동일하지 않은 시퀀스는 어떻게 될까?
- 예를 들어 배열은 Equatable이아니며 요소로 사용하면 현재 구현에서 작동하지 않음.
- 배열에는 다음과 같이 정의 된 ==연산자가 있음.
/// Returns true if these arrays contain the same elements.
func ==<Element: Equatable>(lhs: [Element], rhs: [Element]) -> Bool- 하지만 그건 당신이 isSubset과 함께 사용할 수 있다는 것을 의미하지는 않음.
// Error: Type of expression is ambiguous without more context
[[1,2]].isSubset(of: [[1,2], [3,4]])- 
이것은 Array가Equatable을 따르지 않기 때문.
- 
배열에 포함된 타입 자체가 동등하지 않을 수 있기 때문에 불가능. 
- 
Swift는 현재 조건부 프로토콜 준수에 대한 지원이 부족. 
- 
즉, 특정 제약 조건이 충족될 때 Array(또는Sequence)가 프로토콜을 준수한다는 아이디어를 표현하는 기능.
- 
예: Iterator.Element: Equatable.
- 
따라서 배열은 포함된 타입이 Equatable하지만 프로토콜을 준수 할 수 없는 경우에 대해==구현을 제공.
- 
그렇다면 우리는 isSubset을non-equatable types으로 어떻게 작동시킬 수 있을까?
- 
우리는 equality을 결정하는 함수를 제공하도록 요구함으로써equality가 호출자에게 무엇을 의미 하는지를 제어함으로써 이를 수행?
- 
예를 들어 표준 라이브러리는 다음을 포함하는 contains의 두 번째 버전을 제공.
extension Sequence {
  /// Returns a Boolean value indicating whether the sequence contains an
  /// element that satisfies the given predicate.
  func contains(where predicate: (Iterator.Element) throws -> Bool)
    rethrows -> Bool
}- 즉, 시퀀스의 요소를 취하여 어떤 검사를 수행하는 함수를 취함.
- 검사가 true를 반환하면true를 반환하는 각 요소에 대한 검사를 실행.
- 이 버전의 contains는 훨씬 강력합니다.
- 예를 들어 시퀀스 내에서 조건을 확인하는 데 사용 가능.
let isEven = { $0 % 2 == 0 }
(0..<5).contains(where: isEven) // true
[1, 3, 99].contains(where: isEven) // false- 이 유연한 버전의 contains를 활용하여isSubset과 비슷한 유연한 버전을 작성 가능.
extension Sequence {
  func isSubset<S: Sequence>(of other: S,
    by areEquivalent: (Iterator.Element, S.Iterator.Element) -> Bool)
    -> Bool
  {
    for element in self {
      guard other.contains(where: { areEquivalent(element, $0) }) else {
        return false
      }
    }
    return true
  }
}- 이제 isSubset을 배열의 배열과 함께 사용:==를 사용하여 배열을 비교하는 클로저 표현식을 제공:
[[1,2]].isSubset(of: [[1,2], [3,4]]) { $0 == $1 } // true- 두 시퀀스의 요소는 동일한 타입일 필요조차 없고, 제공된 클로저가 비교를 처리.
let ints = [1,2]
let strings = ["1","2","3"]
ints.isSubset(of: strings) { String($0) == $1 } // true- 컬렉션에 대한 바이너리 검색 알고리즘이 필요한 경우 여기에 하나의 예.
extension Array {
  /// Returns the first index where `value` appears in `self`, or `nil`,
  /// if `value` is not found. ///
  /// - Requires: `areInIncreasingOrder` is a strict weak ordering over the
  /// elements in `self`, and the elements in the array are already
  /// ordered by it.
  /// - Complexity: O(log `count`)
  func binarySearch
    (for value: Element, areInIncreasingOrder: (Element, Element) -> Bool)
    -> Int?
  {
    var left = 0
    var right = count - 1
    while left <= right {
      let mid = (left + right) / 2
      let candidate = self[mid]
      if areInIncreasingOrder(candidate,value) {
        left = mid + 1
      } else if areInIncreasingOrder(value,candidate) {
        right = mid - 1
      } else {
        // If neither element comes before the other, they _must_ be
        // equal, per the strict ordering requirement of areInIncreasingOrder
        return mid
      }
    }
    // Not found
    return nil
  }
}
extension Array where Element: Comparable {
  func binarySearch(for value: Element) -> Int? {
    return self.binarySearch(for: value, areInIncreasingOrder: <)
  }
}- 
유명하고 겉으로 보기에 단순한 알고리즘의 경우 바이너리 검색이 옳다는 것은 악명이 높음. 
- 
여기에는 20년 동안 자바 구현에 존재했던 버그가 포함되어 있고, 일반적인 버그에서 해결할 버그지만 우리는 그것이 유일한 버그라고 보장하지 않음? 
- 
다음과 같은 Swift 표준 라이브러리의 규약 중 일부는 주목해야 함. - 
index(of :)와 비슷하게 선택적 인덱스를 반환하며nil은찾을 수 없음을 의미.
- 두 번 정의됨. 한 번은 사용자 제공 매개 변수를 사용하여 비교를 수행하고 한 번은 적합성을 사용하여 호출자의 편의를 위해 해당 매개 변수를 제공.
- 정렬은 엄격한 weak정렬. 즉, 두 요소를 비교할 때 둘 중 하나가 다른 요소보다 먼저 정렬되지 않으면 둘은 같아야 함.
 
- 
- 
이것은 배열에서도 작동하지만, ContiguousArray나ArraySlice를 바이너리 검색하려면 운이 좋지 않음.
- 
이 메소드는 실제로 RandomAccessCollection의 확장에 있어야 함.
- 
일정 시간에 중간 점을 찾고 <=를 사용하여 인덱스의 순서를 검사할 수 있어야 하므로 로그 복잡도를 유지하려면 임의 액세스가 필요.
- 
지름길은 콜렉션에 Int색인이 있어야 한다는 것.
- 
이것은 표준 라이브러리의 거의 모든 무작위 액세스 컬렉션을 포함하므로 전체 Array버전을 다음과 같이 잘라내어 붙여 넣음.
extension RandomAccessCollection where Index == Int, IndexDistance == Int {
  public func binarySearch(for value: Iterator.Element,
    areInIncreasingOrder: (Iterator.Element, Iterator.Element) -> Bool)
    -> Index?
  {
    // Identical implementation to that of Array...
  }
}- 
경고: 이렇게하면, 더 나쁜 버그가 생기는데 곧 알려줌. 
- 
그러나 이는 여전히 정수 인덱스 컬렉션에만 국한되며 콜렉션에는 항상 정수 인덱스가 있는 것은 아님. 
- 
Dictionary,Set및 다양한String컬렉션 뷰에는 사용자 정의 인덱스 타입이 있음.
- 
표준 라이브러리에서 가장 주목할만한 무작위 액세스 예제는 ReversedRandomAccessCollection.
- 
컬렉션 프로토콜 장에서 보았듯이 원래 인덱스를 래핑하여 역순으로 컬렉션의 해당 위치로 변환하는 불투명 인덱스 타입을 가짐. 
- Int 인덱스에 대한 요구 사항을 해제하면 여러 컴파일러 오류가 발생.
- 이 코드는 완전하게 일반화되기 위해 몇 가지 재작성한 완전히 일반적인 버전.
extension RandomAccessCollection {
  public func binarySearch(for value: Iterator.Element,
    areInIncreasingOrder: (Iterator.Element, Iterator.Element) -> Bool)
    -> Index?
  {
    guard !isEmpty else { return nil }
    var left = startIndex
    var right = index(before: endIndex)
    while left <= right {
      let dist = distance(from: left, to: right)
      let mid = index(left, offsetBy: dist/2)
      let candidate = self[mid]
      if areInIncreasingOrder(candidate, value) {
        left = index(after: mid)
      } else if areInIncreasingOrder(value, candidate) {
        right = index(before: mid)
      } else {
        // If neither element comes before the other, they _must_ be
        // equal, per the strict ordering requirement of areInIncreasingOrder return mid
      }
    }
    // Not found
    return nil
  }
}
extension RandomAccessCollection
  where Iterator.Element: Comparable
{
  func binarySearch(for value: Iterator.Element) -> Index? {
    return binarySearch(for: value, areInIncreasingOrder: <)
  }
}- 
변화는 작지만 중요. 
- 
첫째, 왼쪽 및 오른쪽 변수가 더 이상 정수가 아닌 형식으로 변경. 
- 
대신 시작 및 종료 색인 값을 사용. 
- 
이것들은 정수일지도 모르지만, String의 인덱스 형(또는Dictionary또는Set)과 같은 불투명한 형태일 가능성이 있어, 이것들은 랜덤 억세스는 아님.
- 
그러나 두 번째로, (left + right) / 2가 약간 더 끔찍한index(left, offsetBy: dist / 2),let dist = distance (from: left, to: right)으로 변경됨. 어째서?
- 
핵심 개념은 실제로 이 계산과 관련된 두 가지 타입인 Index와IndexDistance가 있다는 것.
- 
이것들은 반드시 같은 것은 아님. 
- 
정수 인덱스를 사용할 때, 우리는 그것들을 서로 바꾸어 사용할 수 있지만 그 요구 사항을 느슨하게하면 이 문제가 해결됨. 
- 
거리는 컬렉션의 한 지점에서 다른 지점으로 이동하기 위해 index(after:)를 호출해야하는 횟수.
- 
끝 인덱스는 시작 인덱스에서 "도달 가능"해야하는데 index(after:)를 호출해야하는 유한 정수 횟수가 항상 존재.
- 
이것은 반드시 정수여야 함을 의미(반드시 Int일 필요는 없음).
- 
따라서 이것은 Collection의 정의에서 제약 조건.
public protocol Collection: Indexable, Sequence {
  /// A type that can represent the number of steps between a pair of
  /// indices.
  associatedtype IndexDistance: SignedInteger = Int
}- 
이것은 또한 컬렉션이 비어 있지 않도록 우리가 특별한 주의가 필요한 이유. 
- 
정수 연산 만하면 -1의 올바른 값을 생성하고0보다 작은 지 확인하는 데 아무런 해가 없음.
- 
그러나 어떤 종류의 색인을 다루는 경우, 콜렉션의 처음부터 뒤로 이동하지 않도록 해야함. 
- 
이는 유효하지 않은 조작일 수 있음. 예를 들어, 이중 연결된 목록의 시작 부분에서 돌아 가려고 하는 경우? 
- 
정수이기 때문에 인덱스 거리를 함께 더하거나 나머지를 찾기 위해 나눌 수 있음. 
- 
우리가 할 수 없는 일은 어떤 종류의 두 지수를 합치는 것. 
- 
콜렉션 프로토콜 장에서 링크된 목록을 가지고 있다면 분명히 두 노드에 포인터를 "추가"할 수 없음. 
- 
대신 index(after :),index(before :)또는index(_ : offsetBy :)를 사용하여 거리를 이동하는 관점에서만 생각.
- 
이 사고 방식은 배열에 대해 생각하는 데 익숙하다면 익숙해짐? 
- 
그러나 많은 배열 인덱스 표현식을 일종의 속기라고 생각. 
- 
예를 들어 let right = count - 1을 쓸 때, 실제로 의도한 바는right = index(startIndex, offsetBy: count - 1).
- 
인덱스가 Int이고startIndex가0일 때이 값은0 + count - 1로 감소하며, 이는 다시count - 1로 감소.
- 
이는 Array코드를 취한RandomAccessCollection에 구현된 심각한 버그로 이어짐.
- 
정수 인덱스를 가진 콜렉션은 항상 인덱스 0으로 시작하지는 않음.
- 
가장 일반적인 예는 ArraySlice입니다.myArray[3..<5]를 통해 생성된 슬라이스에는startIndex가3.
- 
우리의 간단한 바이너리 서치를 사용하면 런타임에 크래시가 발생. 
- 
인덱스가 정수가 되도록 요구할 수는 있었지만 스위프트 타입 시스템은 콜렉션이 0기반이 되도록 요구하는 좋은 방법이 없음.
- 
왼쪽 및 오른쪽 인덱스를 더하고 결과를 반으로 나누는 대신에 두 인덱스 사이의 거리의 절반을 찾은 다음 왼쪽 인덱스를 그 양만큼 중간점에 도달하도록 진행. 
- 
이 버전은 초기 구현시 버그를 수정. 
- 
배열이 매우 큰 경우 두 개의 정수 인덱스를 함께 추가하면 배열이 두 배가 되기 전에 오버 플로우( Count가Int.max에 가까워지고 검색된 요소가 배열의 마지막 요소라고 가정).
- 
반면에 두 인덱스 사이의 거리를 반으로 늘릴 때 이것은 발생하지 않음. 
- 
물론이 버그를 겪은 사람은 매우 적어 따라서 Java 표준 라이브러리의 버그가 오랜 시간이 지나고 발견됨. 
- 
이제 바이너리 검색 알고리즘을 사용하여 ReversedRandomAccessCollection을 검색.
let a = ["a", "b", "c", "d", "e", "f", "g"]
let r = a.reversed()
r.binarySearch(for: "g", areInIncreasingOrder: >) == r.startIndex // true- 또한 0기반이 아닌 슬라이스를 검색 가능.
let s = a[2..<5]
s.startIndex // 2
s.binarySearch(for: "d") // Optional(3)- 이 개념을 굳어지게 하는데 도움이 되는 또 다른 예가 Fisher-Yates 셔플링 알고리즘 구현.
extension Array {
  mutating func shuffle() {
    for i in 0..<(count - 1) {
      let j = Int(arc4random_uniform(UInt32(count - i))) + i
      // Guard against the (slightly pedantic) requirement of swap that you
      // not try to swap an element with itself.
      guard i != j else { continue }
      swap(&self[i], &self[j])
    }
  }
  /// Non-mutating variant of `shuffle`
  func shuffled() -> [Element] {
    var clone = self
    clone.shuffle()
    return clone
  }
}- 
다시 말하자면 표준 라이브러리를 따라함. 
- 
인플레이스 버전을 제공하는 것이 더 효율적일 수 있기 때문. 
- 
비변형 버전의 경우 배열의 셔플된 복사본을 생성? 
- 
그렇다면 정수 인덱스를 요구하지 않는 일반 버전? 
- 
바이너리 검색과 마찬가지로 랜덤 액세스가 필요하지만, 인플레이스 버전을 제공할 수 있기를 원하기 때문에 콜렉션을 변경할 수 있다는 새로운 요구 사항. 
- 
count - 1의 사용은 분명히 이진 검색과 비슷한 방식으로 변경해야 함.
- 
일반적인 구현을 하기 전에 추가로 복잡한 문제. 
- 
임의의 수를 생성하기 위해 arc4random_uniform을 사용하려고 하지만, 어떤 타입의IndexDistance가 될지 정확히 알지 못함.
- 
우리는 이것이 정수라는 것을 알고 있지만, 꼭 Int일 필요는 없음.
- 
Swift의 현재 정수 API는 제네릭 프로그래밍에 적합하지 않음. 
- 
이 기능을 상당히 개선 할 수있는 수정 된 정수 프로토콜에 대한 제안이 받아 들여졌지만, 
- 
Swift 3.0에는 제때에 구현되지 않음. 
- 
이 문제를 해결하려면 numericCast를 사용.
- 
이 함수는 일반적으로 다른 정수 타입간에 변환하는 함수. 
- 
이를 사용하여 임의의 부호있는 정수 타입에서 작동하는 arc4random_uniform버전을 작성(부호없는 정수 타입에도 버전을 작성할 수 있지만 색인 거리는 항상signed이기 때문에 다음과 같이).
extension SignedInteger {
  static func arc4random_uniform(_ upper_bound: Self) -> Self {
    precondition(upper_bound > 0 &&
      upper_bound.toIntMax() < UInt32.max.toIntMax(),
      "arc4random_uniform only callable up to \(UInt32.max)")
    return numericCast(
      Darwin.arc4random_uniform(numericCast(upper_bound)))
  }
}- 
원하는 경우 음수를 넘는 범위 또는 UInt32의 최대값을 초과하는 범위에서 작동하는arc4random버전을 작성.
- 
그러나 이렇게하려면 더 많은 코드가 필요한데 관심이 있다면, arc4random_uniform의 정의는 실제로 오픈 소스이며 아주 잘 주석 처리되어 있으며, 어떻게 할 것인지에 대한 단서도 제공.
- 
그런 다음 일반 셔플 구현에서 모든 IndexDistance타입에 대해 임의의 숫자를 생성하는 기능을 사용.
extension MutableCollection where Self: RandomAccessCollection {
  mutating func shuffle() {
    var i = startIndex
    let beforeEndIndex = index(before: endIndex)
    while i < beforeEndIndex {
      let dist = distance(from: i, to: endIndex)
      let randomDistance = IndexDistance.arc4random_uniform(dist)
      let j = index(i, offsetBy: randomDistance)
      guard i != j else { continue }
      swap(&self[i], &self[j])
      formIndex(after: &i)
    }
  }
}
extension Sequence {
  func shuffled() -> [Iterator.Element] {
    var clone = Array(self) clone.shuf e()
    return clone
  }
}
var numbers = Array(1...10)
numbers.shuffle()
numbers // [6, 5, 1, 8, 4, 10, 2, 9, 3, 7]- 
shuffle메서드는 비제네릭 버전보다 훨씬 복잡하고 읽기 쉽지 않음.
- 
이는 count - 1과 같은 간단한 정수 계산을index(before: endIndex)와 같은 색인 계산으로 대체해야 했기 때문이기도 함.
- 
다른 이유는 for루프에서while루프로 전환했기 때문입니다.for i in indices.dropLast()에서 i에 대한 인덱스를 반복하는 대안은 컬렉션 프로토콜 장에서 이미 언급한 잠재적인 성능 문제.
- 
인덱스 속성이 컬렉션에 대한 참조를 보유하고 컬렉션을 변경하면서 컬렉션을 변경하면 인덱스는 copy-on-write최적화를 무효화하고 콜렉션이 불필요한 사본을 생성.
- 
물론 대부분의 랜덤 액세스 컬렉션은 인덱스 형식이 기본 컬렉션을 참조할 필요가 없는 일반 정수 인덱스를 사용하기 때문에이 경우 발생 가능성은 적음. 
- 
예를 들어, Array.Indices는 디폴트DefaultRandomAccessIndices대신CountableRange<Int>.
- 
역 참조하는 인덱스 타입을 사용하는 임의 액세스 컬렉션의 한 예는 String.UTF16View(문자열을 다시 호출하면Foundation을 가져올 때RandomAccessCollection을 따름).
- 
그러나 그 중 하나는 MutableCollection이 아니므로 셔플링 알고리즘의 요구 사항을 충족시키지 못함.
- 
루프 내에서 실행 인덱스와 끝까지의 거리를 측정 한 다음 새로운 SignedInteger.arc4random메서드를 사용하여 스왑할 임의 인덱스를 계산.
- 
실제 스왑 작업은 비제너릭 버전과 동일하게 유지됨. 
- 
비변형 셔플을 구현할 때 MutableCollection을 확장하지 않은 이유가 궁금할 것.
- 
다시 말하지만 이것은 표준 라이브러리에서 자주 볼 수있는 패턴. 
- 
예를 들어 ContiguousArray를 정렬하면ContiguousArray가 아니라Array가 반환됨.
- 
이 경우, 우리의 변경 불가능한 버전은 콜렉션을 복제한 다음 그것을 제자리에 셔플하는 능력에 의존하기 때문. 
- 
이것은 차례로 값 의미론을 가진 콜렉션에 의존. 
- 
그러나 모든 컬렉션이 값 의미론을 가지고 있는 것은 아님. 
- 
NSMutableArray가MutableCollection을 준수하면(Swift 컬렉션이 값 의미론를 갖지 않는 나쁜 형식이기 때문에 가능하지 않을 수 있음),
- 
NSMutableArray가 참조 의미론을 가지고 있기 때문에,shuffled와shuffle는 같은 효과를 가질 것.
- 
var clone = self는 레퍼런스의 사본을 만들 뿐이므로, 후속clone.shuffle는 스스로를 뒤섞는데 아마 사용자가 기대하지 않았을 것.
- 
대신 요소의 전체 복사본을 배열로 가져 와서 그 코드를 뒤섞음. 
- 
절충안은 그 형식이 RangeReplaceableCollection인 한shuffled버전과 동일한 형식의 컬렉션을 반환하는shuffle버전을 작성.
extension MutableCollection
  where Self: RandomAccessCollection,
    Self: RangeReplaceableCollection
{
  func shuf ed() -> Self {
    var clone = Self()
    clone.append(contentsOf: self)
    clone.shuffle()
    return clone
  }
}- 이것은 RangeReplaceableCollection의 두 가지 기능에 의존.
- 콜렉션의 새로운 빈 버전을 생성하고 그 빈 콜렉션에 임의의 시퀀스(이 경우 self)를 추가하여 전체 복제를 보장.
- 표준 라이브러리는 이 접근법을 취하지 않음.
- 아마도 모든 종류의 비 위치 연산을 위한 배열을 만드는 일관성이 선호되기 때문일 수도 있지만 원하는 경우 옵션.
- 그러나 시퀀스 버전도 생성해야 하므로 변경 불가능한 범위 대체 가능한 컬렉션 및 시퀀스에 대한 셔플을 제공.
- 일반적으로 슬라이싱을 시도하고 사용하면 발생할 수있는 문제를 보여주는 마지막 예제.
- 주어진 하위 시퀀스를 검색하는 알고리즘을 쓰고 index(of:)와 비슷하지만 개별 요소가 아닌 하위 시퀀스를 검색.
- 첫 번째 인덱스를 반환. 이 인덱스에서 컬렉션 끝 부분까지 패턴으로 시작. index(where:)를 시도해 보면 컴파일러 오류가 발생합니다.
extension Collection where Iterator.Element: Equatable {
  func search<Other: Sequence>(for pattern: Other) -> Index?
    where Other.Iterator.Element == Iterator.Element
  {
    return indices. rst { idx in
      // Error: Missing argument for parameter 'by'
      suffix(from: idx).starts(with: pattern)
    }
  }
}- 
오류 메시지는 컴파일러가 여기에서 starts(with:by:)를 기대하며, 두 개의 요소가 동등한 지 판단하기 위해 추가 클로저를 사용하는데 이상하게 보임.
- 
매개 변수화되지 않은 변수 starts(with:)는 요소가Equatable인 시퀀스에서 사용할 수 있어야 하는데 이는Iterator.Element: Equatable를 통해 확장에 대해 지정한 것과 동일.
- 
또한 starts(with:)오버로드에 필요한 것처럼Other요소를 자신의 요소Other.Iterator.Element == Iterator.Element와 동일하게 제한.
- 
불행하게도, 보장되지 않는 한 가지 즉, SubSequence.Iterator.Element(즉, 슬라이스에있는 요소의 타입)가 컬렉션의 요소 타입과 같고 물론 그것이 있어야 됨?
- 
그러나 Swift 3.0에서 이 제약 조건을 작성할만큼 강력하지 않고 이것은 suffix(from: idx)로 만든 슬라이스를 컴파일러의 관점에서Other와 호환되지 않게 만듦.
- 
언어 수준에서 이 문제를 해결하려면 SubSequence관련 타입을SubSequence.Iterator.Element가Iterator.Element와 같아야 함.
- 
하지만 관련 타입의 where절이 현재 지원되지 않는 제약 조건이 있는 관련 타입을 다시 선언해야 하는데 Swift는 앞으로이 기능을 사용할 가능성이 높음.
- 
그 때까지 프로토콜 확장을 사용하여 사용하는 조각에 컬렉션과 동일한 요소가 포함되도록 해야 하고 첫 번째 시도는 하위 시퀀스를 컬렉션과 동일하게 지정하는 것. 
extension Collection
  where Iterator.Element: Equatable,
    SubSequence == Self
{
  // Implementation of search same as before
}- 
컴파일러는 이번에는 다른 오류 메시지로 응답. 
- 
이번에는 인덱스의 후행 클로저 인수에 관한 것. 
- 
첫 번째 호출 : " (Self.Index) -> Bool형식의 값을 예상되는 인수 타입으로 변환 할 수 없음(_) -> Bool".
- 
본질적으로 같은 문제이지만, 이번에는 요소 대신 인덱스에 대한 것. 
- 
타입 시스템은 Indices(그 자체가 콜렉션)의 요소 타입이 항상 콜렉션의 인덱스 타입과 동일하다는 개념을 표현할 수 없음.
- 
따라서 클로저의 idx파라미터(타입은Indices.Iterator.Element) 인수 타입suffix(from:)expects (Index)와 호환되지 않음?
- 
이 제약 조건을 확장에 추가하면 코드가 컴파일 가능. 
extension Collection
  where Iterator.Element: Equatable,
    SubSequence.Iterator.Element == Iterator.Element,
      Indices.Iterator.Element == Index
{
  func search<Other: Sequence>(for pattern: Other) -> Index?
    where Other.Iterator.Element == Iterator.Element
  {
    return indices. rst { idx in
      suffix(from: idx).starts(with: pattern)
    }
  }
}
let text = "It was the best of times, it was the worst of times"
text.characters.search(for: ["b","e","s","t"])
/*
Optional(Swift.String.CharacterView.Index(
_base: Swift.String.UnicodeScalarView.Index(_position: 11), _countUTF16: 1)) */- 이 프로세스 전체에 걸쳐 실제 코드를 한 번 변경하지는 않았지만 이 기능을 사용하려면 요구 사항 타입을 지정하는 제약 조건만 충족시켜야함.
- 
마지막으로 제약 조건을 약간 조이면 보다 효율적인 일반 알고리즘을 제공할 수 있는 경우가 종종 있음. 
- 
예를 들어 검색된 컬렉션과 패턴이 모두 임의 액세스 컬렉션이라는 것을 알고 있다면 위의 검색 알고리즘의 속도를 향상시킬 수 있음. 
- 
그렇게하면 컬렉션과 일치하기에 너무 짧은 패턴을 찾는 것을 피할 수 있으며 패턴이 컬렉션보다 길면 완전히 검색하지 않을 수 있음. 
- 
이 작업을 하려면 Self및Other가 모두RandomAccessCollection을 준수.
- 
그런 다음 코드 만큼이나 많은 제약 조건을 가진 알고리즘으로 자신을 찾음. 
extension RandomAccessCollection
  where Iterator.Element: Equatable,
    Indices.Iterator.Element == Index,
    SubSequence.Iterator.Element == Iterator.Element,
    SubSequence.Indices.Iterator.Element == Index
{
  func search<Other: RandomAccessCollection>
    (for pattern: Other) -> Index?
    where Other.IndexDistance == IndexDistance,
      Other.Iterator.Element == Iterator.Element
  {
    // If pattern is longer, this cannot match, exit early.
    guard !isEmpty && pattern.count <= count else { return nil }
    // Otherwise, from the start up to the end
    // less space for the pattern ...
    let stopSearchIndex = index(endIndex, offsetBy: -pattern.count)
    // ... check if a slice from this point
    // starts with the pattern.
    return prefix(upTo: stopSearchIndex).indices.first { idx in
      suffix(from: idx).starts(with: pattern)
    }
  }
}
let numbers = 1..<100
numbers.search(for: 80..<90) // Optional(80)- 여기에 하나의 다른 제약 조건이 추가됨.
- 두 컬렉션의 거리 타입이 동일.
- 이렇게 하면 코드가 서로 다를 가능성을 배제하지만 코드는 간단하게 유지됨.
- type-erasing AnyCollection구조체는 32비트 시스템에서 Int와 다른 IntMax를 사용.
- 대안으로는 numericCast를 사용,
- 
guard numericCast (pattern.count) <= count else {return nil}.
- 또한SubSequence.Indices.Iterator.Element == Index를 추가하여
- 
prefix(upTo: stopSearchIndex).indices요소 타입이 콜렉션의 인덱스 타입인지 확인하고 컴파일러에게 명시적으로 알려야 함.
- 
지금까지 살펴본 것처럼 제네릭을 사용하여 동일한 기능을 여러 번 구현 가능. 
- 
일반적인 함수를 작성할 수 있지만 특정 타입의 특정 구현을 제공 가능. 
- 
또한 프로토콜 확장을 사용하여 여러 타입에서 작동하는 일반 알고리즘을 작성 가능. 
- 
Generics는 또한 공유 기능을 제외하고 상용구를 줄이기 위해 프로그램을 설계하는 동안 매우 유용.
- 
이 섹션에서는 Normal 코드를 리펙토링하여 제네릭을 사용하여 Normal 기능을 제거. 
- 
제네릭 함수를 만들 수있을뿐만 아니라 제네릭 데이터 형식을 만들 수도 있음. 
- 
웹 서비스와 상호 작용하는 몇 가지 함수를 작성. 
- 
예를 들어 사용자 목록을 가져 와서 User 데이터 형식으로 파싱. 
- 
loadUsers라는 함수를 작성. 네트워크에서 비동기적으로 사용자를 로드한 다음 가져온 사용자 목록으로 콜백을 호출. 
- 
우선 순진한 방법으로 구현. 
- 
먼저 URL을 작성. 그런 다음 데이터를 동기적으로 로드. 
- 
그런 다음 JSON 응답을 구문 분석하여 사전 배열을 제공. 
- 
마지막으로 일반 JSON 객체를 사용자 구조체로 변환. 
func loadUsers(callback: ([User]?) -> ()) {
  let usersURL = webserviceURL.appendingPathComponent("/users")
  let data = try? Data(contentsOf: usersURL)
  let json = data.flatMap {
    try? JSONSerialization.jsonObject(with: $0, options: [])
  }
  let users = (json as? [Any]).flatMap { jsonObject in
    jsonObject. atMap(User.init)
  }
  callback(users)
}- 
이 함수에는 세 가지 오류가 존재. 
- 
URL 로딩이 실패하거나 JSON 구문 분석이 실패 할 수 있으며 JSON 배열에서 사용자 객체를 작성할 수 없음. 
- 
모든 세 작업은 실패시 nil을 반환.
- 
옵션 값에 flatMap을 사용하면 후속 작업은 이전 작업이 성공한 경우에만 실행.
- 
그렇지 않으면 첫 번째 실패한 작업의 nil값이 체인을 통해 끝에 전달.
- 
여기서 최종적으로 유효한 users 배열 또는 nil로 콜백을 호출.
- 
이제 다른 리소스를 로드하는 데 동일한 함수를 작성하려는 경우 대부분의 코드를 복제. 
- 
예를 들어 블로그 게시물을 로드하는 함수를 고려해 보면 타입은 다음과 같음. 
func loadBlogPosts(callback: ([BlogPost])? -> ())- 구현은 거의 동일.
- 코드 중복이 있을뿐만 아니라 두 기능 모두 테스트하기가 어려움.
- 테스트에서 웹 서비스에 액세스할 수 있는지 확인하거나 요청을 위조 할 수있는 방법을 찾아야 함.
- 또한 함수가 콜백을 사용하기 때문에 테스트를 비동기로 만들어야 함.
- 더 나은 접근법은 다른 파트를 재사용 가능하게 만들기 위해 함수 외부에서 사용자 특정 파트를 이동시키려는 것.
- 예를 들어 JSON 객체를 매개 변수로 사용하여 도메인 객체로 바꾸는 경로와 변환 함수를 추가.
- 이 새로운 함수의 이름을 보편성을 나타 내기 위해 loadResource 이름으로 지정.
- 우리는 변환 함수가 임의의 타입을 처리하기를 원하기 때문에, 함수를 A보다 포괄적으로 만들 수 있음 :
func loadResource<A>(at path: String,
  parse: (Any) -> A?,
  callback: (A?) -> ())
{
  let resourceURL = webserviceURL.appendingPathComponent(path)
  let data = try? Data(contentsOf: resourceURL)
  let json = data.flatMap {
    try? JSONSerialization.jsonObject(with: $0, options: [])
  }  
  callback(json.flatMap(parse))
}- 이제 loadUsers 함수를 loadResource에 기반.
func loadUsers(callback: ([User]?) -> ()) {
  loadResource(at: "/users", parse: jsonArray(User.init), callback: callback)
}- JSON을 사용자 객체로 변환하기 위해 헬퍼 함수 jsonArray를 사용.
- 먼저 Any를 Anys 배열로 변환 한 다음 제공된 함수를 사용하여 각 요소를 구문 분석하고 단계 중 하나라도 실패하면 nil을 반환.
func jsonArray<A>(_ transform: @escaping (Any) -> A?) -> (Any) -> [A]? {
  return { array in
    guard let array = array as? [Any] else {
      return nil
    }
    return array.flatMap(transform)
  }
}- 블로그 게시물을 로드하려면 경로와 구문 분석 기능을 변경하기 만하면 됨.
func loadBlogPosts(callback: ([BlogPost]?) -> ()) {
  loadResource(at: "/posts", parse: jsonArray(BlogPost.init), callback: callback)
}- 이렇게 하면 많은 중복을 피할 수 있음.
- 나중에 동기식 네트워킹에서 비동기 네트워킹으로 전환하려는 경우 loadUsers 또는 loadBlogPosts를 업데이트 할 필요가 없음.
- 그러나 이러한 기능이 매우 짧지만 여전히 테스트하기가 어렵고 비동기 상태로 유지되며 액세스 가능한 웹 서비스에 의존.
- loadResource 함수의 경로 및 구문 분석 매개 변수는 매우 밀접하게 결합.
- 하나를 변경하면 다른 하나도 변경해야 할 가능성이 큼.
- 자원을 설명하는 구조체에 이들을 묶어서 함수와 마찬가지로 구조체(및 다른 타입)도 제네릭일 수 있음.
struct Resource<A> {
  let path: String
  let parse: (Any) -> A?
}- 이제 Resource에 대한 메소드로 loadResource의 대체 버전을 작성.
- 리소스의 속성을 사용하여 로드할 대상과 결과를 구문 분석하는 방법을 결정하므로 나머지 인수는 콜백 함수뿐.
extension Resource {
  func loadSynchronously(callback: (A?) -> ()) {
    let resourceURL = webserviceURL.appendingPathComponent(path)
    let data = try? Data(contentsOf: resourceURL)
    let json = data.flatMap {
      try? JSONSerialization.jsonObject(with: $0, options: [])
    }
    callback(json.flatMap(parse))
  }
}- 특정 리소스를 로드하는 이전의 최상위 함수는 이제 Resource 구조체의 값이 됨.
- 따라서 새 기능을 만들지 않고도 새 리소스를 추가하는 것이 매우 쉬워짐.
let usersResource: Resource<[User]> =
  Resource(path: "/users", parse: jsonArray(User.init))
let postsResource: Resource<[BlogPost]> =
  Resource(path: "/posts", parse: jsonArray(BlogPost.init))- 비동기 네트워킹을 사용하는 변형을 추가하는 것이 최소한의 복제로 가능.
- 엔드 포인트를 설명하는 기존 코드를 변경할 필요가 없음.
extension Resource {
  func loadAsynchronously(callback: @escaping (A?) -> ()) {
    let resourceURL = webserviceURL.appendingPathComponent(path)
    let session = URLSession.shared
    session.dataTask(with: resourceURL) { data, response, error in
      let json = data.flatMap {
        try? JSONSerialization.jsonObject(with: $0, options: [])
      }
      callback(json.flatMap(self.parse))
    }.resume()
  }
}- 
비동기식 URLSession API의 사용과는 별도로, 동기식 버전과 유일한 중요한 차이점은 콜백 함수가 메소드의 범위를 벗어나기 때문에 콜백 인수에 @escaping주석을 추가해야한다는 것.
- 
이스케이프 처리와 비 이스케이프 처리에 대한 자세한 내용은 함수 장을 참조. 
- 
이제 네트워크 호출로부터 엔드 포인트를 완전히 분리. 
- 
usersResource와 postsResource를 절대 최소값으로 끓여서 리소스의 위치와 구문 분석 방법만 설명. 
- 
설계는 또한 확장 가능. 
- 
HTTP 메소드와 같은 구성 옵션을 추가하거나 요청에 POST 데이터를 추가하는 방법은 단순히 자원 타입에 추가 특성을 추가하는 것. 
- 
HTTP 메소드에 GET과 같은 기본값을 지정하면 코드를 깨끗하게 유지 가능. 
- 
테스트가 훨씬 간단해짐. 
- 
리소스 구조체는 완전히 동기적이고 네트워크와 무관하므로 리소스가 올바르게 구성되었는지 여부를 테스트하는 작업은 간단. 
- 
자연히 비동기적이고 네트워크에 의존하기 때문에 네트워킹 코드는 여전히 테스트하기가 더 여렵지만 이 복잡성은 이제 loadAsynchronously 메소드에서 훌륭하게 분리. 
- 
다른 모든 부분은 단순하며 비동기 코드를 포함하지 않음. 
- 
이 섹션에서는 네트워크에서 일부 데이터를 로드하기 위한 비 제네릭 함수를 시작. 
- 
다음으로 중복 매개 변수를 크게 줄이는 여러 매개 변수가 있는 제네릭 함수를 생성. 
- 
마지막으로 매개 변수를 별도의 Resource 데이터 타입으로 묶음. 
- 
구체적인 리소스에 대한 도메인별 로직은 네트워킹 코드와 완전히 분리. 
- 
따라서 네트워크 스택을 변경해도 리소스를 변경할 필요가 없음. 
- 제네릭은 컴파일러의 관점에서 어떻게 작동합니까?
- 이 질문에 대답하려면 표준 라이브러리의 min함수를 고려.
- (Apple의 2015 Worldwide Developers Conference의 Optimizing Swift Performance 세션에서 가져온 예제).
func min<T: Comparable>(_ x: T, _ y: T) -> T {
  return y < x ? y : x
}- 
min의 인수와 반환 값에 대한 유일한 제약 조건은 모두 3개가 동일한 타입T를 가져야하고T가Comparable을 준수해야 한다는 것.
- 
그것 이외에, T는Int,Float,String, 또는 컴파일러가 다른 모듈에서 정의되었으므로 컴파일 타임에 아무것도 알지 못하는 형식일 수 있음.
- 
즉, 컴파일러는 함수에 필요한 코드를 내 보내는데 필요한 두 가지 필수 정보가 부족함. - 
T타입의 변수의 크기 (인수와 반환 값 포함).
- 런타임에 호출되어야하는 <함수의 특정 오버로드 주소.
 
- 
- 
Swift는 제네릭 코드에 대해 간접적인 수준을 도입함으로써 이러한 문제를 해결. 
- 
컴파일러가 제네릭 형식을 가진 값을 발견하면 컨테이너에 값을 저장. 
- 
이 컨테이너에는 값을 저장하기 위한 고정 크기. 
- 
값이 너무 커서 맞지 않으면 Swift는 힙에 할당하고 컨테이너에 대한 참조를 컨테이너에 저장. 
- 
컴파일러는 제네릭 형식 매개 변수당 하나 이상의 감시 테이블 목록을 유지 관리. 
- 
하나는 소위 값 감시 테이블이고 해당 타입에 대한 각 프로토콜 제약 조건에 대해 하나의 프로토콜 감시 테이블. 
- 
감시 테이블( vtable)은 런타임에 함수 호출을 올바른 구현에 동적으로 전달하는 데 사용.
- 
값 감시 테이블은 모든 제네릭 타입에 대해 항상 존재. 
- 
여기에는 할당, 복사 및 삭제와 같은 타입의 기본 작업에 대한 포인터가 포함. 
- 
여기에는 Int와 같은 값 타입에 대한 간단한no-op또는memcopies가 있을 수 있지만 참조 타입에는 여기에 참조 계산 논리가 포함.
- 
값 감시 테이블에는 또한 타입의 크기와 정렬이 기록. 
- 
예제에서 제네릭 형식 T에 대한 레코드에는T가 하나의 프로토콜 제약 조건, 즉Comparable을 가지고 있기 때문에 하나의 프로토콜 감시 테이블이 포함됨.
- 
프로토콜이 선언 한 각 메서드 나 속성에 대해 감시 테이블에는 규격에 맞는 구현에 대한 포인터가 들어 있음. 
- 
그런 다음 일반 함수의 본문에 있는 이러한 메서드 중 하나에 대한 호출이 런타임에 감시 테이블을 통해 전달됨. 
- 
이 예제에서는 y < x표현식이 이런 식으로 전달.
- 
프로토콜 감시 테이블은 제네릭 형식이 준수하는 프로토콜(일반 제약 조건을 통해 정적으로 컴파일러에 알려짐)과 특정 타입에 대해 해당 기능을 구현하는 함수(런타임에만 알려짐)간의 매핑을 제공. 
- 
실제로 어떤 방식 으로든 값을 쿼리하거나 조작하는 유일한 방법은 감시 테이블을 사용하는 것. 
- 
제한되지 않은 매개 변수 <T>를 사용하여min함수를 선언 할 수 없었으며 이 매개 변수가Comparable conformance와 관계없이<에 대한 구현.
- 
컴파일러는 올바른 <구현 위치를 찾을 수있는 미러링 모니터 테이블이 없기 때문에이 작업을 허용하지 않음.
- 
이것이 제네릭이 프로토콜과 밀접한 관련이 있는 이유. 
- 
Array<Element>또는Optional<Wrapped>와 같은 쓰기 컨테이너 타입을 제외하고는 제약이 없는 제네릭으로는 많은 것을 할 수 없음.
- 
요약하면 컴파일러가 min함수에 대해 생성하는 코드는 다음과 유사(의사 코드에서).
func min<T: Comparable>(_ x: TBox, _ y: TBox,
  valueWTable: VTable, comparableWTable: VTable)
  -> TBox
{
  let xCopy = valueWTable.copy(x)
  let yCopy = valueWTable.copy(y)
  let result = comparableWTable.lessThan(yCopy, xCopy) ? y : x
  valueWTable.release(xCopy)
  valueWTable.release(yCopy)
  return result
}- 제네릭 매개 변수에 대한 컨테이너의 레이아웃은 다음 장에서 다룰 프로토콜 타입에 사용되는 실존 컨테이너와 비슷하지만 동일하지 않음.
- 실존적 컨테이너는 값에 대한 저장소와 0개 이상의 감시 테이블에 대한 포인터를 하나의 구조로 결합하는 반면 일반 매개 변수에 대한 컨테이너에는 값 저장만 포함.
- 감시 테이블은 별도로 저장되므로 일밤 함수에서 동일한 유형의 모든 변수간에 공유 가능.
- 
이전 섹션에서 설명한 compile-once-and-dispatch-dynamically모델은 Swift의 제네릭 시스템에서 중요한 설계 목표.
- 
컴파일러가 템플릿을 사용하는 콘크리트 타입의 모든 순열에 대해 템플릿 기능 또는 클래스의 개별 인스턴스를 생성하는 C++ 템플릿의 작동 방식과 비교하면 컴파일 시간이 단축되고 바이너리가 더 작아 질 수 있음. 
- 
스위프트의 모델은 C++과 달리 일반 API를 사용하는 코드는 일반 함수 또는 타입의 구현을 볼 필요가 없으므로 유연함. 
- 
단점은 코드가 간접적으로 수행해야 하는 런타임의 성능 저하. 
- 
이것은 단일 함수 호출을 고려할 때 무시할 만하지만 제네릭이 Swift에 있는 것처럼 보급될 때 더해짐. 
- 
표준 라이브러리는 값을 비교하는 등 최대한 빨리 수행해야 하는 매우 일반적인 작업을 포함하여 모든 곳에서 제네릭을 사용. 
- 
제네릭 코드가 비 제네릭 코드보다 약간 느릴지라도 개발자는 이를 사용하지 않을 수 있음(제네릭을 사용한다는 뜻?) 
- 
다행히 Swift 컴파일러는 이 오버 헤드를 제거하기 위해 generic specialization라고 하는 최적화를 사용.
- 
generic specialization는 컴파일러가Int와 같은 구체적인 매개 변수 타입에 대해min<T>과 같은 일반 형식 또는 함수를 복제한다는 것을 의미.
- 
이 특수 함수는 Int에 대해 최적화되어 모든 오버 헤드를 제거할 수 있고 따라서Int의min<T>의 특수 버전은 다음과 같음.
func min(_ x: Int, _ y: Int) -> Int {
  return y < x ? y : x
}- 
이는 가상 디스 패치의 비용을 제거할 뿐만 아니라 인라이닝과 같은 추가 최적화를 가능하게 하여 간접적인 방향으로 인해 장벽이 될 수 있음? 
- 
옵티마이저는 휴리스틱을 사용하여 어떤 제네릭 형식이나 함수가 특수화를 위해 선택되는지, 어떤 구체 타입에 대해 특수화를 수행할지 결정. 
- 
이 결정에는 컴파일 시간, 바이너리 크기 및 런타임 성능의 균형이 필요. 
- 
코드가 Int인수를 사용하여min을 매우 자주 호출하지만Float인수를 사용하는 경우 한 번만 호출하면Int변형만 특수화.
- 
사용 가능한 모든 휴리스틱을 활용하려면 최적화가 활성화 된 상태로 릴리즈 빌드를 컴파일(명령 행에서 swiftc -O). 
- 
컴파일러가 generic specialization에 얼마나 적극적이든지 관계없이 적어도 제네릭 함수가 다른 모듈에 표시되는 경우 함수의 제네릭 버전이 항상 존재.
- 
이렇게 하면 컴파일러가 제네릭 함수를 컴파일 할 때 외부 형식에 대해 알지 못하더라도 외부 코드가 항상 제네릭 버전을 호출 가능. 
- 
generic specialization는 컴파일러가 일반화된 타입 또는 함수의 전체 정의를 볼 수 있는 경우에만 작동.
- 
Swift는 기본적으로 소스 파일을 개별적으로 컴파일하므로 일반 코드를 사용하는 코드가 일반 코드와 동일한 파일에 있는 경우에만 특수화 가능. 
- 
이것은 꽤 큰 한계이기 때문에, 컴파일러는 전체 모듈 최적화를 가능하게 하는 플래그를 가지고 있음. 
- 
이 모드에서는 현재 모듈의 모든 파일이 하나의 파일인 것처럼 함께 최적화되어 전체 코드 베이스에서 generic specialization를 허용.
- 
-whole-module-optimization을swiftc에 전달하여 전체 모듈 최적화를 활성화.
- 
성능 향상이 엄청날 수 있기 때문에 릴리스 빌드에서 (그리고 디버그 빌드에서도) 사용하면 좋지만 단점은 컴파일 시간이 길다는 것. 
- 
전체 모듈 최적화는 다른 중요한 최적화를 가능하게 함. 
- 
예를 들어, 옵티마이저는 내부 클래스가 전체 모듈에 하위 클래스가 없는 경우를 인식. 
- 
내부 수정자는 클래스가 모듈 외부에 표시되지 않도록하기 때문에 컴파일러가 이 클래스의 모든 메서드에 대해 동적 디스패치를 정적 디스패치로 바꾸는 게 가능. 
- 
generic specialization에는 일반 타입 또는 함수의 정의가 표시되어야 하므로 모듈 경계에서 수행 할 수 없음.
- 
즉, 외부 클라이언트보다 코드가 정의된 동일한 모듈에 있는 클라이언트의 경우 일반 코드가 더 빠를 가능성이 높음. 
- 
유일한 예외는 표준 라이브러리의 제네릭 코드. 
- 
표준 라이브러리는 다른 모든 모듈에서 중요하고 사용하기 때문에 표준 라이브러리의 정의는 모든 모듈에서 볼 수 있으므로 specialization가능.
- 
Swift는 @_specialize라는 준 공식 속성을 포함.
- 
이 속성을 사용하면 일반 코드의 특정 버전을 다른 모듈에서 사용할 수 있음. 
- 
전문화하려는 타입 목록을 지정해야 하므로 코드가 제한된 수의 타입에서만 사용된다는 것을 알고 있는 경우에만 도움. 
- 
다른 모듈에서 사용할 수 있는 정수 및 문자열에 대한 min함수의 특수 버전을 만드는 방법은 다음과 같음.
@_specialize(Int)
@_specialize(String)
public func min<T: Comparable>(_ x: T, _ y: T) -> T {
  return y < x ? y : x
}- 
public을 추가 했음을 주목.
- 
internal,fileprivate또는private APIs에@_specialize를 지정하는 것은 의미가 없음.
- 왜냐하면 그것들은 모듈 외부에서 보이지 않기 때문.
- 
이 장의 시작 부분에서는 알고리즘 또는 데이터 타입에 필요한 필수 인터페이스를 식별하는 것으로 제네릭 프로그래밍을 정의. 
- 
비 제네릭 버전으로 시작한 다음 주의 깊게 제약 조건을 제거하여 isSubset메서드에 대해 이 작업을 수행.
- 
서로 다른 제약 조건을 가진 다중 오버로드를 작성하여 가능한 가장 넓은 타입의 범위에 기능을 제공하고 동시에 성능 기대치를 충족. 
- 
컴파일러는 관련 타입에 가장 적합한 변형을 선택. 
- 
비동기 네트워킹 예제에서 우리는 Resource 구조체에서 네트워크 스택에 대한 많은 가정을 제거. 
- 
구체적인 리소스 값은 서버의 루트 도메인이나 데이터를 로드하는 방법에 대한 가정을 하지 않음. 
- 
이는 API 엔드 포인트의 불활성 표현일 뿐. 
- 
여기서 제네릭 프로그래밍은 리소스를 네트워크 코드에서 간단하고 분리된 상태로 유지하는 데 도움이 되고 테스트가 더 쉬워짐. 
- 
제네릭 프로그래밍의 이론적 세부 사항과 언어가 다른 언어에 대해 자세히 알고 싶다면, 
- 
Ronald Garcia 외 2007 년 논문 제목 "An Extended Comparative Study of Language Support for Generic Programming." 
- 
마지막으로 스위프트의 일반적인 프로그래밍은 다음 장 Protocols없이 불가능.