본문 바로가기

도서27

공유플로우와 상태플로우 플로우는 콜드 데이터 스트림이기 때문에 요청할 때마다 값이 계산이 된다. 이 때 여러개의 수신자 가 하나의 데이터가 변경되는지 감지하는 경우가 생길 수 있다. 이때 공유플로우를 사용할 수 있다. 공유플로우 공유플로우를 통해 메세지를 방출하면 대기하고 있던 모든 코루틴이 수신하게 된다. suspend fun main() { coroutineScope { val flow = MutableSharedFlow() launch { flow.collect(::println) } delay(1000) flow.emit("message 1") flow.emit("message 2") } } // 1초후 message 1 message 2 프로그램은 게속 실행됨 해당 flow 를 중지시킬려면 부모 코루틴인 corouti.. 2024. 3. 24.
플로우 생명주기 함수 플로우에는 여러가지 생명주기 함수가 있다. (중간 함수) 하나씩 알아보자. onEach onEach 함수는 값을 하나씩 받는다. 따라서 onEach 함수에 delay를 넣으면 값을 받아올 때마다 delay 함수가 실행이 된다. suspend fun main() { flowOf(1,2,3).onEach { delay(1000) }.collect(::println) } // 1초 후 1 1초 후 2 1초 후 3 onStart onStart 함수는 이름 그대로 collect 최종 연산이 실행될 때 호출되는 리스너 이다. 첫 번째 원소를 요청했을 때 호출되는 것을 확인할 수 있다. suspend fun main() { flowOf(1, 2, 3).onStart { println("start") }.collect.. 2024. 3. 24.
플로우 만들기 원시값을 가지는 플로우 플로우를 만드는 가장 간단한 방법은 플로우가 어떤 값을 가져야 하는지 정의하는 flowOf를 사용하는 것 이다. listOf 함수와 비슷하게 사용할 수 있다. suspend fun main() { flowOf(1, 2, 3).collect(::println) } // 1 2 3 만약 빈 flow 를 생성하고 싶다면 EmptyFlow 를 사용하면 된다. suspend fun main() { emptyFlow().collect(::println) } // 출력 x 컨버터 asFlow 함수를 사용해 컬렉션 을 flow 로 변환할 수 있다. asFlow 로 변환할 수 있는 컬렉션은 Iterable , Iterator , Sequence 이다. suspend fun main() { list.. 2024. 3. 24.
플로우의 실제 구현 플로우가 실제 어떻게 내부적으로 확인해 보자. 이를 하기위해 먼저 람다식을 만들어 실행하는 코드를 작성해 보자. 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초 람.. 2024. 3. 24.
핫 데이터 소스와 콜드 데이터 소스 Channel 코루틴의 통신방법중 Flow 개념 이전에 Channel 이 먼저 추가되었다. Channel 의 개념은 책에서는 이렇게 설명한다. Channel 은 도서관의 책들처럼 책을 빌리기위해 다른사람이 먼저 책을 반납해야한다. 즉 Channel 에는 송신자와 수신자의 제한이 없다. Channel 은 Hot Stream 으로 구독자의 여부에 관계없이 데이터가 생성이 된다. 하지만 구독을 할 때 데이터가 생성되는 것을 원할 때가 있다. [Cold Stream] 채널은 현재 개발하는 시점에는 자주 사용하지 않으므로, Hot - Cold stream 에 대해 더 자세히 알아보자. Hot vs Cold 위에서 말했다시피, Hot Stream 과 Cold Stream 은 다음과 같은 차이가 있다. Hot Str.. 2024. 3. 24.
플로우란 무엇인가? 플로우란 비동기적으로 처리해야할 값들의 데이터스트림이다. 즉 플로우 자체는 데이터를 모으고 그 끝에는 방출하는 역할을 한다. Flow 의 함수중 collect 가 그 역할을 한다. (== forEach ) 실제로 Flow 의 내부구현을 봐보면 유일한 멤버 함수인 collect 을 제외하고 다른 함수들은 확장 함수로 정의되어 있다. public interface Flow { public suspend fun collect(collector: FlowCollector) } 플로우와 값들을 나타내는 다른 방법들과 비교 여러개의 값들을 반환하고 싶을 때 List 같은 컬렉션을 사용할 수 있다. 아래 예제를 살펴보자. fun getList() = List(3){ Thread.sleep(1000) "list ite.. 2024. 3. 24.
3장 채널과 플로우 - 채널 코루틴끼리의 통신을 위한 기본적인 방법으로 채널 API가 추가되었다. 많은 사람들은 채널을 파이프로 떠올리지만 필자는 책을 교환하는데 사용되는 공공 책장과 비슷하다고 한다. 채널은 송신자와 수신자의 수의 제한이 없으며, 채널을 통해 전송된 모든 값은 단 한번만 받을 수 있다. Channel은 두개의 서로 다른 인터페이스를 구현한 하나의 인터페이스다. SendChennel은 원소를 보내거나(또는 더하거나) 채널을 닫는 용도로 사용된다. ReceiveChennel 은 원소를 받을 때(또는 꺼낼때) 사용된다. interface SendChannel { suspend fun send(element: E) fun close(): Boolean //.. } interface ReceiveChannel { suspe.. 2024. 3. 8.
2장 코루틴 라이브러리 - 공유 상태로 인한 문제 사작하기 전에 아래에 있는 UserDownloader 클래스를 살펴보자. 이 클래스는 아이디로 사용자를 받아오거나, 이전에 전송받은 모든 사용자를 얻을 수 있다. 이렇게 구현하면 무슨 문제가 있을까? class UserDownloader( private val api: NetworkService, ) { private val users = mutableListOf() fun downloaded(): List = users.toList() suspend fun fetchUser(id: Int) { val newUser = api.fetchUser(id) users.add(newUser) } } 앞의 구현 방식에는 동시사용에 대한 대비가 되어 있지 않다. fetchUser 호출은 user를 변경한다. 이 경.. 2024. 2. 10.
2장 코루틴 라이브러리 - 코루틴 스코프 만들기 이번에는 스코프에 대해 배운것들을 요약해보고 일반적으로 사용하는 방법에 대해서 알아보자. CoroutineScope 팩토리 함수 CoroutineScope는 CoroutineContext를 유일한 프로퍼티로 가지고 있는 인터페이스이다. interface CoroutineScope { val coroutineContext: CoroutineContext } CoroutineScope 인터페이스를 구현한 클래스를 만들고 내부에서 코루틴 빌더를 직접 호출할 수 있다. 안드로이드에서 스코프 만들기 이번 예시는 BaseViewModel에 scope를 정의해보자. abstract class BaseViewModel : ViewModel() { protected val scope = CoroutineScope(TOD.. 2024. 2. 10.
2장 코루틴 라이브러리 - 디스패쳐 코루틴 라이브러리가 제공하는 중요한 기능은 코루틴이 실행되어야 할 스레드를 결정할 수 있다는 것이다. 이 결정은 디스패쳐를 이용하여 사용할 수 있다. 디스패쳐의 사전적 의미 : 사람이나 차량, 특히 긴급 차량을 필요한 곳에 보내는 것을 담당하는 사람 코루틴 코틀린에서 코루틴이 어떤 스레드에서 실행될지 정하는 것은 coroutineContext이다. 기본 디스패쳐 디스패쳐를 설정하지 않으면 기본적으로 설정되는 디스패쳐는 CPU 집약적인 연산을 수행하도록 설계된 Dispatcher.Default 이다. 이 디스패쳐는 코드가 실행되는 컴퓨터의 CPU 개수와 동일한 수(최수 두개 이상)의 스레드 풀을 가지고 있다. (필자의 컴퓨터는 12개의 CPU를 가지고 있으므로 풀의 스레드 수는 12개이다.) suspend.. 2024. 2. 9.
2장 코루틴 라이브러리 - 코루틴 스코프 함수 먼저 여러개의 엔드포인트에서 데이터를 동시에 얻어야하는 중단 함수를 떠올려보자. 일단 가장 바람직한 방법을 보기 전에 차선책부터 보자. 아래는 동기적으로 진행되므로 2초가 걸린다. suspend fun getUserProfile(): UserProfileData { val user = getUserData() //(1초후) val notifications = getNotifications() //(1초후) return UserProfileData(user, notifications) } 두개의 중단 함수를 동시에 실행하기 위해 async로 매칭해야 한다. 하지만 async는 스코프를 필요로 하고 차선으로 GlobalScope를 사용하는 것은 올바르지 않다. suspend fun getUserProfile.. 2024. 2. 4.
2장 코루틴 라이브러리 - 예외 처리 코루틴은 부모-자식 관계이므로 자식 코루틴이 예외가 발생하면되면 부모 코루틴도 예외가 발생한다. 이러한 해결책을 try-catch 가 아닌 다른 방법을 사용해야 한다. 코루틴의 상호작용으로 인한 예외이므로 try-catch는 아무 기능도 할 수 없다. SupervisorJob 코루틴 종료를 멈추는 방법은 SupervisorJob를 사용하는 것이다. SupervisorJob를 사용하면 자식에서 발생한 모든 예외를 무시할 수 있다. fun main() { runBlocking { val scope = CoroutineScope(SupervisorJob()) scope.launch { delay(1000) throw Exception("error") } scope.launch() { delay(2000) pr.. 2024. 2. 3.
2장 코루틴 라이브러리 - Job과 자식 코루틴 기다리기 CoroutineContext에는 Job, CoroutineDispatcher, CoroutineName 이 있다고 이전 장에서 설명했다. 이 중 Job에 대해 더 자세히 알아보자. Job Job은 Coroutine Lifecycle를 관리하며, 부모-자식 관계의 특성을 가지고 있다. 더보기 부모 - 자식 설명 자식은 부모로부터 Context를 상속받는다. 부모는 모든 자식이 작업을 마칠 때 까지 기다린다. 부모 코루틴이 취소되면 자식 코루틴도 취소된다. 자식 코루틴에서 에러가 발생하면 부모 코루틴 또한 에러로 소멸된다. Job Lifecycle 위의 이미지에 대해 알 수 있는 항목은 default 상태인 Active 상태는 Job을 실행한다. 주어진 작업이 끝나면 Compleing 상태에서 자식이 모두.. 2024. 2. 3.
2장 코루틴 라이브러리 - Cancel 기본적인 취소 Job 인터페이스는 취소하게 해주는 cancel 함수를 가지고 있다. 이 함수를 호출하면 다음과 같은 효과가 있다. 호출한 코루틴은 첫 번째 중단점(아래 예제 delay)에서 Job 을 끝낸다. Job이 자식을 가지고 있다면, 그들 또한 취소가 되지만 부모는 취소되지 않는다. Job이 취소되면 취소된 Job은 코루틴의 부모로 사용될 수 없다. Cancelling → Cancelled 상태로 변경된다. fun main() = runBlocking { coroutineScope { val job = launch { repeat(1_000) { delay(100L) println("print : $it") } } delay(1000) job.cancel() job.join() println("S.. 2024. 2. 3.
2장 코루틴 라이브러리 - CoroutineContext CoroutineContext란 CoroutineContext는 코루틴이 실행되는 동안 필요한 모든 정보를 담고 있는 요소이다. 이는 다양한 요소들을 결합하여 코루틴의 동작 방식을 정의한다. CoroutineContext는 일종의 맵과 같이 작동하며, 각 요소는 키로 구분된다. CoroutineContext는 다음과 같은 요소를 포함할 수 있다. Job: 코루틴의 생명주기를 제어하며, 코루틴의 취소와 완료를 추적한다. CoroutineDispatcher: 코루틴이 어느 스레드에서 실행될지 결정한다. 예를 들어, Dispatchers.Main은 메인 스레드에서, Dispatchers.IO는 입출력 작업을 위한 백그라운드 스레드에서 코루틴을 실행하도록 지시한다. CoroutineName: 디버깅을 위해 코루.. 2024. 2. 3.