본문 바로가기

코틀린/코루틴

Flow 2주차 2차 스터디 Flows are sequential 까지

Flows Are Cold

Flow가 cold stream 임을 설명한다.

우리는 Cold Stream 이 무엇인지 선행학습을 했으니 그냥 키워드만 들어도 어떤것인지 알 수 있었다.

결론적으로 공식문서에서는 Lazy하게 값을 뽑아온다는것을 설명하고있다.

Flow cancellation basics

Flow는 general cooperative cancellation of coroutines를 따른다고 하는데 직역하면 코루틴의 협력적 취소이다.

Coroutine의 취소되는 메커니즘은 생각보다 간단하지는 않다. -> 많은 내부적인 동작원리들을 알아야한다.

만약 코루틴의 cancelleation이 익숙치 않다면 이글을 보고오자

만약 글을 보고와서 예제를 살펴본다면 그냥 당연한소리 하면서 와닿을것이다

Flow builders

여태까지는 flow{} 이 빌더함수만 봐왔는데 가장 기본적인 방법이고 다른 방법들도 존재한다. 하지만 딱히 쓸모 있어 보이지는 않는다.

  1. flow

가장 기본적인 방법이다. 그냥 일반적인 사용법을 생각하면 될 것 같다.

  1. flowOf

이미 주어진(고정된) 값들로 Flow를 생성할때 쓰는 방법이라고 한다. 예시를 보면 이해는 가지만 딱히 안쓸것같다. 차라리 asFlow를 쓰지

fun main() = runBlocking {
    val fruitsFlow = flowOf("Apple", "Banana", "Cherry")
    fruitsFlow.collect { fruit ->
        println(fruit)
    }
}
​
//결과
//Apple
//Banana
//Cherry
  1. asFlow

다양한 컬렉션과 시퀀스를 asFlow() 확장함수를 통해 Flow로 변환가능하다. -> 사용되는 예시들보면서 익혀서 여기저기 쓰면 될것같다.

// Convert an integer range to a flow
(1..3).asFlow().collect { value -> println(value) }

Intermediate flow operators

중간 연산자를 설명하는 내용으로 일반적으로 collections 혹은 sequences에서 사용하던것을 떠올리면된다.

upStream을 변경하여 downStream 으로 내뱉는데 이것을 cold(lazy)하게 수행하며 그 자체로 suspending function은 아니지만

내부에서 suspending function 을 호출할 수 있다.

Transform operator

Transform 이라는 중간연산자가 있는데 이는 중간연산자 이지만 그 내부 블록안에서 값을 emit할 수 있다.(확장성 장난 아닐듯)

그래서 flow를 마음대로 조절할 수 있는데 값을 단순 변환이 아니라 더 많은 횟수 만큼 방출할 수 있다는것을 강조하고 있다.

(1..3).asFlow() // a flow of requests
    .transform { request ->
        emit("Making request $request") 
        emit(performRequest(request)) 
    }
    .collect { response -> println(response) }
​
//결과
//Making request 1
//response 1
//Making request 2
//response 2
//Making request 3
//response 3

Size-limiting operators

Take 같은 사이즈를 제한하는 중간연산자의 경우 제한하는 상황에서 coroutine 를 Cancellation낸다. 즉 Exception을 throw하여 cancel하는것이다.

고로 리소스 정리용 함수인 Try Catch 등을 사용할 수 있다고 한다.(사용하는게 맞는지는 모르겠다...)

멧돼지 추가내용

중간 연산자가 굉장히 다양한것으로 알고있어 찾아봤더니 이런 것들이 있었다. 추후 각각의 사용예시를 적은 글을 작성하려한다.

  1. map: 각 값을 변환합니다.
  2. filter: 특정 조건을 만족하는 값만 통과시킵니다.
  3. transform: 각 값을 변환할 때 추가적인 효과를 부여할 수 있으며, 하나의 입력 값에 대해 여러 값을 내보낼 수도 있습니다.
  4. take: 지정된 수의 값을 내보낸 후 수집을 완료합니다.
  5. drop: 지정된 수의 값을 건너뛴 후 나머지 값을 내보냅니다.
  6. flatMapConcat: 각 값을 Flow로 변환하고, 이 Flow들을 연결하여 단일 Flow로 만듭니다.
  7. flatMapMerge: flatMapConcat과 유사하지만, 변환된 Flow들을 병렬로 수집합니다.
  8. flatMapLatest: 새로운 값이 들어올 때마다 이전에 변환된 Flow의 수집을 취소하고 최신 Flow만 수집합니다.
  9. buffer: 수집 작업과 발생 작업 사이에 버퍼를 두어 비동기로 실행될 수 있게 합니다.
  10. conflate: 느린 수집기를 가지고 있을 때, 새 값을 내보내기 위해 오래된 값을 버립니다.
  11. collectLatest: 새로운 값이 들어올 때마다 현재 수집 작업을 취소하고 최신 값부터 수집을 시작합니다.
  12. distinctUntilChanged: 연속적으로 중복된 값이 나오지 않게 합니다.
  13. onEach: 각 값을 수집하기 전에 부가적인 동작을 수행할 수 있게 합니다.
  14. zip: 두 Flow의 값들을 한 쌍으로 결합합니다.
  15. combine: 두 개 이상의 Flow를 결합하여, 각 Flow에서 새로운 값이 발생할 때마다 결합된 결과를 내보냅니다.
  16. withIndex: 값에 인덱스를 부여합니다.
  17. scan: 주어진 함수를 사용하여 값을 누적하고, 각 단계의 결과를 반환합니다.

Terminal flow operators

Flow를 수집 할 수 있도록 하는 함수를 터미널 연산자라 하며 이는 suspending 함수이다.

collect가 대표적인 연산자이고 더 다양한 것들이 있다.

공식문서에는 toList, toSet, first, single, reduce, fold 가 나와있는데 이는 이름만 봐도 용도가 어떤것인지 알 수 있을것이다.

하지만 더 많은 터미널 연산자가 있다. 추후 이것들 또한 정리하는 글을 한번써야겠다.

  1. collect: 모든 값들을 수집합니다. 가장 일반적인 터미널 연산자로, 람다를 통해 각 값에 대해 수행할 작업을 정의할 수 있습니다.
  2. toList / toSet: 수집된 값들을 리스트나 세트로 변환합니다.
  3. first: 첫 번째 값을 반환하고 Flow의 실행을 종료합니다.
  4. firstOrNull: 첫 번째 값을 반환하되, 값이 없으면 null을 반환합니다.
  5. single: 하나의 값만을 내보내고 기대하는 Flow에 대해 사용하며, 값이 하나가 아닐 경우 예외를 발생시킵니다.
  6. singleOrNull: 하나의 값만 내보낼 때 사용하며, 값이 없거나 하나 이상일 때는 null을 반환합니다.
  7. reduce: 주어진 연산을 사용하여 모든 값을 하나로 누적합니다.
  8. fold: reduce와 유사하지만, 초기값을 가질 수 있습니다.
  9. count: Flow에서 발행된 값의 개수를 계산합니다.
  10. launchIn: 현재의 Coroutine scope 내에서 Flow를 실행시키고, 결과는 처리하지 않고 즉시 제어를 반환합니다. 주로 side-effect가 목적인 경우에 사용됩니다.
  11. onEach: 각 발행된 값에 대해 액션을 수행하고, 계속해서 다른 터미널 연산자를 호출할 수 있습니다.
  12. onCompletion: Flow가 완료될 때 호출되는 콜백을 정의합니다. 정상적인 완료 또는 예외 발생 시 모두 호출됩니다.

Flows are sequential

당연한 이야기이지만 Flow는 순차적으로 실행된다고 한다.

또한 수집은 터미널 연산자를 호출하는 코루틴내에서 수행되며 새로운 코루틴을 생성하는것은 아니다. (그것을 호출하는 코루틴을 이용)