본문 바로가기
Kotlin

[Kotlin] Collect vs CollectLatest에 대하여 알아보자.

by Echung 2023. 12. 20.

thumbnail

안녕하세요. 오늘은 Flow에서 Collect와 CollectLatest에 대하여 알아보려고 합니다.

Collect는 무엇인가?


Collect는 주로 Flow 및 Kotlin Coroutines와 관련된 개념 중 하나입니다. collect 함수는 Flow에서 값을 수집하여 처리하는데 사용됩니다.

Kotlin의 Flow는 비동기적인 연산을 처리할 수 있는 스트림이며, collect는 Flow에서 발생하는 값을 수집하고 처리하는 메서드 입니다.

val count = flow {
	for(i in 1..10) {
    	emit(i)
        delay(100)
	}
}

fun getCount() {
	lifecycleScope.launch {
		count.collect {
			delay(1000)
			println("${it} 번째")	
        }
	}
}

// 1번째
// 2번째
// 3번째
// 4번째
// 5번째
// 6번째
// 7번째
// 8번째
// 9번째
// 10번째

Collect와 CollectLatest의 차이점은 ?


여기서 Collect는 위의 코드와 같이  데이터가 들어오는 순서대로 데이터를 표현하게 됩니다. 하지만, 이 Collect를 잘못 사용하면 데이터가 발행되더라도 데이터 처리가 제대로 되지 않을 수 있습니다. 

val count = flow {
	for(i in 1..10) {
		emit(i)
		delay(100)
	}
}

fun getCount() {
	lifecycleScope.launch {
		count.collect {
			delay(100000)
			println("${it} 번째")	
		}
	}
}

// 1번째
// dealy 100000
// 2번째 
// dealy 100000
...
// 10번째

예를 들어 flow에서 발행되는 특정 데이터가 처리되는데 많은 시간을 소모하면, 위와 같이 다음 데이터를 엄청난 지연이 발생하여 발행되는 문제가 발생합니다. 그리고 만약 가운데에서 무제한의 시간을 소비하게된다면, 뒤에 데이터는 모두 처리가 되지 않을 것임을 뜻합니다.

위 문제를 처리하는 가장 간단한 방법은 최신 데이터가 들어왔을 때 이전 데이터를 이용해 수행하던 작업을 취소하고 새로 들어올 데이터를 수행하도록 만드는 것입니다. 그것을 제공하는 것이 CollectLatest 입니다.

val count = flow {
	for(i in 1..10) {
		emit(i)
		delay(100)
	}
}

fun getCount() {
	lifecycleScope.launch {
		count.collectLatest {
	        println("${it} 번째")	
			delay(1000)
		}
	}
}

// 1번째
// 2번째
// 3번째
// 4번째
// 5번째
// 6번째
// 7번째
// 8번째
// 9번째
// 10번째

이는 새로운 데이터가 들어오면 기존의 처리를 강제 종료 시키고 새로운 데이터를 처리합니다. CollectLatest는 최신 데이터를 이용해 UI를 구성하도록 하는 작업에 특화되어 있습니다. 그래서 Android에서도 UI에 표기하는 작업에는 CollectLatest를 사용하도록 권장한다고 합니다.

하지만 ! 여전히 CollectLatest에도 한계가 있습니다. 

CollectLatest 한계점


CollectLatest는 데이터 발행 시간 사이의 간격보다 데이터를 처리하는 suspend fun이 수행하는 시간이 오래 걸릴 경우 새로 들어온 데이터는 계속해서 취소되게 됩니다. 그래서 CollctLatest를 쓸 경우 원하는 데이터를 얻을 수 없고 마지막 데이터만을 얻을 수 있다는 문제점이 있습니다.

val count = flow {
	for(i in 1..10) {
		emit(i)
		delay(100)
	}
}

fun getCount() {
	lifecycleScope.launch {
		count.collectLatest {
			delay(1000)
	        println("${it} 번째")	
		}
	}
}

// 10번째

위의 코드를 보면 데이터를 처리하는 시간이 길어지면서 마지막 데이터만을 얻을 수 있는 것을 확인할 수 있었습니다. 이러한 문제를 해결할 수 있는 방식으로 flow에서는 발행과 소비를 위한 Coroutine을 분리시키는 방식의 함수를 제공해줍니다. 그것이 바로 Buffer함수 입니다.

Buffer을 이용한 최적화


Buffer 란, 지정된 용량의 채널을 통해 흐름 방출을 버퍼링하고 별도의 코루틴을 수집해서 실행하는 함수입니다.

사진 1. buffer를 제외한 collect

사진 1을 실행하면 코루틴Q에 의해 다음 순서로 실행됩니다.

사진 2. 1번 사진 실행 순서
사진 3. buffer를 사용한 collect

사진 3을 실행하면 두 개의 코루틴을 사용합니다. 이 코드를 호출하는 코루틴 Q은 collect 실행될 예정이며, buffer 이전의 코드는 별도의 새 코루틴에서 실행됩니다

사진 4. 3번 사진 실행 순서

위와 같이 buffer을 넣으면 어떻게 동작되는지 알 수 있었습니다. 마지막으로 코드의 입력값과 출력값으로 봐보록 하겠습니다.

 

val count = flow {
	for(i in 1..10) {
		emit(i)
		delay(1000)
	}
}

fun getCount() {
	lifecycleScope.launch {
		count.onEach {
        	println("emit ${it} 번째")
        }.buffer().collect {
			delay(3000)
	        println("${it} 번째")	
		}
	}
}

// emit 1번째
// emit 2번째
// emit 3번째
// 1번째
// emit 4번째
// emit 5번째
// emit 6번째
// 2번째
// emit 7번째
// emit 8번째
// emit 9번째
// 3번째
// emit 10번째
// 4번째
// 5번째
// 6번째
// 7번째
// 8번째
// 9번째
// 10번째

이렇게 Buffer을 사용하면 발행은 발행 소비는 소비는 소비대로 일어나는 것을 확인할 수 있었습니다. 이를 통해 발행에 생기는 지연을 방지할 수 있습니다. 그래서 앞으로는 flow을 사용하면서 Collect와 CollectLatest를 적절히 사용하면서 만약, 지연이 생기는 경우에는 buffer을 사용하면서 상황에 맞게 코드를 작성하도록 해야겠습니다.

Reference


https://kotlinworld.com/253?category=973477

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/buffer.html

반응형