[Combine] Selecting Specific Elements - Operator 공부 7
안녕하세요 Pingu입니다.🐧
지난 글에서는 Combine의 Operator 중 Applying Sequence Operations to Elements로 분류된 것들을 알아봤습니다. Sequence에 존재하는 몇몇 메서드와 비슷한 기능으로 Upstream에서 받은 값을 처리해서 Downstream으로 전달하는 역할을 했습니다.
이번 글에서는 Selecting Specific Elements로 분류된 Operator에 대해서 알아보도록 하겠습니다.
Selecting Specific Elements
이번에 공부할 것들을 분류해둔 이름을 보니 뭔가 특정한 값을 선택해서 Downstream으로 보내줄 것 같습니다. 그럼 먼저 여기에 분류된 Publisher에는 어떤 것들이 있는지부터 알아보겠습니다.
- First
- FirstWhere
- TryFirstWhere
- Last
- LastWhere
- TryLastWhere
- Output
그리고 이 Publisher들을 활용해서 만든 Operator는 아래와 같습니다.
- first()
- first(where:)
- tryFirst(where:)
- last()
- last(where:)
- tryLast(where:)
- output(at:)
- output(in:)
그럼 하나씩 어떤 역할을 하는 것들인지 알아보도록 하겠습니다.
First
첫 번째로 알아볼 Publisher는 First입니다.
이름에서 느껴지는 대로 Publisher가 받은 첫 번째 값을 내려보내고 finish 되는 Publisher입니다.
first()
First를 활용해서 만든 Operator는 first()입니다.
예상대로 첫 번째 값을 내려보내고 finish 되는 녀석이라고 하네요.
간단하게 사용해보고 넘어가겠습니다.
let intPublisehr = [1, 2, 3, 4].publisher
intPublisehr
.first()
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// first() 예제 코드
1
finished
FirstWhere
다음으로 알아볼 Publisher는 FirstWhere입니다.
정의를 보면 주어진 클로저를 만족하는 첫 번째 값만 내려보내는 Publisher라고 합니다.
first(where:)
FirstWhere를 활용해서 만들어진 Operator는 first(where:)입니다.
주어진 클로저를 만족하는 첫 번째 값만 내려보내고 finish 된다고 하네요.
이거도 간단하게 사용해보고 넘어가도록 하겠습니다.
let intPublisher = [1, 3, 2, 4].publisher
intPublisher
.first(where: { value in
return value % 2 == 0
})
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// first(where:) 예제 코드
2
finished
TryFirstWhere
다음은 아까 알아본 FirstWhere의 try 버전입니다.
예상대로 FirstWhere이랑 똑같은 역할을 하면서 에러를 던질 수 있는 녀석입니다.
tryFirst(where:)
TryFirstWhere를 활용해서 만든 Operator는 tryFirst(where:)입니다.
뭐 얘도 예상대로 first(where:)과 동일하게 주어진 클로저를 만족하는 첫 번째 값만 내려보내고 finish 되는데, 에러를 던질 수도 있는 녀석입니다.
얘도 간단하게 사용해보고 넘어가겠습니다.
struct PinguError: Error { }
let intPubisher = [1, -1, 2, 3].publisher
intPubisher
.tryFirst(where: { value in
guard value < 0 else { throw PinguError() }
return value % 2 == 0
})
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// tryFirst(where:) 예제 코드
failure(__lldb_expr_8.(unknown context at $10291f3bc).(unknown context at $10291f3c4).(unknown context at $10291f3cc).PinguError())
Last
다음으로 알아볼 Publisher는 Last입니다.
Last는 방금 알아본 First와는 다르게 Upstream에서 받은 값 중 가장 마지막 값을 내려보내는 Publisher입니다.
Upstream에게 finish를 전달받으면 바로 직전에 받은 값을 내려보내게 됩니다.
last()
Last Publisher를 활용해서 만든 Operator는 last()입니다.
Upstream에서 받은 값 중 가장 마지막에 받은 값만 Downstream으로 내려보내는 역할을 합니다.
얘도 간단하게 사용하고 넘어갈게요.
let intPublisehr = [1, 2, 3, 4].publisher
intPublisehr
.last()
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// last() 예제 코드
4
finished
LastWhere
LastWhere Publisher는 주어진 클로저를 만족하는 값들 중 가장 마지막 값을 내려보내는 Publisher입니다.
last(where:)
LastWhere Publisher를 활용해서 만든 Operator는 last(where:)입니다.
Upstream에서 받은 값 중에 클로저를 만족시키는 가장 마지막 값만 Downstream으로 내려보내는 역할을 합니다.
간단하게 사용해보면 아래와 같습니다.
let intPublisher = [1, 5, 7, 3, 4, 9, 2].publisher
intPublisher
.last(where: { value in
return value > 4
})
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// last(where:) 예제 코드
9
finished
위 코드를 보면 Upstream에서 받은 값 중 4보다 큰 가장 마지막 값만 내려보내는 코드이므로 9만 Downstream으로 전달된 것을 볼 수 있습니다.
TryLastWhere
다음은 LastWhere의 try 버전인 TryLastWhere입니다.
LastWhere과 마찬가지로 클로저를 만족하는 가장 마지막 값을 내려보내며 에러를 던질 수 있습니다.
tryLastWhere(where:)
TryLastWhere을 활용해서 만든 Operator는 tryLast(where:)입니다.
last(where:)과 동일하게 클로저를 만족하는 값 중 마지막 값을 Downstream으로 전달하며 에러도 던질 수 있습니다.
간단하게 사용해보겠습니다.
struct PinguError: Error { }
let intPublisher = [1, 3, 7, -4, 6, 4].publisher
intPublisher
.tryLast(where: { value in
guard value < 0 else { throw PinguError() }
return value > 5
})
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// tryLast(where:) 예제 코드
failure(__lldb_expr_14.(unknown context at $1039f063c).(unknown context at $1039f067c).(unknown context at $1039f0684).PinguError())
위와 같이 사용할 수 있는데요, 어떻게 보면 모든 값을 어떤 조건으로 확인한다는 점에서 예전에 알아본 allSatisfy()와도 비슷한 부분이 있는 거 같네요.
Output
마지막으로 알아볼 Publisher는 Output입니다.
정의를 보면 Upstream에서 받은 값들 중 특정 범위로 지정된 값들만 Downstream으로 내려보내는 Publisher라고 합니다.
여기서 범위란 Upstream에서 받은 값들에 순서를 매겨서 특정 순서의 값들만 Downstream으로 내려보내겠다! 이런 의미입니다.
output(at:)
Output Publisher를 활용해서 만든 첫 번째 Operator는 output(at:)입니다.
정의를 보면 Upstream에서 받은 값들 중 특정 순서의 값만 Downstream으로 전달한다고 합니다.
특정 순서의 값을 만약 받았다면 그 이후의 값은 필요가 없으니 바로 finish 되는 특징도 있습니다.
간단하게 사용해볼게요.
let intPublisher = [0, 1, 2, 3, 4].publisher
intPublisher
.output(at: 2)
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// output(at:) 예제 코드
2
finished
output(in:)
Output Publisher를 활용해서 만든 두 번째 Operator는 output(in:)입니다.
아까 output(at:)은 특정 순서의 값 하나만 Downstream으로 전달했다면 output(in:)은 특정 순서 범위의 모든 값을 Downstream으로 전달합니다.
output(in:)도 특정 범위를 벗어나는 값들은 필요가 없으므로 해당 범위의 마지막 순서의 값을 받으면 finish 됩니다.
간단하게 사용해볼게요.
let intPublisher = [0, 1, 2, 3, 4, 5, 6].publisher
intPublisher
.output(in: 3...5)
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// output(in:) 예제 코드
3
4
5
finished
print()를 활용해서 간단하게 output(in:)이 정말 범위를 초과하는 값은 받지도 않고 finish 되는지 확인해보겠습니다.
let intPublisher = [0, 1, 2, 3, 4, 5, 6].publisher
intPublisher
.print()
.output(in: 3...5)
.sink(receiveCompletion: { print($0) },
receiveValue: { print($0) })
// output(in:) 예제 코드
receive subscription: ([0, 1, 2, 3, 4, 5, 6])
request unlimited
receive value: (0)
request max: (1) (synchronous)
receive value: (1)
request max: (1) (synchronous)
receive value: (2)
request max: (1) (synchronous)
receive value: (3)
3
receive value: (4)
4
receive value: (5)
5
receive cancel
finished
방금 본 예제에 print()만 추가한 예제입니다.
결과를 보면 5번째 값을 받으니까 receive cancel이 출력되고 finish 되는 것을 볼 수 있습니다.
이렇게 Combine의 Publisher와 Operator 중에서 Selecting Specific Elements로 분류된 것들에 대해 알아봤습니다. 비교적 이름들이 직관적이고 간단해서 쉽게 이해할 수 있었던거 같아요.
다음 글에서는 Combining Elements from Multiple Publishers로 분류된 것들에 대해 알아보도록 하겠습니다.
이번 글의 전체 코드는 여기에서 볼 수 있습니다.
감사합니다~!