아티클 목록으로 가기

snapshotFlow의 내부 메커니즘

skydovesJaewoong Eum (skydoves)||5분 소요

snapshotFlow의 내부 메커니즘

Jetpack Compose 생태계에서 상태(state)는 일반적으로 동기적으로 소비됩니다. 컴포저블 함수는 리컴포지션(Recomposition) 과정에서 State<T> 객체를 읽어 현재 값을 가져옵니다. 하지만 오늘날 대부분의 안드로이드 아키텍처는 비동기 스트림을 기반으로 설계되어 있으며, Kotlin의 Flow를 사용하여 시간에 따른 값의 흐름을 표현합니다. snapshotFlow 함수는 이 두 세계를 연결하는 유용하면서도 매우 효율적인 브릿지(bridge) 역할을 하며, Compose의 풀 기반(pull-based) State를 푸시 기반(push-based) Flow로 변환할 수 있게 해 줍니다.

snapshotFlow의 내부 메커니즘을 분석해 보면, 정교한 3단계 시스템으로 구성되어 있음을 알 수 있습니다. 전역 상태 변경을 **관찰(observe)**하고, 사용자 코드가 읽은 특정 상태 객체를 **추적(track)**하며, 코루틴 **Channel**을 활용하여 재평가를 트리거합니다. 이 과정에서 정확성과 효율성을 모두 보장하는 구조입니다. Compose의 상태 시스템과 코루틴이 어떻게 유기적으로 연결되는지 이해하는 데 있어 snapshotFlow는 핵심적인 사례라 할 수 있습니다.

snapshotFlow 메커니즘의 핵심 구성 요소

flow { ... } 빌더 내부의 구현은 크게 세 가지 핵심 역할로 나눌 수 있으며, 이 세 가지가 긴밀하게 협력하여 동작합니다. 각 구성 요소가 어떤 책임을 맡고 있는지 하나씩 살펴보겠습니다.

1. 전역 옵저버: Snapshot.registerApplyObserver

메커니즘의 첫 번째이자 가장 핵심적인 부분은 애플리케이션 전체에서 발생하는 모든 상태 변경에 대한 리스너를 등록하는 것입니다. 이 역할은 Snapshot.registerApplyObserver를 통해 수행됩니다.

val appliedChanges = Channel<Set<Any>>(Channel.UNLIMITED)

val unregisterApplyObserver =
    Snapshot.registerApplyObserver { changed, _ ->
        // ... (maybeObserved 확인 로직은 간결함을 위해 생략)
        appliedChanges.trySend(changed)
    }
  • 동작 방식: 이 함수는 Compose 런타임이 가변 스냅샷(mutable snapshot)을 성공적으로 "적용(apply)"할 때마다 호출되는 람다를 등록합니다. 스냅샷 적용은 상태 쓰기(예: myState.value = "new")의 최종 단계에 해당합니다. 여기서 changed 매개변수는 해당 트랜잭션에서 수정된 모든 StateObject 인스턴스의 Set입니다.
  • Channel의 역할: 옵저버가 하는 일은 단 하나, 변경된 상태 객체의 집합을 Channel로 전송하는 것입니다. Channel은 코루틴의 통신 프리미티브(communication primitive)로, Channel.UNLIMITED를 사용함으로써 옵저버는 블로킹 없이 상태 변경 알림을 전송할 수 있습니다. 덕분에 메인 상태 시스템의 동작을 전혀 방해하지 않습니다.

이 아티클은 구독자 전용입니다

Dove Letter를 구독하시면 안드로이드, 코틀린 개발 관련 독점 아티클의 전체 내용을 볼 수 있습니다.

구독하기
아티클 목록으로 가기