apple/DesignPattern & Architecture

[Swift] Iterator Pattern

lgvv 2022. 5. 13. 17:46

Iterator Pattern

 

✅ Iterator Pattern

 

아래의 문서를 구입하여 영어 문서를 번역하고 이해한 것을 바탕으로 글을 작성하고 있습니다.

https://www.raywenderlich.com/books/design-patterns-by-tutorials/v3.0/chapters/13-iterator-pattern

 

Design Patterns by Tutorials, Chapter 13: Iterator Pattern

The iterator pattern provides a standard way to loop through a collection. Use the iterator pattern when you have a class or struct that holds a group of ordered objects, and you want to make it iterable using a “for in” loop.

www.raywenderlich.com

반복자 패턴은 컬렉션을 반복하는 표준 방법을 제공하는 패턴입니다. 이 패턴에는 두가지 유형이 포함됩니다.

 

이터레이터 패턴의 2가지 유형

 

1.  Swift는 IteratorProtocol를 사용하여 반복할 수 이있는 for in loop를 정의합니다.

2. 반복자 개체는 사용할 수 있도록 만들려는 유형입니다. 그러나 Iterator Protocol에 직접 따르는 대신 Sequence(시퀀스)에 따를 수 있으며, Sequence(시퀀스) 자체는 Iterator Protocol(시퀀스 프로토콜)을 준수합니다. 그렇게 하면 map, filter 등을 포함한 많은 고차 기능들을 for free(무료, 쉽게) 얻을 수 있습니다.

 

** what does "for free" mean? 

즉, 이러한 유용한 기본 제공 함수는 Sequence를 준수하는 모든 개체에서 사용할 수 있으며, 사용자가 직접 정렬, 분할 및 비교 알고리즘을 작성하는 것을 방지할 수 있습니다.

이러한 기능에 대해 자세히 알아보려면 아래의 페이지를 확인하세요!

https://developer.apple.com/documentation/swift/sequence

 

Apple Developer Documentation

 

developer.apple.com

 

When should you use it?

개체 그룹을 보유하는 유형이 있고 구문 내 표준을 사용하여 개체를 사용할 수 있도록 하려면 반복자 패턴을 사용합니다.

 

Playground example

이번 예시에서는 queue를 생성합니다.

Swift Algorithm Club에 queue에 대한 설명이 나와 있습니다. 

( * 우리가 아는 queue가 맞아요! )

https://github.com/raywenderlich/swift-algorithm-club/blob/master/Queue/README.markdown

 

GitHub - raywenderlich/swift-algorithm-club: Algorithms and data structures in Swift, with explanations!

Algorithms and data structures in Swift, with explanations! - GitHub - raywenderlich/swift-algorithm-club: Algorithms and data structures in Swift, with explanations!

github.com

 

코드 예제를 추가합니다.

아래의 코드는 Queue입니다.

import Foundation

// 1
public struct Queue<T> {
  private var array: [T?] = []

  // 2
  private var head = 0
  
  // 3
  public var isEmpty: Bool {
    return count == 0
  }
  
  // 4
  public var count: Int {
    return array.count - head
  }
  
  // 5
  public mutating func enqueue(_ element: T) {
    array.append(element)
  }
  
  // 6
  public mutating func dequeue() -> T? {
    guard head < array.count,
      let element = array[head] else {
        return nil
    }
    
    array[head] = nil
    head += 1
    
    let percentage = Double(head)/Double(array.count)
    if array.count > 50,
      percentage > 0.25 {
        array.removeFirst(head)
        head = 0
    }
    
    return element
  }
}

위의 코드를 분석합니다.

1. Queue에 모든 유형의 배열을 포함하도록 정의했습니다.

2. Queue의 헤드는 배열의 첫 번째 요소의 인덱스가 됩니다.

3. Queue가 비어 있는지 여부를 확인하는 isEmpty가 있습니다.(Bool 타입)

4. Queue의 count 반환합니다.

5. Queue에 elements를 추가하기 위해 enqueue 함수를 생성했습니다.

6. dequeue의 기능은 queue의 첫번째 element 제거하기 위한 것입니다. 이 함수의논리는 배열에 nil이 되는 것을 방지합니다.

 

 

다음으로 아래 코드를 추가합니다.

public struct Ticket {
  var description: String
  var priority: PriorityType

  enum PriorityType {
    case low
    case medium
    case high
  }

  init(description: String, priority: PriorityType) {
    self.description = description
    self.priority = priority
  }
}

var queue = Queue<Ticket>()
queue.enqueue(Ticket(
  description: "Wireframe Tinder for dogs app",
  priority: .low))
queue.enqueue(Ticket(
  description: "Set up 4k monitor for Josh",
  priority: .medium))
queue.enqueue(Ticket(
  description: "There is smoke coming out of my laptop",
  priority: .high))
queue.enqueue(Ticket(
  description: "Put googly eyes on the Roomba",
  priority: .low))
queue.dequeue()

queue에는 네 개의 항목이 있습니다.

첫 번째 티켓을 queue에 넣으면 위에는 3개의 티켓이 들어있습니다.

실제 사용 사례 시나리오에서는 우선 순위에 따라 이러한 티켓을 정렬할 수 있습니다. 지금과 같은 상황이라면, 많은 if 문장으로 정렬 기능을 작성해야 할 것입니다. 시간을 절약하고 대신 Swift의 기본 제공 정렬 기능 중 하나를 사용합시다!!

 

현재 큐에서 for를 loop 또는 sorted()로 사용하려고 하면 오류가 발생합니다. queue가 시퀀스 프로토콜을 준수하도록 만들어야 합니다.

 

큐 구조 아래에 다음을 추가합니다.

extension Queue: Sequence {
  public func makeIterator()
    -> IndexingIterator<ArraySlice<T?>> {
   
    let nonEmptyValues = array[head ..< array.count]
    return nonEmptyValues.makeIterator()
  }
}

dequeue와 마찬가지로 객체가 0개인 상황을 노출하지 않고 비어 있지 않은 값만 반복하는지 확인하려고 합니다.

Sequence 프로토콜을 준수할 때 두 가지 필수 파트가 있습니다.
첫 번째는 연결된 유형, 즉 반복기입니다. 위의 코드에서 Indexing Iterator는 associated type으로, 자체 유형을 선언하지 않는 모든 컬렉션의 기본 iterator입니다.

두 번째 부분은 Iterator 프로토콜로, 필요한 makeIterator 기능입니다. 클래스 또는 구조체에 대한 Iterator를 구성합니다.

 

아래의 코드를 추가하세요!

print("List of Tickets in queue:")
for ticket in queue {
  print(ticket?.description ?? "No Description")
}

이렇게 하면 티켓이 반복되고 인쇄됩니다.


시퀀스별 정렬 기능을 사용하기 전에 위로 스크롤하여 티켓 구조 아래에 다음 extension을 추가해 봅시다.

extension Ticket {
  var sortIndex : Int {
    switch self.priority {
    case .low:
      return 0
    case .medium:
      return 1
    case .high:
      return 2
    }
  }
}

우선 순위 수준에 숫자 값을 할당하면 정렬이 쉬워집니다. 

 

sortIndex를 참조로 사용하여 티켓을 정렬하고 파일 끝에 다음 코드를 추가합니다.

let sortedTickets = queue.sorted {
  $0!.sortIndex > ($1?.sortIndex)!
}
var sortedQueue = Queue<Ticket>()

for ticket in sortedTickets {
  sortedQueue.enqueue(ticket!)
}

print("\n")
print("Tickets sorted by priority:")
for ticket in sortedQueue {
  print(ticket?.description ?? "No Description")
}

정렬 함수는 일반 배열을 반환하므로 정렬된 큐를 가지려면 각 배열 항목을 새 큐에 넣습니다. 그룹을 쉽게 정렬할 수 있는 기능은 강력한 기능이며 목록과 큐가 커질수록 가치가 높아집니다.

 

What should you be careful about?

개체가 반복되는 방법을 사용자 정의할 수 있는 Iterator Protocol이라는 프로토콜이 있습니다. 반복에서 다음 개체를 반환하는 next() 메서드를 구현하면 됩니다. 그러나 Iterator Protocol을 직접 준수할 필요는 없습니다.

 

사용자 지정 반복자가 필요하더라도 Iterator Protocol을 직접 따르는 대신 Sequence를 준수하고 사용자 지정 next() 로직을 제공하는 것이 거의 항상 더 좋습니다.

 

Iterator Protocol에 대한 자세한 정보와 Sequence에 대한 작동 방식을 아래의 문서에서 볼 수 있습니다..

https://developer.apple.com/documentation/swift/iteratorprotocol

 

Apple Developer Documentation

 

developer.apple.com

 

 

Tutorial project

 

접근 기능과 자료구조를 분리하여 객체화할 때 사용합니다. 서로다른 구조를 가지고 있는 저장 객체에 대해서 접근하기 위해서 interface를 통일하고 싶을 때 사용하는 패턴입니다.

for i in 0..<10 { 
   arr[i]
}

만약 arr의 타입이 배열이 아닌 다른 것으로 바뀐다면 반복문 자체가 수정되어야 하는데, 이를 예방합니다.

 

https://github.com/lgvv/DesignPattern/tree/main/iterator-pattern/CoffeeQuest

 

GitHub - lgvv/DesignPattern: ✨ 디자인 패턴을 공부합니다!

✨ 디자인 패턴을 공부합니다! Contribute to lgvv/DesignPattern development by creating an account on GitHub.

github.com

 

 

Key points

당신은 이 장에서 이터레이터 패턴에 대해 공부했습니다.

핵심 사항을 요약해 볼까요?

 

1. 이터레이터 패턴은 for in 구문을 사용하여 컬렉션을 반복하는 표준 방법을 제공합니다.
2. 사용자 직접만든 객체를 Iterator Protocol이 아닌 Sequence에 맞게 만드는 것이 좋습니다.
3. Sequence를 준수하면 map과 filter 같은 고차 함수를 손쉽게 얻을 수도 있습니다!