fun getPagedFavoritePosts(): Flow<PagingData<PostEntity>> = Pager(
config = PagingConfig(25),
pagingSourceFactory = {
GenericPagingSource { afterKey ->
redditApi.loadFavoritePosts(getUserName(), afterKey)
}
}
).flow.transform { pagingData -> emit(pagingData.map { (it.data as PostDto).map() }) }
우연히 오픈소스 코드를 보다가, 나라면 map
을 썼을 것 같던 구문에 transform
을 사용한 것을 발견했다. transform
? 처음 들어봤는데 이름이 map
과 왠지 비슷한 일을 할 것같아서 찾아보았다.
map
Flow의 각 값을 변환하여 새로운 요소로 매핑하여 Flow를 반환한다.
val squaredFlow = flowOf(1, 2, 3, 4).map {
it * it
}
transform
Flow의 각 값을 변환하는 함수를 적용한다. transform
의 receiver는 FlowCollector
이므로 요소를 변환하거나, 건너 뛸 수도 있다. 또 emit
함수를 여러번 호출하여 여러 요소를 추가할 수도 있다.
val squaredFlow = flowOf(1, 2, 3, 4).transform {
emit(it * it)
}
이 예시는 map에서도 비슷하지만, 예컨대 이런 것도 가능하다는 것이다.
// 홀수의 제곱만을 가지는 Flow
val squaredOddFlow = flowOf(1, 2, 3, 4).transform {
if (it % 2 == 1) {
emit(it * it)
}
}
메모리 사용량?
그럼 전부 map보다 유연한 transform을 쓰는 것이 좋지않나...생각이 들던 찰나, 메모리 사용량을 살펴보기로 했다. 미묘하지만 똑같이 사용했을 경우 map
이 메모리 사용량이 더 적었다.
runBlocking {
flowOf(1, 2, 3, 4).map {
it * it
}.collectLatest {
printMemory()
}
}
map()
Used memory: 6111 KB
Used memory: 8161 KB
Used memory: 8192 KB
Used memory: 8685 KB
runBlocking {
flowOf(1, 2, 3, 4).transform<Int, Int> {
emit(it * it)
}.collectLatest {
printMemory()
}
}
transform()
Used memory: 6111 KB
Used memory: 8687 KB
Used memory: 8687 KB
Used memory: 9178 KB
하지만 사실은 map도 내부적으론 transform을 사용중이다. 유일한 차이점은 map에서 사용하는 것이 unsafeFlow
라는 건데...
어째서 메모리 사용량의 차이를 부르는지는 잘 모르겠다. 어쨌든 map, filter 한방에 필요하다면 transform
을 사용하자.
// map
public inline fun <T, R> Flow<T>.map(crossinline transform: suspend (value: T) -> R): Flow<R> = transform { value ->
return@transform emit(transform(value))
}
@PublishedApi
internal inline fun <T, R> Flow<T>.unsafeTransform(
@BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = unsafeFlow {
collect { value ->
return@collect transform(value)
}
}
// transform
public inline fun <T, R> Flow<T>.transform(
@BuilderInference crossinline transform: suspend FlowCollector<R>.(value: T) -> Unit
): Flow<R> = flow {
collect { value ->
return@collect transform(value)
}
}
참고자료
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/map.html
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/transform.html
'프로그래밍 > Kotlin' 카테고리의 다른 글
[KotlinConf2024] Kotlin 2.0의 새로운 피쳐들 (1) | 2024.09.18 |
---|---|
의존성을 가지는 Initializer 만들기 (0) | 2024.06.01 |
[Kotlin] 클로저(Closure)에 대해 알아보자 (0) | 2023.03.20 |
Kotlin은 왜 나왔고, 왜 Android 공식언어로 채택되었을까? (0) | 2023.03.19 |
[Kotlin] CompletableDeferred의 개념과 활용 (0) | 2022.11.12 |