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)
}
}
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()
의 추론이 가능합니다.
startIndex
로 첫 번째의 Index를 알 수 있습니다.index(after:)
메서드로 다음 Index를 얻을 수 있고subscript
로 각각의 Element 값을 얻을 수 있고- 마지막엔
endIndex
와index(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
부족한 설명이지만, 조금은 이해 가셨나요?
틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃
참고
'Xcode > Swift 문법' 카테고리의 다른 글
[iOS/Swift] AsyncStream / AsyncThrowingStream (0) | 2023.01.26 |
---|---|
[iOS/Swift] 프로토콜 AsyncSequence (비동기 시퀀스) (0) | 2023.01.20 |
[iOS/Swift] AssociatedTypes (프로토콜을 위한 Generic문법) (0) | 2023.01.13 |
[iOS/Swift] 프로토콜 Equatable (타입 비교) (0) | 2023.01.12 |
[iOS/Swift] 속성(Properties)의 종류 (0) | 2023.01.04 |