07
27

coroutine에 해당하는 처리를 해주려면 코루틴 스코프를 열어줘야한다. fragment에서는 lifecycleScope를 자주 썼는데, viewLifecycleOwner의 lifecycleScope도 있다는 걸 이번에 알게되어 어떤 차이점이 있는 지 비교해보겠다.

# `lifecycleScope`

public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
        get() = lifecycle.coroutineScope

lifecycleScope는 Fragment 자체 생명주기랑 직결되어있다. 즉 `onAttach` -> `onDetach`에 해당하는 생명주기가 lifecycleScope다.

Fragment의 생명주기를 따르기 때문에 view 생명주기랑은 약간의 차이가 있다. fragment에서 binding을 해제하는 걸 생각해보자.

override fun onDestroyView() {
    super.onDestroyView()
    _binding = null
}

view 생명주기랑 차이나서 메모리 누수 날 위험을 없애기 위해 onDestroryView 때 binding에 null을 넣어준다.

# `viewLifecycleOwner.lifecycleScope`

`lifecycleScope`는 이전과 동일한 객체를 사용하지만 `LifecycleOwner`를 뷰로 지정해서 사용하는 방식이다. 즉 코루틴 스코프의 생명 주기를 `onCreateView` -> `onDestroyView`로 한정하는 것이다. 그래서 ui 작업이면 lifecycle을 view로 한정짓고, ui에 종속된 작업이 아니면 fragment 생명주기를 따르게 하는 게 좋다. io작업은 화면 회전과 같은 view 생명주기가 초기화되는 시점에도 유지되어야하니 작업 형태에 따라 조절해야된다.

 

직접 코드로 비교를 해보겠다.

viewLifecycleOwner.lifecycleScope.launch {
    viewModel.data.collect { newData ->
        updateUI(newData)
    }
}

lifecycleScope.launch {
    while(isActive) {
        refreshData() // 백그라운드 작업, 네트워크 작업 등
    }
}

ViewModel로 분리하면서 작업이 ViewModel의 생명주기를 따른다면 크게 신경 쓸건 아니지만, 최적화할 수 있는 부분은 최적화 하고 넘어가는게 좋을 것 같다. view update에 사용할 작업은 viewLifecycleOwner를 명시해주자.

 

한가지 주의할 점이라면 viewLifecycleOwner가 사용할 수 있는 시점이 onCreateView 이후라는 것이다. onCreate에서 접근하면 예외가 발생해서 runtime 에어로 앱이 죽는다. 그렇다고 onCreate에서 그냥 lifecycleScope로 ui에 접근하는 것도 말이 안된다는 건 알고 있을 것이다. 

class TestFragment : Fragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        lifecycleScope.launch {
            while(isActive) {
                delay(1000)
                updateUI() // View가 아직 생성되지 않았는데 UI 업데이트 시도하는 중
            }
        }
    }
    
    private fun updateUI() {
        binding.tv.text = "Updated"
    }
}

이렇게 하는 걸 지양해야된다.

 

도움이 됐다면 댓글이나 공감 버튼 한 번씩 누르고 가주세요!
반응형
COMMENT