728x90
코틀린 코루틴을 요약한 내용입니다.
CoroutineScope 팩토리 함수
- CoroutineScope는 coroutineContext를 유일한 프로퍼티로 가지고 있는 인터페이스
- interface CoroutineScope { val coroutineContext: CoroutineContext }
- CoroutineScope 인터페이스를 구현한 클래스를 만들고 내부에서 코루틴 빌더를 직접 호출 가능
- 잘 사용되지 않음
- cancel, ensureActive 같은 다른 CoroutineScope의 메서드를 직접 호출하면 문제 발생 할 수 있음
class SomeClass: CoroutineScope { override val coroutineContext: CoroutineContext = Job() fun onStart() { launch { // .... } } }
- 코루틴 스코프 인스턴스를 프로퍼티로 가지고 있다가 사용하는 방법을 선호
- class SomeClass { val scope: CoroutineScope = ... fun onStart() { scope.launch { } } }
- 코루틴 스코프 객체를 만드는 가장 쉬운 방법은 CoroutineScope 팩토리 함수를 사용
- public fun CoroutineScope( context: CoroutineContext ): CoroutineScope = ContextScope( if (context[job] != null) { context } else { context = Job() } ) internal class ContextScope( context: Cor )
안드로이드에서 스코프 만들기
- 사용자에게 보여주는 부분을 ViewModels나 Presenters와 같은 객체로 추출
- 일반적으로 코루틴이 가장 먼저 시작되는 객체
- 유스 케이스나 저장소와 같은 다른 계층에서는 보통 중단 함수를 사용
- 안드로이드의 어떤 부분에서 코루틴을 시작하든지 간에 코루틴을 만드는 방법은 비슷
- BaseViewModel에서 스코프를 만들면, 모든 뷰 모델에서 쓰일 소크포를 단한 번으로 정의
- abstract class BaseViewModel: ViewModel() { protected val scope = CoroutineScope(TODO()) } class MainViewModel( private val userRepo: UserRepository, private val newsRepo: NewsRepository ): BaseViewModel { fun onCreate() { scope.launch { val user = userRepo.getUser() view.showUserData(user) } scope.launch { val news = newsRepo.getNews().sortedByDescending { it.data } view.showNews(news) } } }
- 안드로이드에서는 메인 스레드가 많은 수의 함수를 호출
- 기본 디스패처를 Dispatchers.Main으로 정하는 것이 좋음
abstract class BaseViewModel: ViewModel() { protected val scope = CoroutineScope(Dispatchers.Main) }
- 스코프 취소 가능하게 만들기
- 스코프를 취소 가능하게 하려면 Job이 필요
- CoroutinScope 함수가 잡을 추가하므로 따로 추가하지 않아도 상관 없음
abstract class BaseViewModel: ViewModel() { protected val scope = CoroutineScope(Dispatchers.Main + Job()) override fun onCleard() { scope.cancel() } }
- 자식 코루틴만 취소하는 것이 더 좋은 방법
- abstract class BaseViewModel: ViewModel() { protected val scope = CoroutineScope(Dispatchers.Main + Job()) override fun onCleard() { scope.coroutinContext.cancelChildren() } }
- 스코프를 취소 가능하게 하려면 Job이 필요
- 해당 스코프에서 코루틴이 독립적으로 작동해야 할 필요도 있음
- Job을 사용하고 에러가 발생하여 자식 코루틴 하나가 취소된 경우 부모와 다른 자식 코루틴 모두가 함께 취소가 됨
- Job대신 SupervisorJob을 사용
- abstract class BaseViewModel: ViewModel() { protected val scope = CoroutineScope(Dispatchers.Main + SupervisorJob()) override fun onCleard() { scope.coroutinContext.cancelChildren() } }
- 잡히지 않는 예외를 처리하는 방법
- BaseActivity에 예외 처리 핸들러를 한 번만 정의
- 뷰 모델에서 전달하는 방법으로 사용
- 잡히지 않는 예외가 있는 경우 CoroutinExceptionHandler를 사용
abstract class BaseViewModel( private val onError: (Throwable) -> Unit ): ViewModel() { private val exceptionHandler = CoroutineExceptionHandler { _, throwable -> onError(throwable) } private val context = Dispatchers.Main + SupervisorJob() + exceptionHandler protected val scope = CoroutineScope(context) override fun onCleard() { context.cancelChildren() } }
- BaseActivity에 예외 처리 핸들러를 한 번만 정의
viewModelScope와 lifecycleScope
- viewModelScope 또는 lifecycleScope를 사용 가능
- lifecycle-viewmodel-ktx 2.0.0 이상 버전에서
- Dispatchers.Main과 SupervisorJob을 사용하고, 뷰 모델이나 라이프사이클이 종료되었을 떄 잡을 취소시킨다는 점에서 우리가 만들었던 스코프와 거의 동일
- public val ViewModel.viewModelScope: CoroutineScope get() { val scope: CoroutineScope? = this.getTag(JOB_KEY) if (scope != null) { return scope } return setTagIfAbsent( JOB_KEY, CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) } internal class CloseableCoroutineScope( context: CoroutineContext ): Closeable, CoroutineScope { override val coroutineContext: CoroutineContext = context override fun close() { coroutineContext.cancel() } }
- 스코프에서 특정 컨텍스트가 필요 없다면 viewModelScope와 lifecycleScope를 사용하는 것이 좋음
- class ArticlesListViewModel( private val produceArticles: ProduceArticlesUseCase ): ViewModel() { private val _progressBarVisible = MutableStateFlow(false) val progressBarVisible: StateFlow<Boolean> = _progressBarVisible private val _articlesListState = MutableStateFlow<ArticlesListState> = _articlesListState fun onCreate() { viewModelScope.launch { _progressBarVisible.value = true val articles = produceArticles.produce() _articlesListState.value = ArticlesLoaded(articles) _progressBarVisible.value = false } } }
백엔드에서 코루틴 만들기
- 프레임워크에서 중단 함수를 기본적으로 지원
- 스프링 부트는 컨트롤러 함수가 suspend로 선언되는 것을 허용
- 따로 스코프를 만들 필요는 거의 없음
- 따로 만들어야 할 경우
- 스레드 풀(또는 Dispatchers.Default)을 가진 커스텀 디스패처
- 각각의 코루틴을 독립적으로 만들어 주는 SupervisorJob
- 적절한 에러 코드에 응답하고, 데드 레터를 보내거나 발생한 문제에 대해 로그를 남기는 CoroutineExceptionHandler
@Configuration public class CoroutineScopeConfiguration { }
추가적인 호출을 위한 스코프 만들기
- 스코프를 중단하기 위한 목적으로 사용하는 경우 SupervisorScope를 사용하는 것으로 충분
- val analyticsScope = CoroutineScope(SupervisorJob())
- 모든 예외는 로그를 통해 볼수 있으므로 예외를 관제 시스템으로 보내고 싶다면 CoroutineExceptionHandler를 사용
- private val exceptionHandler = CoroutineExceptionHandler = { _, throwable -> FirebaseCrashlytics.getInstance().recordException(throwable) } val analyticsScope = CoroutineScope( SuervisorJob() + exceptionHandler )
728x90
'코틀린 스터디' 카테고리의 다른 글
셀렉트 (0) | 2024.01.04 |
---|---|
채널 (0) | 2023.12.04 |
코루틴 기반 동시성 프로그래밍 (0) | 2023.11.24 |
코틀린 - 예외처리, Type System, 컬렉션 (0) | 2023.11.10 |
코틀린 - 클래스, 인터페이스, 상속 (0) | 2023.11.10 |