Soroush khanlou sequence collection header

Swift의 Sequence와 Collection에 대해 알아야 하는것들

소 개

저는 오늘 강연을 맡은 Soroush입니다. 이번 강연에서는 많은 분이 궁금해하시는 Sequence와 Collection에 대해 알아보도록 하겠습니다.

정렬된 요소들의 목록이 필요할 경우, Swift 언어에서는 대부분 배열(Array) 타입을 사용할 것입니다. Swift의 Array, Collection 프로토콜, Collection 객체들은 프로토콜 계층(hierarchy), associated type, 편의를 제공하는 다양한 요소 등을 활용해 세심하게 설계되었습니다. 이 강연에서는 이들 프로토콜에 대해서 알아보고 원하는 기능을 구현할 때 이들을 어떻게 활용할 수 있는지 살펴보도록 하겠습니다.

Sequence와 Collection에 관한 모든 것

layer_of_sequence

위의 계층 구조를 보면, 모든 것들이 Sequence 프로토콜을 기반으로 작성되었다는 사실을 알 수 있습니다. Array 타입을 사용할 때 Sequence가 대부분의 기능을 제공합니다. map, filter 뿐만 아니라, Sequence 안에서 특정 조건을 만족하는 첫 번째 요소를 찾는 기능까지, 모두 다 Sequence 프로토콜 안에 정의되어 있습니다. Sequence가 가장 단순한 것이고, 나머지는 Sequence를 기반으로 작성된 것들입니다. 우선, Sequence에 대해서 살펴본 다음, Collection과 BidirectionalCollection에 대해 알아보도록 하겠습니다. (_이 강연에서는 RandomAccessCollection, RangeReplaceableCollection, MutableCollection_에 대해서는 다루지 않습니다)

Sequence

Sequence부터 시작해볼까요? Sequence는 요소들의 목록입니다. Sequence는 두 가지 중요한 특징이 있습니다. 첫째, 무한하거나 유한합니다. 둘째, 한 번만 이터레이트(iterate) 할 수 있습니다. 한 번 이상 이터레이트할 수도 있지만 한 번 이상 이터레이트가 가능할지에 대해서는 장담할 수 없습니다.

protocol Sequence {
    associatedtype Iterator : IteratorProtocol where Iterator.Element == Element
    func makeIterator() -> Iterator
}

Sequence 프로토콜은 두 개의 요소로 구성됩니다. 우선, Iterator라는 associated type이 있습니다. 이 associated type은 IteratorProtocol을 준수(conform)하고 있습니다. 다른 하나는 Iterator를 만드는 makeIterator라는 함수이며, 앞서 선언한 Iterator 타입을 반환합니다.

IteratorProtocol

IteratorProtocol에 대해 좀 더 자세히 살펴봅시다. IteratorProtocol은 Sequence와 매우 유사합니다. Element라는 associated type을 가지고 있으며, 이것은 요소를 추가하거나 이터레이트를 수행할 때 사용하는 타입을 나타냅니다. 그리고 next라는 함수가 있는데 이것은 다음(next) 요소를 반환합니다.

protocol IteratorProtcol {
    associatedtype Element
    mutating func next() -> Element?
}

IteratorProtocol은 Sequence의 근간이며, Sequence는 프로그래밍할 때 필요한 기초적인 기능들을 제공합니다. 이해를 돕기 위해, Sequence를 이용해 링크드 리스트를 구현해보겠습니다. 링크드 리스트를 선택한 이유는 Sequence에 아주 적합한 데이터 구조이기 때문입니다. 링크드 리스트는 다음번 요소를 찾고, 또 다음번 요소를 찾고, 결국 마지막 요소를 찾을 때까지 이러한 작업을 반복하는 데이터 구조입니다.

예제: 링크드 리스트

indirect enum LinkedListNode<T> {
    case value(element: T, next: LinkedListNode<T>)
    case end
}

링크드 리스트 예제 코드입니다. 첫 번째 요소는 두 번째 요소를 가리키고 있고, 두 번째 요소는 세 번째 요소를, 세 번째 요소는 마지막 요소를 가리키고 있습니다. Swift에서 Sequence나 링크드 리스트를 정의하는데 여러 방법이 있는데, 저는 enumeration으로 구현해 보았습니다. enumeration에서 사용되는 링크드 리스트 타입을 나타내기 위해 제너릭 타입 T를 선언했습니다. 추가적으로 indirect 키워드가 사용되었는데 _indirect_는 enumeration 내부에서 LinkedListNode 타입을 사용한다는 의미입니다.

이런 개발 뉴스를 더 만나보세요

링크드 리스트를 구성하기 위해 두 개의 case 문을 작성했습니다. case value에서는 값을 가지고 있는 경우를 처리하며 값의 타입은 T입니다. 이것은 다음 요소가 값이 있다는 것을 보장하며, 다음 요소의 값은 전체 링크드 리스트에 사용된 타입과 같은 타입이라는 것을 보장하고 있습니다. 그다음 요소에서도 같은 작업이 진행되며 마지막에는 end case를 만나게 되는데 이것은 링크드 리스트가 끝났다는 것을 의미합니다.

하지만, 이 코드만으로는 아직 아무것도 할 수 없습니다. 즉 각각의 요소를 탐색(enumerate)할 수 없으며 for-in 루프도 사용할 수 없고, map, filter 역시 사용할 수 없습니다. 이러한 기능들을 이용하려면 링크드 리스트가 Sequence 프로토콜을 준수해야 합니다.

extension LinkedListNode: Sequence {
    func makeIterator() -> ??? {
        return ???
    }
}

Sequence 프로토콜을 준수하려면 makeIterator 함수를 구현해야하는데, 아직은 우리가 작성하고 있는 링크드 리스트에서 Iterator 타입이 무엇인지 알 수 없기 때문에 ???로 표기했습니다. 위의 코드에서 associated type(Iterator 타입)이 빠져있는 것을 눈치채셨나요? Swift 덕분에 makeIterator()의 반환 값 타입만 정의하면 됩니다. 이제, 이터레이션(iteration)을 위해 특정 타입의 객체를 작성할 차례입니다.

struct LinkedListIterator<T>: IteratorProtocol {

    var current: LinkedListNode<T>

}

제너릭 타입 T를 가진 LinkedListIterator를 작성했는데, next 함수를 호출할 때마다 T를 반환합니다. 즉, 그 값은 T 타입이며 링크드 리스트로 만들고자 하는 어떤 종류의 타입이든 모두 사용할 수 있다는 의미입니다. Sequence에 관해 설명할 때 Iterator가 IteratorProtocol을 준수하고 있었는데 기억나시나요? Iterator는 Sequence 안에서 이동하기 때문에 커서(cursor)와 유사하다고 할 수 있습니다. Sequence 안에서 현재 어디쯤 있는지에 대한 상태 정보를 가지고 있지요. 코드에서는 이 상태를 current라는 속성을 선언하여 표현했는데 링크드 리스트 안에서 지금 가리키고 있는 노드를 의미합니다. 이 노드 역시 Iterator처럼 제너릭 타입인 T로 정의했습니다.

struct LinkedListIterator<T>: IteratorProtocol {

    var current: LinkedListNode<T>

    mutating func next() -> T? {

    }
}

다음으로 T?를 반환하는 next 함수를 구현해보도록 하겠습니다. nil을 반환하기 전까지는 값을 반환합니다. nil을 반환한다는 것은 Sequence의 마지막을 의미하며, 값을 반환하지 않아야 하므로 마지막에 nil을 반환하는 것입니다. 우리의 링크드 리스트는 enum 으로 구현했기 때문에 선택할 수 있는 옵션이 그다지 많지 않습니다. enum 내부에서는 어떤 일이 일어나는지 알아볼까요?

current 속성 값을 처리하기 위해 switch 구문을 사용했습니다. 두 개의 case 문을 작성해보았는데 첫 번째 case는 마지막 링크드 리스트인지를 확인하고 nil을 반환합니다. 쉽지요?

var current: LinkedListNode<T>

mutating func next() -> T? {
    switch current {

    case .end:
        return nil
    }
}

만약 elementnext를 가진 value case에 해당하면 element를 반환해야 할 것입니다. element를 반환한다고 했는데 그러면 next 값은 어떻게 처리해야 할까요?

var current: LinkedListNode<T>

mutating func next() -> T? {
    switch current {
    case let .value(element, next):
        return element
    case .end:
        return nil
    }
}

nextLinkedListNode가 가리키는 다음 번째 요소를 나타내기 때문에 current 속성에 next를 할당하는 코드를 추가했습니다.

var current: LinkedListNode<T>

mutating func next() -> T? {
    switch current {
    case let .value(element, next):
        current = next
        return element
    case .end:
        return nil
    }
}

이렇게 코드를 변경하면 다음 번에 next 함수가 호출되면 currentnext 값으로 업데이트되고 새로운 값을 반환하거나 Sequence의 마지막을 알리는 nil을 반환할 것입니다. 지금까지 Sequence 프로토콜을 준수하는데 필요한 것을 모두 살펴보았습니다. 이제 우리의 Iterator 타입이 LinkedListIterator라는 사실을 알았으니 코드에 반영해봅시다.

extension LinkedListNode: Sequence {
    func makeIterator() -> LinkedListIterator<T> {
        return LinkedListIterator(current: self)
    }
}

링크드 리스트의 헤드에서 시작하도록 설정했습니다. self는 링크드 리스트의 헤드가 될 것이며, 맨 먼저 시작될 것입니다. 이제 이 타입이 Sequence 타입을 준수하는데 필요한 모든 과정을 마쳤습니다. 이렇게 완성된 최종 소스 코드는 다음과 같습니다:

indirect enum LinkedListNode<T> {
    case value(element: T, next: LinkedListNode<T>)
    case end
}
  
extension LinkedListNode: Sequence {
    func makeIterator() -> LinkedListIterator<T> {
        return LinkedListIterator(current: self)
    }
}

struct LinkedListIterator<T>: IteratorProtocol {

    var current: LinkedListNode<T>

    mutating func next() -> T? {
        switch current {
        case let .value(element, next):
            current = next
            return element
        case .end:
            return nil
        }
    }
}

Sequence 프로토콜을 구현하면 for 루프, map, filter와 같은 기능을 이용해 이터레이트를 할 수 있습니다.

우리가 작성한 Iterator를 그림으로 표현하면 다음과 같습니다. 3 개의 요소를 가진 링크드 리스트입니다.

a pointer to the first value

let second = LinkedListNode.value(element: "C", next: LinkedListNode.end)
let first = LinkedListNode.value(element: "B", next: second)
let head = LinkedListNode.value(element: "A", next: first)

var iterator = LinkedListIterator<String>(current: head)
while let value = iterator.next() {
    print(value)
}
// "A"
// "B"
// "C"

Iterator를 생성하면 첫 번째 요소, 즉 current가 가리키고 있는 값을 가리키게 됩니다. current를 통해 링크드 리스트의 헤드에 접근한 다음, iterator.next를 호출하면 A를 반환할 것입니다. 이후에는 current가 링크드 리스트의 두 번째 요소를 가리키고 있을 것입니다. next 함수를 다시 한번 호출하면 B를 반환하고 다시 current는 그다음 번 요소를 가리키게 됩니다. 이 과정은 링크드 리스트 종료를 알리는 nil을 반환할 때까지 반복되며 이후에 next 함수를 호출할 경우 항상 nil이 반환될 것입니다. 만약 Sequence에 또 다른 기능을 추가하고 싶다면 어떻게 해야 할까요?

어떤 객체를 여러 개 가지고 있고, 이 객체가 몇 개나 되는지 확인하고 싶은 경우, 보통 아래와 같은 코드를 작성합니다.

let numberOfAdmins = users.filter({ $0.isAdmin }).count // => fine

꽤 괜찮은 코드입니다. filter를 이용해 배열의 모든 요소를 점검하여 조건에 맞는 요소가 몇 개나 되는지 파악하고 마지막에 count를 호출했습니다. 하지만, 이 방법은 임시 배열이 생성되는 문제가 있습니다. 개수 파악을 위해 생성된 배열은 사용 후 바로 버려집니다. 이것은 이상적인 방법이 아니죠.

let numberOfAdmins = users.count({ $0.isAdmin }) // => great

저는 users.count와 같은 형태의 코드를 이용해 이 문제를 해결하고 싶습니다. 루비 언어를 사용해보셨다면 Enumerable 모듈에서 이와 유사한 함수를 보셨을 겁니다. 이 방법이 더 괜찮아 보이는군요. Sequence에 이 기능을 추가해보겠습니다.

extension Sequence {

    func count(_ shouldCount: (Element) -> Bool) -> Int {

        var count = 0

        for element in self {

           if shouldCount(element) {

               count += 1

           }

        }

        return count

   }

}

Sequence 타입을 확장할 수 있는 extension 구문을 이용해 count라는 함수를 추가했습니다. 함수는 Int 타입을 반환할 것입니다. 이 함수에는 shouldCount라는 매개변수도 정의했습니다. 매개변수가 어떤 역할을 하는지 한눈에 알 수 있는 이름이지요? 이 코드에서 가장 주목해야 할 사항은 Sequence에 있는 요소의 타입을 Element를 이용해 참조할 수 있다는 사실입니다. 이를 통해 요소가 String 타입인지, user 객체인지 파악할 수 있지요. Sequence이기 때문에 이터레이트를 할 수 있으며, for-in 루프를 사용해 각 요소에 접근할 수 있습니다.

여기서는 count 기능이 필요한데 이를 위해 count 속성을 선언했습니다. count 변수는 0에서 시작하고 마지막에 count가 반환됩니다. shouldCount(element)가 true이면 user가 admin이라는 뜻이므로 count가 1 증가합니다. 상당히 직관적이지요?

이상으로 Sequence의 기능을 확장하는 방법에 대해 알아보았습니다. 다른 타입에서처럼 extension 구문을 이용해 기능을 확장할 수 있으며 Sequence 내부에서 사용되는 타입은 Element를 통해 참조할 수 있습니다. 필요한 기능은 무엇이든 이곳에 추가할 수 있습니다. 저는 거의 모든 프로젝트에서 이 기능을 확장하여 사용하고 있고, 언젠가 Swift 표준 라이브러리에서도 이런 기능이 기본적으로 제공되길 희망합니다.

each_pair

Sequence에 “eachPair”라는 이름의 기능을 하나 더 추가해보겠습니다. 이것은 연속된 요소 중 인접한 두 개의 요소를 튜플로 구성합니다. Sequence가 숫자들로 구성되어 있고, 이들 숫자 간의 차이를 알고 싶은 경우 이 함수를 유용하게 사용할 수 있습니다. 이 함수를 이용하면 숫자를 두 개씩 서로 묶어서 그 차이를 계산할 수 있습니다. 또한, 여러 개의 뷰가 존재하고, 이들 사이에 오토 레이아웃 설정을 추가할 경우에도 이 기능을 유용하게 사용할 수 있을 것입니다. 뷰를 두 개씩 묶은 다음, 서로 간의 constraint를 설정하고 그다음 묶음에 대해서도 같은 동작을 수행할 수 있습니다.

zip(sequence, sequence.dropFirst()) // Sequence<(T, T)>

Swift 표준 라이브러리를 이용해 이 기능을 구현해보겠습니다. Sequence로 시작해볼까요?

zip_sequence

두 개씩 그룹으로 묶는 효과를 얻기 위해서는 Sequence를 복제할 필요가 있습니다.

zip_sequence_copy

그리고, 복제한 것에서 첫 번째 요소를 제거합니다.

zip_dropfirst

그런 다음 이들에 대해 zip을 수행하면 더 작은 요소를 가진 Sequence를 기준으로 튜플이 구성되며, 짝을 찾지 못한 마지막 요소인 D는 제거됩니다.

zip_droplast

두 개씩 그룹으로 묶이면 아래와 같은 모습이 됩니다.

zip_making_pairs

그 결과, 요소들은 두 개씩 짝을 이루어 튜플로 구성된 상태가 됩니다.

zip_result

두 개의 Sequence에 대해 zip을 적용하여 요소들을 두 개씩 묶었습니다. 이 기능을 Sequence에 추가하여 map, filter 등과 결합해서 사용하고 싶네요.

extension Sequence {
  
    func eachPair() -> AnySequence<(Iterator.Element, Iterator.Element)> {

      return AnySequence(zip(self, self.dropFirst()))
  
  }

}

앞에서 사용했던 extension을 이용해 Sequence에 eachPair 함수를 추가했고, 이 함수는 두 개의 요소로 구성된 튜플 Sequence를 반환합니다. 내부에서는 zip 함수가 모든 일을 처리합니다. AnySequence가 사용된 것이 보이시나요? 이것은 타입 이레이저(type eraser)입니다. (Gwen Weston의 강연을 참고자료로 추천합니다). 하지만, 위의 코드 그대로 컴파일하면 에러 메시지가 나오네요. 메시지 내용은 다음과 같습니다. Argument type Self.SubSequence does not conform to expected type Sequence. Sequence는 SubSequence라는 또 하나의 associated type을 가지고 있습니다. Swift 컴파일러의 제약으로 인해, _모든 SubSequence는 Sequence이기도 하다_라고 표현할 수 없습니다. 제약사항은 우리가 직접 구현해야 합니다.

extension Sequence where Self.SubSequence: Sequence {
  
    func eachPair() -> AnySequence<(Iterator.Element, Iterator.Element)> {

      return AnySequence(zip(self, self.dropFirst()))
  
  }

}

where 구문을 통해 제약사항을 추가했는데 그 내용은 다음과 같습니다. “SubSequence를 사용할 예정인데 그 SubSequence는 Sequence가 될 것이다.” 다시 한번 컴파일을 했더니 또 다른 에러 메시지가 나오네요. Cannot convert return expression of type AnySequence<(Self.Element, Self.SubSequence.Element> to return type AnySeqence<(Self.Element, Self.Element)>. 에러 메시지가 엄청 길지만 중요한 부분은 두 번째에 나오는 Self.SubSequence.ElementSelf.Element가 일치하지 않는다는 내용입니다. 즉, SubSequence가 있고, SubSequence는 Sequence이기도 하다는 사실을 알고 있지만 SubSequence 안의 타입이 Sequence 타입과 같다는 것은 모른다는 뜻입니다. 이 부분에 대해 제약사항을 추가해보겠습니다.

extension Sequence 
  
  where Self.SubSequence: Sequence,

  Self.SubSequence.Element == Self.Element {
  
    func eachPair() -> AnySequence<(Element, Element)> {

      return AnySequence(zip(self, self.dropFirst()))
  
  }

}

Self.SubSequence.Element == Self.Element를 추가했습니다. 이제 정상적으로 컴파일이 되네요. eachPair를 원하는 곳에 사용할 수 있게 되었는데 구현과정에서도 나타났듯이 Swift의 프로토콜과 Collection 시스템에서 associated type을 처리하는 부분은 상당히 힘든 일입니다. 구현과정은 힘들었지만 그래도 원하는 결과를 얻었으니 좋네요. 지금까지 Sequence에 관한 주요 사항들을 살펴봤습니다.

Collection

이제 Collection을 살펴보도록 하겠습니다. 모든 Collection은 Sequence를 상속받아 구현되었으며, Collection은 Sequence가 지닌 두 가지 문제점을 해결했습니다. 우선, 모든 Collection은 유한(finite)합니다. 몇 개의 요소로 구성되어 있는지 항상 알 수 있다는 뜻이지요. 절대로 무한(infinite)해질 수 없으며, 원하는 만큼 이터레이트를 할 수 있습니다. Sequence에서는 오직 한 번만 이터레이트가 가능했었던 것을 생각하면 멋진 기능이지요?

Collection 프로토콜은 어떻게 작성되어 있는지 한번 살펴보겠습니다.

protocol Collection: Sequence {
  associatedtype Index: Comparable
  var startIndex: Index
  var endIndex: Index
  subscript(position: Index) -> Element { get }
  func index(after i: Index) -> Index
}

새로운 associated type으로 Index가 보이네요. 배열을 사용해봤다면 이 IndexInt로 이해해도 됩니다. 모든 Collection은 Index를 가지고 있기 때문에 Dictionary 타입도 자신의 Index를 가지고 있습니다. 그리고, Index는 내부에서 사용되는 것이기 때문에 이것을 직접 다루지 않아도 됩니다. Index가 있으니 시작과 끝을 알리는 startIndexendIndex도 필요합니다. Array에서는 startIndex가 0이지만, ArraySlice라면, 다른 값이 될 것입니다. 인덱스에 해당하는 요소를 얻고 싶을 때는 subscript 함수를 사용하면 됩니다. 또한, Swift가 제공하는 index(after:)를 통해 현재 인덱스의 다음 인덱스에 대한 정보도 얻을 수 있습니다.

Collection의 forEach 함수를 구현할 경우 아래와 같이 소스코드를 작성할 수 있습니다.

func forEach(_ block: (Element) -> Void) {
    var currentIndex = self.startIndex
    while currentIndex < self.endIndex {
        block(self[currentIndex])
        currentIndex = self.index(after: currentIndex)
    }
}

Swift 표준 라이브러리에서 forEach 함수를 제공하고 있기 때문에 여러분이 이것을 직접 구현할 필요는 없지만, 만약 구현해야 한다면 위의 예제처럼 구현할 수 있습니다. currentIndexendIndex보다 작은지 검사하는 코드가 보이시나요? 이러한 비교가 가능한 이유는 associated type인 Index가 Comparable을 준수하기 때문입니다. currentIndex가 endIndex보다 작다면, block(self[currentIndex])를 호출하고, currentIndex 값을 다음번 인덱스 값으로 갱신합니다. 이 과정은 계속 반복되다가 endIndex를 만날 때 종료될 것입니다. Sequence 덕분에 startIndex, endIndex, subscript(position:), index(after:) 등 4가지 함수는 우리가 직접 구현하지 않아도 사용할 수 있습니다.

struct APIErrorCollection {
    fileprivate let _errors: [APIError]
}

errorCollection.contains({ $0.id == "error.item_already_added" })

    // won't compile!

다음으로 Colletion처럼 동작하는 APIErrorCollection 타입을 만들어보도록 하겠습니다. 타입 내부에는 APIError라는 배열 속성이 있는데 private로 선언되어 있습니다. 자, 그럼 APIErrorCollection를 Collection으로 만들어볼까요?

struct APIErrorCollection {

    fileprivate let _errors: [APIError]

}

extension APIErrorCollection: Collection {

    var startIndex: Int {

        return _errors.startIndex

    }

    var endIndex: Int {

        return _errors.endIndex

    }

    func index(after index: Int) -> Int {

        return _errors.index(after: index)
    }

    subscript(position: Int) -> APIError {

        return _errors[position]

    }

}

errorCollection.contains({ $0.id == "error.item_already_added" })

    // compiles!

extension을 이용해 Collection 프로토콜을 준수하는 APIErrorCollection을 만들었습니다. Collection 프로토콜의 내부속성들이 어떻게 구현되었는지 살펴볼까요? startIndex는 _errors.startIndex를 반환하고, endIndex는 _errors.endIndex를 반환합니다. 나머지 index(after:), subscript(position:)도 _errors를 처리하는 구현코드를 가지고 있습니다. 예제 코드의 마지막 부분에서는 APIErrorCollection안에 특정 에러가 있는지 검사하는데 Collection은 Sequence이므로, map, filter 등을 결합해서 사용할 수 있습니다.

BidirectionalCollection

BidirectionalCollection은 한 가지만 제외하고는 Collection과 매우 유사합니다. Collection이 Sequence를 상속받은 것처럼, BidirectionalCollection은 Collection을 상속받았지만 정방향뿐만 아니라 역방향으로도 탐색할 수 있다는 차이가 있습니다. 역방향으로 이동하는 기능을 위해 index(before:) 함수가 추가되었습니다.

protocol Collection {

  //...

  func index(after index: Index) -> Index

}


protocol BidirectionalCollection: Collection {

  func index(before index: Index) -> Index

}

last 속성을 통해 BidirectionalCollection가 제공하는 기능을 무료로 이용할 수 있습니다. 이 속성은 Collection의 맨 마지막 요소를 반환하거나 Collection이 empty이면 nil을 반환합니다. 맨 마지막 요소를 찾을 때까지 Collection을 탐색하면 시간이 많이 소요됩니다. 때문에, 맨 마지막 요소에서 시작해 해당 요소를 반환하는 방법을 택했고, Collection이 empty인지 확인하는 코드도 추가했습니다. empty이면 nil을 반환합니다. endIndex를 찾은 다음, 그 endIndex 앞의 index에 해당하는 요소를 찾아 반환합니다.

var last: Iterator.Element? {

    guard !self.isEmpty else { return nil }

    let indexOfLastItem = self.index(before: self.endIndex)

    return self[indexOfLastItem]

}

BidirectionalCollection은 역방향 이동을 쉽게 만들어줍니다.

다른 프로토콜들

Sequence와 관련해 아직 설명하지 않은 프로토콜이 3개 더 있습니다.

  • RandomAccessCollection은 빠르게 요소에 접근할 수 있게 도와줍니다. 순서대로 하나씩 요소에 접근하는 대신, 해당 요소로 바로 접근할 수 있습니다.
  • RangeReplaceableCollection은 Collection 중간의 요소를 다른 것으로 교체할 수 있는 기능을 제공합니다.
  • MutableCollection은 요소들의 값을 설정할 수 있는 기능을 제공합니다.

Swift 문서 또는 Swiftdoc.org를 통해 이들 프로토콜에 대한 추가정보를 얻을 수 있으며, 이 강연에서 배운 사항들이 이들 프로토콜을 이해하는 데 도움이 될 것입니다.

질의응답

질문: Swift에서 커스텀 Iterator 타입을 추가하고 나면, 어떤 상황에 사용하면 좋을까요? 대부분의 경우 Iterator 타입은 내부에서 사용합니다. 여러 장으로 구성된 페이지들을 스와이프(swipe)하는 경우를 생각해보죠. 스와이프가 발생할 때 Collection의 인덱스를 이용해 페이지에 대한 정보를 처리하는 대신 Iterator를 이용할 수 있습니다. 추가로 스와이프가 발생하면 next 함수를 호출해서 정보를 처리할 수 있으며 Iterator 타입을 사용함으로써 코드 가독성이 향상됩니다.

컨텐츠에 대하여

2016년 3월에 진행한 try! Swift Tokyo 행사의 강연입니다. 영상 녹화와 제작, 정리 글은 Realm에서 제공하며, 주최 측의 허가 하에 이곳에서 공유합니다.

Soroush Khanlou

Soroush Khanlou is a New York-based iOS consultant. He’s written apps for the New Yorker, David Chang’s Ando, Rap Genius, and non-profits like Urban Archive. He blogs about programming at khanlou.com, mostly making fun of view controllers. In his free time, he runs, bakes bread and pastries, and collects suitcases.

4 design patterns for a RESTless mobile integration »

close