- 
                Notifications
    You must be signed in to change notification settings 
- Fork 0
Built In Collections
- 컬렉션은 모든 프로그래밍 언어에서 가장 중요한 데이터 타입 중 하나.
- 여러 종류의 컨테이너에 대한 언어 지원은 프로그래머의 생산성과 행복에 직결.
- Swift는 시퀀스와 컬렉션에 특별한 강조점을 가지며 표준 라이브러리의 상당 부분이 이 주제에 전념.
- 결과로 나오는 모델은 다른 언어에서 익숙해진 모델보다 훨씬 확장성이 뛰어나지만 꽤 복잡함.
- 이 장에서는 스위프트가 제공하는 주요 컬렉션 타입을 살펴보고 효과적으로 관용적으로 작업하는 방법을 알아봄.
- 배열은 Swift의 가장 일반적인 컬렉션.
- 배열은의 순서가 지정된 컨테이너.
- 모든 요소는 동일한 타입을 가지며 각 요소에 임의로 액세스 가능.
// The Fibonacci numbers
let fibs = [0, 1, 1, 2, 3, 5]- 위에서 정의한 배열을 수정하려고 하면 컴파일 오류가 발생.
- 배열이 let을 사용하여 상수로써 정의 되었기 때문이고 실수로 배열을 변경하는 것을 방지.
- 배열을 변수로 만들려면 var를 사용하여 배열을 정의.
var mutableFibs = [0, 1, 1, 2, 3, 5]
mutableFibs.append(8)
mutableFibs.append(contentsOf: [13, 21])
mutableFibs // [0, 1, 1, 2, 3, 5, 8, 13, 21]- 
let으로 정의 된 상수는 변경할 수 없기 때문에 추론하기가 더 쉬움.
- 
let fibs = ...와 같은 선언문을 읽을 때,fibs의 값은 절대로 변경되지 않는다는 것 알 수 있음.
- 이것은 컴파일러에 의해 강제되며 코드를 통해 읽을 때 크게 도움이 되지만, 값 의미(Value Semantics)를 갖는 형식에만 해당됨.
- 
Value Semantics관련 참고 : https://realm.io/kr/news/letswift-swift-performance/
- 
let을 클래스 인스턴스(즉, 참조 타입)에 적용하면 참조가 절대 변경되지 않기 때문에 다른 객체를 해당 변수에 할당 할 수 없음.
- 그러나 참조가 가리키는 개체가 변경 가능.
var x = [1,2,3]
var y = x       // x의 복사본 생성
y.append(4)     // y에 4를 추가
y               // [1, 2, 3, 4]
x               // [1, 2, 3]- 로컬 사본을 가져오고 변경 사항이 호출자에게 영향을 미치지 않음
let a = NSMutableArray(array:[1,2,3])
// I don't want to be able to mutate b
let b: NSArray = a
// But it can still be mutated - via a
a.insert(4, at: 3)
b // ( 1, 2, 3, 4 )
let c = NSMutableArray(array: [1,2,3])
// I don't want to be able to mutate d
let d = c.copy() as! NSArray
c.insert(4, at: 3)
d // ( 1, 2, 3 )- 
mutability는let대신var로 선언하여 정의됨.
- 하지만 참조 공유는 없음 - let을 사용하여 두 번째 배열을 선언하면 절대 바뀌지 않을 것이라고 보장됨.
- 너무 많은 복사본을 만드는 것은 성능 문제를 일으킬 수 있음.
- Swift 표준 라이브러리의 모든 컬렉션 타입은 copy-on-write기법을 사용해 필요할 때만 데이터가 복사되어 사용.
- 
copy-on-write참고 : http://talkingaboutme.tistory.com/451
- Swift 배열은 isEmpty 및 count와 같이 예상되는 모든 일반적인 작업을 제공.
- 배열은 fibs[3]와 같이 첨자를 통해 특정 색인에 있는 요소에 직접 액세스 가능.
- 첨자를 통해 요소를 가져 오기 전에 색인이 Range 내에 있는지 확인 필요.
- 그렇지 않으면 프로그램이 치명적인 오류로 중단됨.
- 배열 인덱스를 사용하는 방법에 따라 달라지며 Swift에서 인덱스를 실제로 계산해야하는 경우는 거의 없음.
- 또 다른 하나는 Swift 3에서 전통적인 C 스타일의 for루프를 제거하는 것.
// 배열을 반복하고 싶은 경우
for x in array
// 첫 요소를 제외한 나머지 요소를 반복하고 싶은 경우
for x in array.dropFirst()
// 마지막 다섯 요소를 제외한 나머지 요소를 반복하고 싶은 경우
for x in array.dropLast(5)
// 배열의 모든 요소에 번호를 지정하고 싶은 경우
for (num, element) in collection.enumerated()
// 특정 요소의 위치를 찾고 싶은 경우
if let idx = array.index { someMatchingLogic($0) }
// 배열의 모든 요소를 변형하고 싶은 경우
array.map { someTransformation($0) }
// 특정 기준과 일치하는 요소만 가져오고 싶은 경우
array.filter { someCriteria($0) }- 
인덱스를 수동으로 조작하는 것은 버그 발생의 가능성 때문에 가급적 피하는 것이 가장 좋지만 때로는 색인을 사용해야 함. 
- 
배열 인덱스를 사용하면 인덱스 계산의 논리에 대해 매우 신중하게 생각. 
- 
인덱스가 유효해야 한다는 것을 알고 있으므로 결과를 강제로 언래핑하는 것이 좋음(?) 
- 
이것은 (a) 귀찮고 (b) 나쁜 습관. 
- 
subscripting은 메모리 안전과 관련하여 완전히 안전.
- 
표준 라이브러리 컬렉션은 Range를 벗어난 인덱스로 승인되지 않은 메모리 액세스를 방지하기 위해 항상 경계 검사를 수행. 
- 
first와last속성은 옵셔널을 반환 (배열이 비어 있는 경우nil리턴)
- 
first는isEmpty ? nil : self[0]와 같음
- 
removeLast메소드는 빈 배열에서 호출하면 크래시
- 
popLast는 배열이 비어 있지 않으면 마지막 요소만 삭제하고 반환하고, 그렇지 않으면 아무것도 수행하지 않고nil을 반환.
- 
배열을 스택으로써 사용할 때, 항상 빈 체크와 마지막 엔트리 제거를 결합하고 싶을 것. 
- 
반면에, 배열이 비어 있는지 여부를 이미 불변을 통해 알았다면, 옵션을 다루는 것은 곤란한 일(?) 
- 
dictionaries에 대해 이야기 할 때이 장의 뒷부분에서 이와 같은 단점을 다시 만날 것.
- 배열의 모든 값에 대해 변환을 수행해야하는 것은 일반적.
- 새 배열을 만들고 기존 배열의 모든 요소를 반복하며 요소에 대한 연산을 수행하고 해당 연산의 결과를 새 배열에 추가.
- 예를 들어, 다음 코드는 정수 배열을 제곱함.
// using for-loop
var squared: [Int] = []
for fib in fibs {
  squared.append(fib * fib)
}
squared // [0, 1, 1, 4, 9, 25]
// using map
let squares = fibs.map { fib in fib * fib }
squares // [0, 1, 1, 4, 9, 25]- 
map을 사용하는 경우 코드의 길이 더 짧고 오류가 발생할 여지가 적음. 하지만 더 중요한건 명확하다는 것.
- 어떤 일이 발생했는지 즉시 알 수 있습니다. 함수가 모든 요소에 적용되어 변형된 요소의 새 배열을 반환.
- 제곱에 대한 선언도 더 이상 var로 만들 필요가 없음.
- 완전한 형태의 map에서 전달되므로let으로 선언할 수 있습니다. 그리고
- 내용이 map에 전달된 함수에서 유추될 수 있기 때문에 더이상 명시적으로 입력 할 필요가 없음.
- 
map은 쓰기가 어렵지 않음. for 루프의 상용구 부분을 일반 함수로 마무리하는 것.
extension Array {
  func map<T>(_ transform: (Element) -> T) -> [T] {
    var result: [T] = []
    result.reserveCapacity(count)
    for x in self {
      result.append(transform(x))
    }
    return result
  }
}- 요소는 배열에 포함 된 모든 타입의 일반적인 위치표시자.
- T는 요소 변형의 결과를 나타낼 수 있는 새로운 위치표시자.
- 
map함수 자체는Element와T가 무엇인지 신경 쓰지 않고 무엇이든 될 수 있음.
- 변환된 요소의 구체적인 타입 T는 호출자가 전달할 변환 함수의 반환 타입에 의해 정의됨.
- 
map은 즉 각 요소를 얼마나 정확하게 변환 할 것인지에 대한 논리에서 호출 간 호출에 따라 달라지지 않는 상용구를 분리 관리.
- 
이는 호출자가 제공하는 매개 변수인 변환 함수를 통해 수행됨. 
- 
이 매개 변수화 동작 패턴은 표준 라이브러리에서 찾을 수 있으며 클로저를 사용하는 12 가지 이상의 개별 함수가 존재. - 
map,flatMap: 요소를 변환하는 방법
- 
filter: 요소가 포함되어야 하는가
- 
reduce: 요소를 집계 값으로 접는 방법(?)
- 
sequence: 다음 요소는 무엇인가
- 
forEach: 요소로 수행할 side effect
- 
sort,lexicographicallyPrecedes,partition: 두 가지 요소가 어느 순서로 오게 되나
- 
index,first,contains: 요소가 일치하는가
- 
min,max: 두 요소 중 최솟값과 최댓값은 무엇인가
- 
elementsEqual,starts: 두 요소가 동등한가
- 
split: 이 요소는 separator인가
 
- 
- 
이 모든 기능의 목표는 새로운 배열의 생성, 소스 데이터에 대한 for 루프 등과 같이 코드의 관심없는 부분의 불필요한 부분을 제거하는 것. 
- 
대신, 중요한 코드(프로그래머가 표현하고자하는 논리)를 앞으로 가져옴. 
- 
sort는 사용자가 달리 지정하지 않는 한 요소를 비교할 때 오름차순으로 요소를 정렬
- 
contains는 요소가 동등한 한 검사할 값을 취할 수 있음.
- 
오름차순 정렬은 자연스럽기 때문에 array.sort()의 의미는 직관적.
- 
array.index(of: "foo")는array.index { $0 == "foo" }보다 명확함.
- 
유사한 기능의 다른 기능들이 있지만 행동을 지정하기 위해 클로저를 사용하지만 표준 라이브러리에는 없는 경우도 있음. - 
accumulate: 요소를 실행 값의 배열로 결합. (예 : reduce, 각 임시 조합의 배열 반환)
- 
all(matching:),none(matching:): 시퀀스의 모든 요소가 기준과 일치하는지 테스트. (contains 포함하여 빌드 할 수 있으며 일부는 신중하게 배치)
- 
count(where:): 일치하는 요소의 수. (필터와 유사하지만 배열을 구성하지 않음)
- 
indices(where:): 기준과 일치하는 인덱스 목록을 반환. (index와 비슷하지만 첫 번째 인덱스에서는 멈추지 않음(?)).
- 
prefix(while:): 술어가 true를 반환하는 동안 필터 요소를 처리한 다음 나머지는 삭제 (필터와 유사하지만 초기 이탈이 있으며 무한 또는 지연 계산 시퀀스에 유용함)
- 
drop(while:): 술어가 true가 될 때까지 요소를 놓은 다음 나머지 부분을 반환 (prefix(while:)과 비슷하지만 inverse를 반환)
 
- 
let names = ["Paula", "Elena", "Zoe"]
var lastNameEndingInA: String?
for name in names.reversed() where name.hasSuffix("a") {
  lastNameEndingInA = name
  break
}
lastNameEndingInA // Optional("Elena")- 이 경우 Sequence에 짧은 확장 기능을 작성. last(where:)는 이 로직을 래핑하고 클로저를 사용하여 for 루프 부분을 추상화.
extension Sequence {
  func last(where predicate: (Iterator.Element) -> Bool) -> Iterator.Element? {
    for element in reversed() where predicate(element) {
      return element
    }
    return nil
  }
}
let match = names.last { $0.hasSuffix("a") }
match // Optional("Elena")- 
last(where:)를 사용한 예제는for루프를 사용한 예제보다 읽기 쉽고, 오류가 발생할 가능성이 적음.
- 
var대신let을 사용하여 결과 변수를 선언 가능.
- 
guard와도 잘 어울리며 요소가 발견되지 않으면 일찍 종료할 것.
guard let match = someSequence.last(where: { $0.passesTest() })
  else { return }
// Do something with match- 배열을 반복할 때 map을 사용하여 side effect를 수행할 수 있음. (예: 요소를 조회 테이블에 삽입).
array.map { item in
  table.insert(item)
}- 
위와 같은 것을 본다면 map과 같은 함수 대신for루프를 사용하는 것이 명백함.
- 
forEach메소드는이 경우map보다 더 적합하지만 약간의 문제점이 있음.
- 
side effect를 수행하는 것은 의도적으로 클로저 로컬 상태를 주는 것과는 다른데, 이는 특히 유용한 기술이며, 클로저를 만드는 것. 
- 
즉, Range를 벗어나는 변수를 캡처하고 변형할 수 있는 기능이며, 고차원 함수와 결합 할 때 강력한 도구. 
- 
예를 들어 위에서 설명한 누적 함수는 다음과 같이 map과stateful closure로 구현 될 수 있습니다.
extension Array {
  func accumulate<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> [Result] {
    var running = initialResult
    return map { next in
      running = nextPartialResult(running, next)
      return running
    }
  }
}- 이것은 실행중인 값을 저장하기 위한 임시 변수를 생성한 다음 map을 사용하여 진행중인 값의 배열을 만듦.
[1,2,3,4].accumulate(0, +) // [1, 3, 6, 10]- 배열을 가져와 특정 조건과 일치하는 요소만 포함하는 새 배열을 만드는 것.
- 배열을 반복하고 주어진 술어와 일치하는 요소를 선택하는 패턴은 filter메소드에서 캡처.
nums.filter { num in num % 2 == 0 } // [2, 4, 6, 8, 10]
nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]- 매우 짧은 클로저의 경우이 값을 보다 쉽게 읽을 수 있지만, 더 복잡하다면 인수를 명시하는 것이 더 좋음. (개인적인 취향의 문제)
- 
map과filter를 결합함으로써 하나의 중간 배열을 도입할 필요없이 배열에 많은 연산을 쉽게 작성 가능.
- 결과 코드는 더 짧아지고 읽기 쉽게됨.
(1..<10).map { $0 * $0 }.filter { $0 % 2 == 0 } // [4, 16, 36, 64]
extension Array {
  func filter(_ isIncluded: (Element) -> Bool) -> [Element] {
    var result: [Element] = []
    for x in self where isIncluded(x) {
      result.append(x)
    }
    return result
  }
}// if you ever find yourself writing something like the following, stop!
bigArray.filter { someCondition }.count > 0- 필터는 새로운 배열을 만들고 배열의 모든 요소를 처리하나 이것은 불필요합니다.
- 이 코드는 하나의 요소가 일치하는지만 확인하면 됨.
- 어떤 경우에는 contains(where:)가 작업을 수행.
bigArray.contains { someCondition }- 이것은 두 가지 이유 때문에 훨씬 빠름.
- 즉, 필터 된 요소를 계산하기 위해 완전히 새로운 배열을 만들지 않고 첫 번째 요소와 일치하는 즉시 조기에 종료.
- 일반적으로 모든 결과를 원할 경우에만 필터를 사용하는걸 권장.
extension Sequence {
  public func all(matching predicate: (Iterator.Element) -> Bool) -> Bool {
    // Every element matches a predicate if no element doesn't match it:
    return !contains { !predicate($0) }
  }
}
let evenNums = nums.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]
evenNums.all { $0 % 2 == 0 } // true- 
map과filter는 모두 배열을 가져와 새로운 수정된 배열을 생성.
- 그러나 때로는 모든 요소를 새로운 값으로 결합이 필요.
var total = 0
for num in fibs {
  total = total + num
}
total // 12- 
reduce메소드는 이 패턴을 취하여 초기 값(이 경우 0)과 중간 값(total)과 요소(num)를 결합하는 함수의 두 부분을 추상화.
- 
reduce를 사용하면 다음과 같은 예제를 작성.
let sum = fibs.reduce(0) { total, num in total + num } // 12
fibs.reduce(0, +) // 12
fibs.reduce("") { str, num in str + "\(num) " } // 0 1 1 2 3 5
extension Array {
  func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) -> Result) -> Result {
    var result = initialResult for x in self {
      result = nextPartialResult(result, x)
    }
    return result
  }
}- 또 다른 성능 팁 : reduce는 매우 유연하며 배열을 만들고 다른 작업을 수행하는 데 사용되는 것이 일반적
- 예를 들어, reduce만 사용하여map및filter를 구현 가능.
extension Array {
  func map2<T>(_ transform: (Element) -> T) -> [T] {
    return reduce([]) {
      $0 + [transform($1)]
    }
  }
  func filter2(_ isIncluded: (Element) -> Bool) -> [Element] {
    return reduce([]) {
      isIncluded($1) ? $0 + [$1] : $0
    }
  }
}- This is kind of beautiful and has the benefit of not needing those icky imperative for loops. But Swift isn’t Haskell, and Swift arrays aren’t lists. What’s happening here is that every time, through the combine function, a brand new array is being created by appending the transformed or included element to the previous one. This means that both these implementations are O(n2), not O(n) — as the length of the array increases, the time these functions take increases quadratically.
- 때로는 변환 함수가 단일 요소가 아닌 다른 배열을 반환하는 배열을 매핑.
- 예를 들어 Markdown 파일을 읽고 파일에 있는 모든 링크의 URL을 포함하는 배열을 반환하는 함수, 링크가 있다고 할 때 함수 타입은 다음과 같음.
func extractLinks(markdownFile: String) -> [URL]let markdownFiles: [String] = // ...
let nestedLinks = markdownFiles.map(extractLinks)
let links = nestedLinks.joined()- 
map을 수행하고 배열을 가져온 다음 조인을 호출하여 결과를 단일 배열로 병합.
- 
flatmap메서드는 이 두 작업을 단일 단계로 결합.
- markdownFiles.flatMap (extractLinks)는 Markdown 파일의 배열에 있는 모든 URL을 단일 배열로 반환.
- 
flatMap의 구현은 배열을 반환하는 함수 인수를 사용한다는 점을 제외하면map과 거의 동일.append(_:)대신append(contentsOf:)를 사용하여 추가할 때 결과를 평평하게 만듦.
extension Array {
  func flatMap<T>(_ transform: (Element) -> [T]) -> [T] {
    var result: [T] = []
    for x in self {
      result.append(contentsOf: transform(x))
    }
    return result
  }
}- 
flatMap의 또 다른 유용한 사용법은 다른 배열의 요소를 결합하는 것.
- 가능한 한 두 배열의 쌍을 가져 오려면 한 배열의 flatMap을 한 배열에 매핑한 다음 다른 배열 위에 매핑합.
let suits = ["♠", "♥", "♣", "♦"]
let ranks = ["J","Q","K","A"]
let result = suits.flatMap { suit in
  ranks.map { rank in
    (suit, rank)
  }
}- 
for루프와 거의 동일하게 작동.
- 전달된 함수는 시퀀스의 각 요소에 대해 한 번 실행.
- 
map과 달리,forEach는 아무 것도 반환하지 않음.
for element in [1,2,3] {
  print(element)
}
[1,2,3].forEach { element in
  print(element)
}- 표현식 대신 forEach에 함수 이름을 전달하면 명확하고 간결한 코드로 이어질 수 있음.
- 그러나 for루프와forEach사이에는 약간의 차이점이 있음. (for루프에return문이 있는 경우)
extension Array where Element: Equatable {
  func index(of element: Element) -> Int? {
    for idx in self.indices where self[idx] == element {
      return idx
    }
    return nil
  }
}
extension Array where Element: Equatable {
  func index_foreach(of element: Element) -> Int? {
    self.indices.filter { idx in
      self[idx] == element
    }.forEach { idx in
      return idx
    }
    return nil
  }
}- 
forEach클로저 내부의return은 외부 함수를 벗어나지 않고 클로저 자체에서만 반환.
(1..<10).forEach { number in
  print(number)
  if number > 2 { return }
}- 
return문은 루프를 깨뜨리는 것이 아니라 클로저에서 돌아오는 것.
- 일부 상황에서는 forEach가for루프보다 좋을 수 있고 일련의 연속된 작업의 일부로 실제로 빛을 발함.
- 반환과 함께 명백하지 않은 동작 때문에 forEach의 다른 용도에 대해 권장하며 그냥for루프 대신 사용할 것.
- 첨자(예: fibs[0])로 배열의 단일 요소에 액세스하는 것 외에도 하위 요소를 사용하여 요소 Range에 액세스 가능
let slice =  bs[1..< bs.endIndex]
slice // [1, 1, 2, 3, 5]
type(of: slice) // ArraySlice<Int>- 마지막 요소를 포함하여 두 번째 요소에서 시작하는 배열 조각을 가져오고 결과의 타입은 Array가 아니라ArraySlice.
- 
ArraySlice는 배열에 대한 뷰.
- 
ArraySlice타입은Array와 동일한 메소드가 정의되어 있어 마치 배열인 것처럼 사용 가능.
- 배열로 반환해야 한다면, ArraySlice에서 새로운 배열을 만들면 됨.
Array(fibs[1..<fibs.endIndex]) // [1, 1, 2, 3, 5]- Swift 배열은 Objective-C에 연결 가능.
- 
NSArray는 객체만 저장할 수 있기 때문에 Swift 배열의 요소를 연결할 수 있도록AnyObject로 변환할 수 있어야 함.
- 이로 인해 브리징이 클래스 인스턴스와 소수의 값으로 제한됨. Objective-C에 자동 브리징을 지원하는 타입(예 : Int, Bool 및 String)
- 이 제한 사항은 Swift 3에서는 더 이상 존재하지 않고 이제 Objective-C id타입이AnyObject대신Any로 Swift로 가져 오므로 Swift 배열이 이제NSArray에 연결 가능.
- 
NSArray는 항상 객체를 기대하기 때문에 컴파일러와 런타임은 자동으로 뒤에서 불투명 한 박스 클래스에 호환되지 않는 값을 래핑하고 반대 방향으로 래핑 해제도 자동으로 발생.
- Swift 값이 Objective-C 객체에 연결될 수 있게 되었으므로 앞으로 Swift 버전에서는 Swift 값 타입이 @objc프로토콜을 준수.
- Swift의 또 다른 주요 데이터 구조.
- 사전에는 해당 값이 있는 키가 존재.
- 중복 키는 지원되지 않음.
- 해당 키를 사용하여 값을 검색하는 것은 평균적으로 일정한 시간이 걸림.
- 반면 특정 요소에 대한 배열 검색은 배열의 크기에 따라 선형적으로 커짐.
- 배열과 달리 순서가 지정 안됨.
- 
for루프에서 쌍이 열거되는 순서는 정의되지 않음.
enum Setting {
  case text(String)
  case int(Int)
  case bool(Bool)
}
let defaultSettings: [String:Setting] = [
  "Airplane Mode": .bool(true),
  "Name": .text("My iPhone"),
]
defaultSettings["Name"] // Optional(Setting.text("My iPhone"))- 
subscripting을 사용하여 설정 값을 가져오며 조회는 항상 선택적 값을 반환.
- 
지정된 키가 존재하지 않으면 nil을 리턴.
- 
이것을 프로그램 충돌로 인한 Range를 벗어나는 액세스에 응답하는 배열과 비교됨. 
- 
이 차이점에 대한 이론적 근거는 배열 색인과 사전 키가 매우 다르게 사용된다는 것. 
- 
이미 배열 인덱스로 직접 작업해야 하는 경우는 드물고 배열 색인은 일반적으로 어떤 식으로든 배열에서 직접 파생(예: 0 .. <array.count와 같은 Range). 
- 
따라서 유효하지 않은 색인을 사용하는 것은 프로그래머 오류입니다. 
- 
반면에 사전 키가 사전 자체가 아닌 다른 소스에서 온 것은 매우 일반적(?). 
- 
배열과 달리 사전도 드문 드문 존재할 수 있고, 키 "이름" 아래에 값이 존재한다고 해서 키 "주소"가 존재하는지 여부는 알 수 없음. 
- 배열과 마찬가지로 let을 사용하여 정의 된 사전은 변경 불가능.
- 항목을 추가, 제거 또는 변경할 수 없음.
- 사전에서 값을 제거하기 위해 첨자를 사용하여 nil로 설정하거나removeValue(forKey:)를 호출 가능.
- 후자는 또한 삭제 된 값을 반환하거나 키가 존재하지 않으면 nil을 반환.
- 불변 사전을 변경하기 원한다면 사본을 생성해야 함.
var localizedSettings = defaultSettings
localizedSettings["Name"] = .text("Mein iPhone")
localizedSettings["Do Not Disturb"] = .bool(true)
let oldName = localizedSettings.updateValue(.text("Il mio iPhone"), forKey: "Name")
localizedSettings["Name"] // Optional(Setting.text("Il mio iPhone"))
oldName // Optional(Setting.text("Mein iPhone"))- 기본 설정 사전과 사용자 지정 설정을 결합하기 위한 방법.
extension Dictionary {
  mutating func merge<S>(_ other: S)
    where S: Sequence, S.Iterator.Element == (key: Key, value: Value) {
    for (k, v) in other {
      self[k] = v }
    }
  }
}- 다음 예에서와 같이 이 사전을 다른 사전에 병합하는 데 사용할 수 있지만 메서드 인수는 키-값 쌍 또는 다른 시퀀스의 배열일 수 있음.
var settings = defaultSettings
let overriddenSettings: [String:Setting] = ["Name": .text("Jane's iPhone")]
settings.merge(overriddenSettings)
settings
// ["Name": Setting.text("Jane\'s iPhone"), "Airplane Mode": Setting.bool(true)]- 또 다른 흥미로운 확장은 (키, 값) 쌍의 시퀀스로부터 사전을 생성하는 것.
extension Dictionary {
  init<S: Sequence>(_ sequence: S)
    where S.Iterator.Element == (key: Key, value: Value) { self = [:]
    self.merge(sequence)
  }
}
// All alarms are turned off by default
let defaultAlarms = (1..<5).map { (key: "Alarm \($0)", value: false) }
let alarmsDictionary = Dictionary(defaultAlarms)- 
Dictionary는Sequence이므로 배열을 생성하는map메서드가 이미 존재하지만 때로는 사전 구조를 손상시키지 않고 값을 변환하기를 원한다면,
- 
mapValues메소드는 우선 표준 맵을 호출하여 (키 변환 된 값) 쌍의 배열을 만든 다음 위에서 정의한 새 생성자를 사용하여 사전으로 다시 설정.
extension Dictionary {
  func mapValues<NewValue>(transform: (Value) -> NewValue)
    -> [Key:NewValue] {
      return Dictionary<Key, NewValue>(map { (key, value) in
        return (key, transform(value))
      })
  }
}
let settingsAsStrings = settings.mapValues { setting -> String in
  switch setting {
  case .text(let text): return text
  case .int(let number): return String(number)
  case .bool(let value): return String(value)
  }
}
settingsAsStrings // ["Name": "Jane\'s iPhone", "Airplane Mode": "true"]- 
사전은 해시 테이블. 
- 
사전은 각 키에 키의 hashValue에 따라 기본 저장소 배열의 위치를 할당.
- 
이것이 사전이 Hashable프로토콜을 준수하기 위해Key타입을 요구하는 이유.
- 
문자열, 정수, 부동 소수점 및 부울 값을 포함하여 표준 라이브러리의 모든 기본 데이터 타입이 이미 수행. 
- 
관련 값이 없는 열거형은 자동으로 Hashable을 준수.
- 
사용자 정의 타입을 사전 키로 사용하려면 Hashable적합성을 수동으로 추가해야 함.
- 
이를 위해서는 hashValue속성을 구현해야 하며,Hashable이Equatable을 확장하므로 사용자 타입에 대한==연산자 함수 오버로드가 발생.
- 
구현시 중요한 invariant가 있어야 하고 두 인스턴스가 동일하면 (==구현에 의해 정의 됨) 동일한 해시 값을 가져야 하지만, 반대의 경우는 사실이 아님(?)
- 
동일한 해시 값을 가진 두 인스턴스가 반드시 동일하게 비교되지는 않음. 
- 
많은 수의 해시 가능 타입 (문자열 등)은 본질적으로 무한 카디널리티를 가지지만 유한 수의 고유 해시 값만 있다는 점을 감안하면 의미가 있음(?) 
- 
If you want to use your own custom types as dictionary keys, you must add Hashable conformance manually. This requires an implementation of the hashValue property and, because Hashable extends Equatable, an overload of the == operator function for your type. Your implementation must hold an important invariant: two instances that are equal (as defined by your == implementation) must have the same hash value. The reverse isn’t true: two instances with the same hash value don’t necessarily compare equally. This makes sense, considering that there’s only a finite number of distinct hash values, while many hashable types (like strings) have essentially infinite cardinality. 
- 
중복 해시 값의 가능성은 사전이 충돌을 처리 할 수 있어야 한다는 것을 의미. 
- 
그럼에도 불구하고, 양호한 해시 함수는 콜렉션의 성능 특성을 보존하기 위해 최소의 충돌 횟수를 위해 노력이 필요. 
- 
즉, 해시 함수는 전체 정수 범위에 걸쳐 균일 한 분포를 생성해야 함. 
- 
모든 인스턴스에 대해 구현이 동일한 해시 값(예: 0)을 반환하는 극단적인 경우에는 사전의 조회 성능이 O(n)으로 저하됨. 
- 
좋은 해쉬 함수의 두 번째 특징은 빠르다는 것. 
- 
해시 값은 키가 삽입, 제거 또는 조회 될 때마다 계산된다는 점에 유의해야 함. 
- 
hashValue구현에 너무 많은 시간이 걸리면 O(1) 복잡도에서 얻은 이득을 모두 잃을 수 있음.
- 
이러한 요구 사항을 만족하는 좋은 해시 함수를 작성하는 것은 쉽지 않음. 
- 
Hashable자체 인 기본 데이터 타입으로 구성된 타입의 경우 멤버의 해시 값을XOR하면 좋은 출발점.
struct Person {
  var name: String
  var zipCode: Int
  var birthday: Date
}
extension Person: Equatable {
  static func ==(lhs: Person, rhs: Person) -> Bool {
    return lhs.name == rhs.name
      && lhs.zipCode == rhs.zipCode
      && lhs.birthday == rhs.birthday
  }
}
extension Person: Hashable {
  var hashValue: Int {
    return name.hashValue ^ zipCode.hashValue ^ birthday.hashValue
  }
}- 
이 기술의 한계 중 하나는 XOR이 해시되는 데이터의 특성에 따라 필요한 것보다 충돌 가능성을 높일 수 있는 대칭(즉, a ^ b == b ^ a).
- 
이를 피하기 위해 비트 회전을 믹스에 추가 할 수 있음(?) 
- 
One limitation of this technique is that XOR is symmetric (i.e. a ^ b == b ^ a), which, depending on the characteristics of the data being hashed, could make collisions more likely than necessary. You can add a bitwise rotation to the mix to avoid this. 
- 
마지막으로, 값 의미가 없는 타입을 사용할 때는 특히 주의. 
- 
(예 : 변경 가능한 객체)을 사전 키로 사용하면 해시 값이나 평등을 변경하는 방식으로 사전 키로 사용한 후 개체를 변형하면 사전에서 다시 찾을 수 없음. 
- 
사전은 이제 개체를 잘못된 슬롯에 저장하여 내부 저장소를 손상시키며 사전의 키가 사본의 저장소를 공유하지 않으므로 바깥에서 변형 할 수 없기 때문에 값 타입에 문제가 없음(?) 
- 
Finally, be extra careful when you use types that don’t have value semantics (e.g. mutable objects) as dictionary keys. If you mutate an object after using it as a dictionary key in a way that changes its hash value and/or equality, you’ll not be able to find it again in the dictionary. The dictionary now stores the object in the wrong slot, effectively corrupting its internal storage. This isn’t a problem with value types because the key in the dictionary doesn’t share your copy’s storage and therefore can’t be mutated from the outside. 
- 
표준 라이브러리의 세 번째 주요 컬렉션 타입은 Set.
- 
집합은 요소의 순서가 없는 집합이며 각 요소는 한 번만 나타남. 
- 
기본적으로 키와 값을 저장하지 않는 사전이라고 생각. 
- 
사전과 마찬가지로 집합은 해시 테이블로 구현되며 비슷한 성능 특성과 요구 사항을 갖음. 
- 
집합의 멤버쉽 값을 테스트하는 것은 상수 시간 연산이며, 사전 요소와 마찬가지로 집합 요소는 Hashable이어야 함.
- 
멤버쉽(배열에 대한 O(n) 연산)을 효율적으로 테스트해야하는 경우 배열 대신 집합을 사용하고 요소의 순서는 중요하지 않으며 컬렉션에 중복이 없는지 확인해야 할 때 사용. 
- 
집합은 ExpressibleByArrayLiteral프로토콜을 따르므로 다음과 같이 배열 리터럴로 초기화 가능.
let naturals: Set = [1, 2, 3, 2]
naturals // [2, 3, 1]
naturals.contains(3) // true
naturals.contains(0) // false- 모든 콜렉션과 마찬가지로, 집합은 이미 보았던 일반적인 작업을 지원.
- 
for루프의 요소를 반복하고, 맵핑하거나 필터링하고, 다른 모든 작업을 수행 가능.
- 이름에서 알 수 있듯이 집합은 수학 개념과 밀접하게 관련.
- 수학 수업에서 배운 모든 공통 집합 연산 지원.
let iPods: Set = ["iPod touch", "iPod nano", "iPod mini", "iPod shuffle", "iPod Classic"]
let discontinuedIPods: Set = ["iPod mini", "iPod Classic"]
let currentIPods = iPods.subtracting(discontinuedIPods) // ["iPod shuffle", "iPod nano", "iPod touch"]
let touchscreen: Set = ["iPhone", "iPad", "iPod touch", "iPod nano"]
let iPodsWithTouch = iPods.intersection(touchscreen)
// ["iPod touch", "iPod nano"]
var discontinued: Set = ["iBook", "Powerbook", "Power Mac"]
discontinued.formUnion(discontinuedIPods) // ()- 여기서 우리는 변형 된 formUnion변형을 사용하여 원래 집합을 변형 (결과적으로 var로 선언해야 함).
- 거의 모든 집합 연산은 변형되지 않은 형식과 변경하는 형식을 모두 가지고 있으며, 후자는 양식으로 시작(?)
- 더 많은 집합 연산을 위해서는 SetAlgebra프로토콜을 확인.
- 
집합은 표준 라이브러리에서 SetAlgebra를 준수하는 유일한 유형이지만 이 프로토콜은Foundation의IndexSet및CharacterSet에서도 채택됨.
- 
IndexSet은 양의 정수 값 집합.
- 
Set<Int>를 사용하여 이 작업을 수행 할 수 있지만 내부적으로 범위 목록을 사용하기 때문에 더 효율적인 저장 방법.
- 
1,000 요소가 있는 테이블 뷰가 있고 사용자가 선택한 행의 인덱스를 관리하기 위해 집합을 사용한다고 가정할 때 Set<Int>는 선택된 행의 수에 따라 최대 1,000 개의 요소를 저장해야 함.
- 
반면에 IndexSet은 연속 범위를 저장하므로 테이블의 처음 500 행을 선택하면 저장하기 위한 두 개의 정수(선택 항목의 상한 및 하한)만 사용됨.
- 
그러나 IndexSet의 사용자는 친숙한SetAlgebra및Collection인터페이스 뒤에 완전히 숨겨져 있기 때문에 내부 구조를 알 필요가 없음.
- 
범위를 직접 작업하지 않는 한, 즉 IndexSet속성은rangeView속성을 통해 뷰를 노출. 컬렉션 자체는 컬렉션.
- 
예를 들어 인덱스 집합에 몇 개의 범위를 추가 한 다음 그들이 개인 회원인 것처럼 색인. 
var indices = IndexSet()
indices.insert(integersIn: 1..<5)
indices.insert(integersIn: 11..<15)
let evenIndices = indices. lter { $0 % 2 == 0 } // [2, 4, 12, 14]- 
CharacterSet은 유니 코드 문자 집합을 저장하는 효율적인 방법.
- 특정 문자열에 특정 문자 하위 집합의 문자(예: 영숫자 또는 decimalDigits)만 포함되어 있는지 확인하는 데 자주 사용됨.
- 
CharacterSet은IndexSet과 달리 컬렉션이 아님.
- 사전과 세트는 호출자에게 노출되지 않을 때도 함수 내부에서 사용할 수 있는 매우 유용한 데이터 구조가 될 수 있음.
- 예를 들어, 시퀀스의 모든 고유 요소를 검색하기 위해 시퀀스에 확장을 작성하려는 경우 요소를 세트에 쉽게 넣을 수 있고 내용을 반환 할 수 있지만, 안정적이지는 않음.
- 세트에 정의 된 순서가 없기 때문에 입력 요소가 결과에서 재정렬.
- 이 문제를 해결하기 위해 내부 저장 세트를 사용하여 순서를 유지 관리하는 확장 프로그램을 작성 가능.
extension Sequence where Iterator.Element: Hashable {
  func unique() -> [Iterator.Element] {
    var seen: Set<Iterator.Element> = []
    return filter {
      if seen.contains($0) {
        return false
      } else {
        seen.insert($0)
        return true
      }
    }
  }
}
[1,2,3,12,1,3,4,5,6,4,6].unique() // [1, 2, 3, 12, 4, 5, 6]- 위의 방법을 사용하면 원래 순서 (요소가 Hashable이어야한다는 제약 조건)를 유지하면서 시퀀스의 모든 고유 요소를 찾을 수 있음.
- 클로저를 통과할 때 클로저의 외부에서 정의된 변수를 참조하여 클로저의 여러 반복에 대해 상태를 유지.
- 
Range는 값의 하한값과 상한값으로 정의되는 값의 간격.
- 
..<상한을 포함하지 않는 반 개방 범위
- 
...두 경계를 포함하는 닫힌 범위
// 0 to 9, 10 is not included
let singleDigitNumbers = 0..<10
// "z" is included
let lowercaseLetters = Character("a")...Character("z")- Ranges seem like a natural fit to be sequences or collections, so it may surprise you to learn that they’re neither — at least not all of them are.
- 표준 라이브러리에는 네 가지 범위 유형.
| . | Half-open range | Closed range | 
|---|---|---|
| Elements are Comparable | Range | ClosedRange | 
| Elements are Strideable (with integer steps) | CountableRange | CountableClosedRange | 
- 
테이블의 열은 위에서 본 두 범위 연산자에 해당하며 [Countable] Range (반 열림) 또는 [Countable] ClosedRange (닫힘)를 각각 만듦. 
- 
반 개방 및 폐쇄 형 모두 범위를 가짐. - 반 개방 범위만 비어 있는 간격을 나타낼 수 있음(5 .. <5와 같이 하한 및 상한이 동일한 경우).
- 닫힌 범위만 요소 타입이 나타낼 수있는 최대 값을 포함 가능(예 : 0 ... Int.max).
- 반 개방 범위에는 항상 범위에서 가장 높은 값보다 큰 표현 가능한 값이 하나 이상 필요.
 
- 
Swift 2에서 모든 Range는 기술적으로 반 개방 범위였고, 연산자로 작성된 경우에도Range가 최대 표현 가능 값을 포함 안됨.
- 
표준 라이브러리 HalfOpenInterval및ClosedInterval을 사용하여 구제 가능한 표준 라이브러리 Swift 3에서 제거됨(?)
- 
테이블의 행은 요소 타입이 Comparable프로토콜(최소 요구 사항)만을 준수하는 "일반"Range와Strideable이고 요소 사이에 정수 단계를 사용하는 타입을 구분.
- 
후자의 범위만이 모음으로,이 장에서 본 모든 강력한 기능을 상속받음. 
- 
Swift는 이러한 반복 가능한 요소를 반복 할 수 있기 때문에 가능한 범위를 셀 수 있음. 
- 
유효 범위는 정수 및 포인터 타입을 포함하지만 부동 소수점 타입은 포함하지 않음. 
- 
타입의 Stride에 대한 정수 제약 때문.
- 
연속적인 부동 소수점 값을 반복할 필요가 있다면 stride(from:to:)와stride(from:through:by)함수를 사용하여 그러한 시퀀스를 생성 가능.
- 
Swift calls these more capable ranges countable because only they can be iterated over. Valid bounds for countable ranges include integer and pointer types, but not floating-point types, because of the integer constraint on the type’s Stride. If you need to iterate over consecutive floating-point values, you can use the stride(from:to:by) and stride(from:through:by) functions to create such a sequence. 
- 
즉, 일부 범위에서는 반복 할 수 있지만 다른 범위에서는 반복 할 수 없음(?) 
- 
예를 들어 위에서 정의한 Character값의 범위는 시퀀스가 아니므로 다음과 같이 작동 안함.
for char in lowercaseLetters { // ...
}
// Error: Type 'ClosedRange<Character>' does not conform to protocol 'Sequence'- 한편, 다음은 아무런 문제가 되지 않는 집계 가능한 정수 범위이므로 컬렉션.
singleDigitNumbers.map { $0 * $0 } // [0, 1, 4, 9, 16, 25, 36, 49, 64, 81]- 표준 라이브러리에는 현재 CountableRange및CountableClosedRange에 대해 별도의 타입이 필요.
- 이들은 뚜렷한 타입이 아니며, 일반 매개 변수가 필수 제약 조건을 충족시키는 조건에서 컬렉션 준수를 추가하는 Range및ClosedRange에 대한 확장.
// Invalid in Swift 3
extension Range: RandomAccessCollection
where Bound: Strideable, Bound.Stride: SignedInteger
{
  // Implement RandomAccessCollection
}- 
Swift 3의 타입 시스템은 이 아이디어를 표현할 수 없기 때문에 별도의 타입이 필요. 
- 
Swift 4에서는 조건부 적합성 지원이 예상되며 CountableRange및CountableClosedRange는 착륙시(?)Range및ClosedRange로 변환됨.
- 
Alas, Swift 3’s type system can’t express this idea, so separate types are needed. Support for conditional conformance is expected for Swift 4, and CountableRange and CountableClosedRange will be folded into Range and ClosedRange when it lands. 
- 
반 개방 Range와 닫힌ClosedRange사이의 구별이 유지 될 수 있으며 때로는 이전보다 훨씬 더 세게 작업할 수 있음.
- 
Range<Character>를 취하고 위에 작성한 닫힌 문자 범위를 전달하고자 하는 함수가 있다고 가정할 때, 가능하지 않다는 사실을 알면 놀라 수 있음(?)
- 
설명 할 수 없게도 ClosedRange를Range로 변환 할 방법이없는 것 같음(?)
- 
하지만 왜? 닫힌 범위를 동등한 반 개방 범위로 바꾸려면 원래 범위의 상한 다음에 오는 요소를 찾아야 하고, 요소가 Strideable이 아니면 가능한 셀 범위에 대해서만 보장되는 경우가 아니면 불가능(?)
- 
The distinction between the half-open Range and the closed ClosedRange will likely remain, and it can sometimes make working with ranges harder than it used to be. Say you have a function that takes a Range and you want to pass it the closed character range we created above. You may be surprised to find out that it’s not possible! Inexplicably, there appears to be no way to convert a ClosedRange into a Range. But why? Well, to turn a closed range into an equivalent half-open range, you’d have to find the element that comes after the original range’s upper bound. And that’s simply not possible unless the element is Strideable, which is only guaranteed for countable ranges. 
- 
이는 해당 함수의 호출자가 올바른 유형을 제공해야 함을 의미. 
- 
함수가 Range를 예상하는 경우...연산자를 사용하여 만들 수 없음.
- 
실제로 대부분의 범위는 정수를 기반으로 하기 때문에 실제로 얼마나 큰 제한이 있는지는 확실하지 않지만 직관적이지는 않음. 
- 
This means the caller of such a function will have to provide the correct type. If the function expects a Range, you can’t use the ... operator to create it. We’re not certain how big of a limitation this is in practice, since most ranges are likely integer based, but it’s definitely unintuitive.