플로우 이해하기
fun interface FlowCollector<T> {
suspend fun emit(value: T)
}
interface Flow<T> {
suspend fun collect(collector: FlowCollector<T>)
}
fun <T> flow(
builder: suspend FlowCollector<T>.() -> Unit
) = object : Flow<T> {
override suspend fun collect(collector: FlowCollector<T>) {
collector.builder()
}
}
suspend fun main() {
val f: Flow<String> = flow {
emit("A")
emit("B")
emit("C")
}
f.collect { print(it) } // ABC
f.collect { print(it) } // ABC
}
collect
를 호출하면, flow 빌더를 호출할 때 넣은 람다식이 실행됨.
- 빌더의 람다식이
emit
을 호출하면 값을 방출함.
- 위 프로세스가 플로우가 작동하는 원리임.
- 다른 빌더 또한 내부에서
flow
를 사용함.
Flow 처리 방식
flow
, collect
, emit
함수로 플로우를 쉽게 만들 수 있음.
- 플로우의 각 원소를 변환하는
map
함수는 새로운 플로우를 만들기 때문에, flow 빌더를 사용함.
fun <T, R> Flow<T>.map(transformation: suspend (T) -> R): Flow<R> = flow {
collect {
emit(transformation(it))
}
}
- 플로우가 시작되면 래핑하고 있는 플로우를 시작하게 되므로, 빌더 내부에서
collect
메서드를 호출함.
- 원소를 받을 때마다,
map
은 원소를 변환하고 새로운 플로우로 방출함.
- 플로우의 원리를 이해하면 코드가 어떻게 작동하는지 쉽게 이해할 수 있음.
동기로 작동하는 Flow
- 플로우 또한 중단 함수처럼 동기로 작동하기 때문에, 플로우가 완료될 때까지
collect
호출이 중단됨.
- 즉, 플로우는 새로운 코루틴을 시작하지 않음.
- 중단 함수가 코루틴을 시작할 수 있는 것처럼, 플로우의 각 단계에서도 코루틴을 시작할 순 있음.
- 플로우에서 각각의 처리 단계는 동기로 실행됨.
onEach
내부에 delay
가 있으면 모든 원소가 처리되기 전이 아닌 각 원소 사이에 지연이 생김.