플로우란 비동기적으로 처리해야할 값들의 데이터스트림이다.
즉 플로우 자체는 데이터를 모으고 그 끝에는 방출하는 역할을 한다.
Flow 의 함수중 collect 가 그 역할을 한다. (== forEach )
실제로 Flow 의 내부구현을 봐보면 유일한 멤버 함수인 collect 을 제외하고 다른 함수들은 확장 함수로 정의되어 있다.
public interface Flow<out T> {
public suspend fun collect(collector: FlowCollector<T>)
}
플로우와 값들을 나타내는 다른 방법들과 비교
여러개의 값들을 반환하고 싶을 때 List 같은 컬렉션을 사용할 수 있다.
아래 예제를 살펴보자.
fun getList() = List(3){
Thread.sleep(1000)
"list item $it"
}
fun main(){
val list = getList() // 3초 기다려야함.
println("start")
list.forEach(::println)
}
//
(3초 후)
start
list item 0
list item 1
list item 2
List 는 할당할 때 계산을 완료하기 때문에 값이 채워질 때 까지 기다려야 한다.
따라서 위의 예제에서 3초를 기다리고 출력하는 모습을 볼 수 있다.
다른 컬렉션 중 sequence 같은 경우 이런 연산이 필요할 때마다 계산하기 때문에 조금 더 용이하게 사용할 수 있다.
fun getSequence() = sequence<String> {
repeat(3) {
Thread.sleep(1000)
yield("sequence item : $it")
}
}
fun main() {
val list = getSequence()
println("start")
list.forEach(::println) // loop 돌 때 delay 가 됨
}
//
start
1초 후
sequence item : 0
1초 후
sequence item : 1
1초 후
sequence item : 2
sequence 와 Flow 는 동일한 역할을 한다. 왜 sequence 가 아닌 Flow 를 사용하는 걸까?
sequence 의 최종연산은 때 중단함수 내부에서 사용하지 않으므로 HTTP 통신같은 경우에 사용할 때 적합하지 않다.
// 잘못된 코드
suspend fun getUser(
api: UserApi,
) = sequence<User> {
val user = api.getAPI() // 중단함수와 같이 사용할 수 없음
yield(user)
}
위의 예제와 같은 경우에 Flow 를 사용해야 한다.
플로우의 특징
플로우의 최종연산(collect)에서는 코루틴을 중단시키고, 구조화된 동시성, 예외처리 등 기본적인 기능들을 가지고 있다.
아래 예제를 살펴보자.
fun getFlow() = flow {
repeat(3) {
delay(1000)
emit("it in ${currentCoroutineContext()[CoroutineName]?.name}")
}
}
suspend fun main() {
val flow = getFlow()
withContext(CoroutineName("Name")) {
val job = launch {
flow.collect(::println)
}
launch {
delay(2100)
println("finish")
job.cancel()
}
}
}
//
it in Name
it in Name
finish
Flow 는 collect 할 때 부모-자식 관계가 성립이 된다.
따라서 부모가 취소될때 자식도 취소가 되므로 emit 이 2번만 된 것을 확인할 수 있다.
플로우 명명법
플로우는 몇가지 요소로 구성이 된다.
- 플로우는 어딘가 에서 실행되어야 한다.
- 어딘가 는 플로우 빌더, 다른 객체의 변환, 헬퍼 함수 등을 뜻한다.
- 플로우의 마지막 연산은 최종연산 이라고 부른다.
- 최종연산 에서 유일하게 스코프를 필요로 하므로 중요하다.
- 시작 연산과 최종 연산 사이의 플로우를 변경할 수 있는 중간 연산 을 가질 수 있다.
suspend fun main() {
flow { emit("123") }// 시작 연산, 플로우 빌더
.onEach(::println) // 중간연산
.onStart(::println) // 중간연산
.onCompletion { emit("finish") } // 중간연산
.collect() // 최종 연산
}
개인적으로 플로우의 기능중 중간연산이 가장 강력한 기능이라고 생각한다.
'도서 > 코틀린 코루틴' 카테고리의 다른 글
플로우의 실제 구현 (0) | 2024.03.24 |
---|---|
핫 데이터 소스와 콜드 데이터 소스 (0) | 2024.03.24 |
3장 채널과 플로우 - 채널 (0) | 2024.03.08 |
2장 코루틴 라이브러리 - 공유 상태로 인한 문제 (1) | 2024.02.10 |
2장 코루틴 라이브러리 - 코루틴 스코프 만들기 (1) | 2024.02.10 |