프로젝트에 있는 모든 Global Scope를 제거하였다. 그 이유는?
그 이유에 대해서도 3가지나 나와 있음.
하지만 그냥 3줄 나와있고 끝임.
나는 궁금하다! 왜 쓰면 안될까?
자 그럼 공식문서에서 말해주는 이유에 대해서 가져와보겠다.
This is similar to the Inject Dispatchers best practice. By using GlobalScope, you're hardcoding the CoroutineScope that a class uses bringing some downsides with it:
Promotes hard-coding values. If you hardcode GlobalScope, you might be hard-coding Dispatchers as well.
Makes testing very hard as your code is executed in an uncontrolled scope, you won't be able to control its execution.
You can't have a common CoroutineContext to execute for all coroutines built into the scope itself.
한국어로 바꿔보면
-> 이게 왜 안좋냐? 공식문서에 따르면 Dispatcher를 생성하지 말고 주입해서 끼우라고 했음.
그럼 Dispatcher는 왜 주입해야하냐?
-> 테스트 용이성, 유연성, 유지보수성 때문이다.
// DO inject Dispatchers
class NewsRepository(
private val defaultDispatcher: CoroutineDispatcher = Dispatchers.Default
) {
suspend fun loadNews() = withContext(defaultDispatcher) { /* ... */ }
}
// DO NOT hardcode Dispatchers
class NewsRepository {
// DO NOT use Dispatchers.Default directly, inject it instead
suspend fun loadNews() = withContext(Dispatchers.Default) { /* ... */ }
}
-> ㅇㅋ 이건 이해 완료
-> 공식문서에는 can't have니까 가질 수 없다지만.. 사실 가질 수 없다보단, 공통된 하나의 CoroutineContext를 쓸 이유가 없다가 더 맞는 말이다. 코루틴의 장점은 유연성으로, CoroutineScope 마다, 심지어 그 scope안에서도 context를 바꿔서 실행하게 할 수 있는데, 그에 반해 GlobalScope를 써버리면 그 자체가 불가능하니까 구리다..는 거다.
val scope = CoroutineScope(Job()) // 기본적으로 어떤 Dispatcher도 지정하지 않음
scope.launch(Dispatchers.Main) {
println("UI 업데이트 작업")
}
scope.launch(Dispatchers.IO) {
println("네트워크 작업")
}
정리하자면 coroutine의 설계 원칙인 "유연성"을 활용할 수 없게 되어 버린다는 내용이다.
그외에도 쓰지 말아야하는 이유를 개발자의 관점에서 더 말해보자면, 2번이랑 연관이 있긴 한데,
메모리 누수가 뭐냐?
응용 프로그램에서 데이터를 메모리에 올려놓은 후, 더 이상 사용하지 않음에도 불구하고 해제하지 않아 사용할 수 있는 메모리가 점점 줄어드는 현상을 말합니다.
사용하지 않는 메모리는 해제해야한다. 그걸 자동으로 해주는게 GC (Garbage Collection)인데, GlobalScope의 경우 앱의 전체를 주기로 작동하는 녀석이기 때문에, 앱이 종료되기 전까지는 죽지 않는다. viewModelScope
나 lifecycleScope
를 사용한다면 알아서 GC가 처리해줄텐데, 앱 전체를 무대로 하니.. 알아서 처리되지 않는다.
GlobalScope.launch {
delay(5000) // 5초 대기
println("작업 완료")
}
// 액티비티 종료 후에도 "작업 완료"가 출력됨.
그렇기 때문에 해당 사용이 완료되더라도, 참조가 남아 있어 GC 되지 않고, 코루틴이 계속 실행될 수 위험이 있다. 이러한 리소스 낭비가 발생할 수 있기 때문에 GlobalScope 남발을 지양해야 한다.
비슷한 문제로, 생명주기를 고려하기 힘들다는 문제도 있다. 안드로이드에서는 lifecycle
이라는것이 있는데, 액티비티가 시작되서~종료되는 그 일련의 과정을 말한다. 네트워크 호출의 경우 해당 화면(액티비티)에서 나가버리면 해당 네트워크 호출은 더이상 필요가 없기때문에 cancel 해야한다. 그런 lifecycle을 다 알고서 실행되는 코드의 경우 사용자가 뒤로가기를 누르면 해당 네트워크 호출을 자동으로 취소해준다. 하지만 GlobalScope는 말그대로 글로벌 영역에서 실행되기 때문에 lifecycle 따위는 알지 못한다.
다 일일히 개발자가 화면에서 벗어나면~ 호출을 취소~ 하는 코드를 작성해야하는데, 어느 개발자가 그 많은 코드에 취소 명령을 적고 있겠나, 생각만 해도 번잡시럽다. 그래서 GlobalScope
보다는viewModelScope
, lifecycleScope
등 수명 주기를 인식하는 스코프를 사용하는게 앱에게도, 그리고 개발자의 생산성에도 큰 도움이 될 것이다.
로깅 같이 정말 앱의 수명주기와 별개로 돌아가야하는 로직이라면.. 추천한다..
https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html
https://developer.android.com/kotlin/coroutines/coroutines-best-practices?hl=ko