Danny의 iOS 컨닝페이퍼
article thumbnail
반응형

Sequence란?

순차적이고 반복적인 동작을 할 수 있게 만드는 프로토콜입니다. 즉, 순차적인 나열이죠.

 

선언

protocol Sequence<Element>

 

개요

설명은 공식문서 기반으로 할 예정이므로 자세한 내용을 알고 싶다면 공식문서 참고해 주세요.

 

Sequence는 한 번에 하나씩 단계별(step)로 진행할 수 있는 값의 목록(list of values)입니다.

Sequence요소를 반복하는 가장 일반적인 방법은 for문(for-in loop)을 사용하는 것입니다.

let oneTwoThree = 1...3
for number in oneTwoThree {
    print(number)
}
// Prints "1"
// Prints "2"
// Prints "3"

 

특정 값을 포함하고 있는지 확인을 위해 for-in loop을 통하여 순차적인 테스트도 가능합니다.

let bugs = ["개미", "호박벌", "매미", "잠자리", "집게벌래"]
var hasMosquito = false
for bug in bugs {
    if bug == "모기" {
        hasMosquito = true
        break
    }
}
print("벌레에 모기가 포함되어 있나요? : \(hasMosquito)")
// Prints 벌레에 모기가 포함되어 있나요? : false

 

또한, 시퀀스 구현(Sequence Implementations)에는 여러 메서드가 정의돼 있습니다.

그중 contains(_:) 메서드를 통하여 아래와 같이 간결하게 Sequence의 생성도 가능하죠.

if bugs.contains("모기") {
    print("에프킬라로 벌레 퇴치하기!")
} else {
    print("모기는 없네요")
}
// Prints "모기는 없네요"

 

커스텀 타입에서 프로토콜 Sequence 적용 방법

Conforming to the Sequence Protocol

 

위에서 설명한 것과 같이 순차적으로 나열을 하기 위해서는 Sequence 프로토콜을 준수해야 합니다.

 

이해를 돕기 위해 일단 Danny라는 구조체(커스텀 타입)을 만들어서 for-in loop를 사용해 봅시다.

struct Danny {}
let danny = Danny()
for count in danny {
    print(count)
}
// For-in loop requires 'Danny' to conform to 'Sequence'

이렇게 사용하면 에러가 발생합니다. 읽어보니 Sequence 프로토콜을 준수하라고 하네요.

 

우리는 컴파일러 말을 잘 들어야 됩니다. 하라는 대로 해봅시다.

struct Danny: Sequence {}
// Type 'Danny' does not conform to protocol 'Sequence'
// note: unable to infer associated type 'Iterator' for protocol 'Sequence' associatedtype Iterator : IteratorProtocol
// note: candidate would match and infer 'Iterator' = 'Danny' if 'Danny' conformed to 'IteratorProtocol' @inlinable public __consuming func makeIterator() -> Self

또 에러가 발생하네요 😦 🫠

 

그 이유는 Sequence 프로토콜를 사용하기 위해서는 필수로 IteratorProtocol(반복자)를 구현 및 채택을 해줘야 합니다.

struct DannyIterator: IteratorProtocol {
    typealias Element = Int
    var count: Int
    
    mutating func next() -> Int? {
        if count == 0 { return nil }
        defer { count -= 1 }
        return count
    }
}
// next() 메서드는 리턴으로 nil이 나올 때까지 반복하여 값을 계산한다.

// defer는 보통 함수 안에서 사용되는 클로저인데, 작성된 위치와 상관 없이 함수 종료 직전에 실행되는 구문이다.
// 즉, 자신의 실행을 함수의 맨 마지막으로 미룬다.

공식문서 를 참고하여 IteratorProtocol 채택 후, 필수 메서드인 next()를 정의 후 DannyIterator를 만들어 줬습니다.

 

또한, Sequence에서 IteratorProtocol 사용 시, 꼭 구현해줘야 하는 메서드가 있습니다.

바로 makeIterator() -> Self입니다.

struct Danny: Sequence {
    func makeIterator() -> DannyIterator {
        return DannyIterator(count: 5)
    }
}

 

모두 만들어 주면 아래와 같이 정상적으로 for-in loop를 사용 할 수 있게 됩니다.
struct Danny: Sequence {
    func makeIterator() -> DannyIterator {
        return DannyIterator(count: 5)
    }
}

struct DannyIterator: IteratorProtocol {
    typealias Element = Int
    var count: Int
    
    mutating func next() -> Int? {
        if count == 0 { return nil }
        defer { count -= 1 }
        return count
    }
}

let danny = Danny()
for vlaue in danny {
    print(vlaue)
}

// 5
// 4
// 3
// 2
// 1

 

조금 더 간결하게 사용하기

 

타입 자체에 Sequence, IteratorProtocol를 모두 준수한다면,

makeIterator() 기본 구현이 되기 때문에 생략이 가능합니다.

struct Danny: Sequence, IteratorProtocol {
    var count: Int
    
    mutating func next() -> Int? {
        if count == 0 else { return nil }
        defer { count -= 1 }
        return count
    }
}

let danny = Danny(count: 5)
for count in danny {
    print(count)
}

// 5
// 4
// 3
// 2
// 1

 

조금 더 알아보기

1. Collection

 

대표적으로 Array, Set, Dictionary들은 Collection 프로토콜을 준수합니다.

Collection 프로토콜Sequence 프로토콜을 준수하죠. 그러므로 순차적으로 나열이 가능한 것 입니다.

protocol Collection<Element> : Sequence

 

위에서 Sequence 프로토콜을 채택할 때는 IteratorProtocol를 구현해 줬는데,

왜? Collection 프로토콜를 사용할 때에는 IteratorProtocol을 구현하지 않아도 될까?

 

일단 간단히 Collection 프로토콜 내부를 살펴봅시다.

protocol Collection<Element> : Sequence {
    // Index는 위치를 나타낸다.
    associatedtype Index : Comparable
    
    // 첫번째 Element의 위치
    var startIndex: Self.Index { get }
    
    // 마지막 Element의 위치
    var endIndex: Self.Index { get }
    
    // 지정된 위치의 Element 값
    subscript(position: Self.Index) -> Self.Element { get }
    
    // 주어진 Index의 바로 뒤의 위치
    func index(after i: Self.Index) -> Self.Index
}

그 이유는 Collection 프로토콜은 이미 IteratorProtocol 필수 메서드인 next()를 추론할 수 있습니다.

 

아래와 같은 형식으로 next()의 추론이 가능합니다.

  1. startIndex로 첫 번째의 Index를 알 수 있습니다.
  2. index(after:) 메서드로 다음 Index를 얻을 수 있고
  3. subscript로 각각의 Element 값을 얻을 수 있고
  4. 마지막엔 endIndexindex(after:)로 얻은 Index가 같다면 iterate(반복)을 종료합니다.

 

2. 컴파일러가 for-in loop를 만났을 때

let arr = [1, 2, 3]
for value in arr {
    print(value)
}
// 1
// 2
// 3

 

컴파일러는 이때, 몇 가지 간단한 변환을 한다고 합니다.

이와 같이 iterator를 생성하고 while Loop로 변환시킨다고 합니다.

var iterator = arr.makeIterator()
while let value = iterator.next() {
    print(value)
}
// 1
// 2
// 3

 

 

부족한 설명이지만, 조금은 이해 가셨나요?

틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃

 

 

참고

 

Apple Developer Documentation

 

developer.apple.com

 

Apple Developer Documentation

 

developer.apple.com

 

Swift - Sequence와 Collection (Sequence와 Collection의 차이)

안녕하세요 :) 오늘은 Array, Dictionary, Set을 공부하기 전에 Sequence와 Collection Protocol에 대해서 공부해보도록 하겠습니다. 이번장은 이해하기 어려운 부분이 많이 있어서 이해하기 힘드시면 나중에

beepeach.tistory.com

 

Swift ) Sequence

안녕하세요 :) Zedd입니다. 오늘은 Sequence에 대해서 공부! # Sequence Sequence가 익숙하지 않으시다면, 혹시 Collection은 들어보셨나요? Collection은 프로토콜이며 Swift에서 가장 유명한 Collection Type들인 Arra

zeddios.tistory.com

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!