플로우가 실제 어떻게 내부적으로 확인해 보자.
이를 하기위해 먼저 람다식을 만들어 실행하는 코드를 작성해 보자.
fun main() {
val f: () -> Unit = {
print("1")
print("2")
print("3")
println()
}
f()
f()
f()
}
//
123
123
123
결과는 누구나 예상할 수 있듯이 123 이 출력되고 있는 것을 확인할 수 있다.
흥미를 더 돋우기 위해 중단함수를 추가해 보자.
suspend fun main() {
val f: suspend () -> Unit = {
print("1")
delay(1000)
print("2")
delay(1000)
print("3")
println()
}
f()
f()
}
//
123 // 2초
123 // 2초
람다식은 순차적으로 호출되기 때문에, 이전 호출이 완료될 때까지 같은 람다식을 호출할 수 없다.
람다식은 함수를 나타낼 수 있는 파라미터를 가질 수 있다.
이 파라미터를 emit 이라고 해보자.
람다식 f 를 호출 할 때 emit 으로 또다른 람다식을 명시해야 한다.
suspend fun main() {
val f: suspend ((Int) -> Unit) -> Unit = { emit ->
emit(1)
emit(2)
emit(3)
}
f { print(it) }
f { print(it) }
}
// 123123
함수명이 많이 복잡해진 상태이므로, emit 함수 메서드를 가진 FlowCollector 를 만들어 사용해보자.
fun interface FlowCollector {
suspend fun emit(value: Int)
}
suspend fun main() {
val f: suspend (FlowCollector) -> Unit = {
it.emit(1)
it.emit(2)
it.emit(3)
}
f { print(it) }
f { print(it) }
}
// 123123
위의 코드에서 더 나아가 it 에서 emit 을 호출하는 것은 깔끔하지 않으므로 FlowCollector 를 리시버로 만들어 사용해보자.
fun interface FlowCollector {
suspend fun emit(value: Int)
}
suspend fun main() {
val f: suspend FlowCollector.() -> Unit = { // this : FlowCollector
emit(1)
emit(2)
emit(3)
}
f { print(it) }
f { print(it) }
}
// 123123
람다식을 전달하는 것 보다 인터페이스로 구현된 객체를 만드는 것이 낫다.
이 때 인터페이스 이름을 Flow 로 하고 인터페이스의 정의는 객체 표현식으로 매핑하면 된다.
fun interface FlowCollector {
suspend fun emit(value: Int)
}
interface Flow {
suspend fun collect(collector: FlowCollector)
}
suspend fun main() {
val builder: suspend FlowCollector.() -> Unit = {
emit(1)
emit(2)
emit(3)
}
val flow: Flow = object : Flow {
override suspend fun collect(collector: FlowCollector) {
collector.builder()
}
}
flow.collect(::println) //123
flow.collect(::println) // 123
}
// 123123
이 때 flow 생성을 간단하기 위해 flow 빌더를 정의해보자.
fun interface FlowCollector {
suspend fun emit(value: Int)
}
interface Flow {
suspend fun collect(collector: FlowCollector)
}
fun flow(
builder: suspend FlowCollector.() -> Unit,
) = object : Flow {
override suspend fun collect(collector: FlowCollector) {
collector.builder()
}
}
suspend fun main() {
val flow: Flow = flow {
emit(1)
emit(2)
emit(3)
}
flow.collect(::print) // 123
flow.collect(::print) // 123
}
//
123123
마지막으로 Int 타입 뿐만 아니라 모든 타입으로 사용할 수 있도록 제너틱 타입으로 변경해보자.
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 flow: Flow<Int> = flow {
emit(1)
emit(2)
emit(3)
}
flow.collect(::print) // 123
flow.collect(::print) // 123
}
//
123123
위에서 만든 Flow을 구현한 방식은 실제 Flow 가 개발되어 있는 모습과 거의 흡사하다.
collect() 를 호출하면, flow 빌더를 호출할 때 넣는 람다식이 호출된다.
빌더의 람다식이 emit 을 호출하면 collect 가 호출되었을 때 명시된 람다식이 호출된다.
'도서 > 코틀린 코루틴' 카테고리의 다른 글
플로우 생명주기 함수 (0) | 2024.03.24 |
---|---|
플로우 만들기 (0) | 2024.03.24 |
핫 데이터 소스와 콜드 데이터 소스 (0) | 2024.03.24 |
플로우란 무엇인가? (0) | 2024.03.24 |
3장 채널과 플로우 - 채널 (0) | 2024.03.08 |