-> 변경 사항을 기억하고 관찰할 수 있는 분리된 상태
상태관리 시스템에서 1은 부적합하다.
컴포즈의 스냅샷 시스템은 행위자 시스템은 아니지만 2번에 가까운 접근방식.
컴포즈의 스냅샷 상태 시스템은 동시성 제어 시스템을 사용하여 모델링 됨.
-> 모든 스냅샷 상태 개체가 구체화하는 인터페이스
컴포즈의 상태 스냅샷 시스템은 동시성 제어 시스템을 기반으로 구현된다.
“동시성 제어“는 동시 작업에 대한 올바른 결과를 보장하는 것, 즉 조정 및 동기화를 의미
Compose 상태 스냅샷 시스템에서는 스냅샷의 상태 변경 사항이 다른 스냅샷에 전파될 때, 상태 쓰기 작업이 원자적인 단일 작업으로 작용
컴포즈는 낙관적임
Jetpack Compose가 스냅샷 시스템을 구현하는 데 사용함
Q. 스레드의 갯수만큼 스냅샷이 생성되는 것인지? 1:1 관계?
해당 시점의 모든 스냅샷 상태 객체에 대한 현재 상태의 복사본 -> State 인터페이스의 구현체
Compose Runtime은 프로그램의 현재 상태를 모델링하기 위해 Snapshot 클래스를 제공
O. breakPoint와 유사한 느낌으로, 디버깅 시 활용도가 많을 것 같은 느낌
enter 함수는 일반적으로 “스냅샷에 진입한다“라고도 불리며, 람다를 스냅샷의 컨텍스트에서 실행.
따라서 스냅샷이 모든 상태에 대한 진실의 원천(Source of Truth)이 됨.
스냅샷은 트리를 형성할 수 있음
val snapshot = Snapshot.takeSnapshot()
val nestedSnapshot = snapshot.takeNestedSnapshot()
스냅샷이 반드시 스레드에 바인딩되지는 않음
-> 스냅샷에는 스레드가 자유롭게 오고갈 수 있음
Compose Runtime에는 관찰된 상태 값이 변경될 때 recomposition을 트리거 함.
enter 호출 내에서 스냅샷에서 상태 개체를 읽을 때마다 알림을 받음
// takeSnapShot의 람다가 Observer.
val snapshot = Snapshot.takeSnapshot { reads++ }
// enter를 통해 상태값을 읽으면 위의 observer 블록이 실행됨.
snapshot.enter { /* some state reads */ }
snapshotFlow
LaunchedEffect(listState) {
// 람다 내의 state가 변경되면 변경된 state 값을 방출
snapshotFlow { listState.firstVisibleItemIndex }
.map { index -> index > 0 }
.distinctUntilChanged()
.filter { it == true }
.collect {
MyAnalyticsService.sendScrolledPastFirstItemEvent()
}
}
관찰자는 값 쓰기(상태 업데이트)에 대해서도 연동이 가능하고, writeObserver는 가변적인 스냅샷을 생성할 때만 전달할 수 있음.
Q.
- snapshot.enter(block)은 읽기 작업을 수행하고, 그 과정에서 readObserver가 실행
- applyAndCheck(snapshot)은 쓰기 작업을 수행하고, 그 과정에서 writeObserver가 실행
변경 사항은 트리의 아래쪽에서 위쪽으로 전파. 중첩된 하위의 가변적인 스냅샷은 변경 사항을 먼저 적용한 다음 해당 변경 사항을 상위 스냅샷이나, 트리의 루트인 경우는 전역 스냅샷에 전파
루트 스냅샷이 적용될 때만 변경 사항이 전역 상태에 도달
MutableSnapshot.apply가 이 스냅샷을 적용하지 못하면 composition 중에 계산된 스냅샷과 변 경 사항이 삭제되고 새 composition이 다시 계산되도록 예약
수명주기는 항상 apply 및 dispose를 호출하여 종료
Q. 스냅샷 트리에서 반드시 상위 스냅샷까지 적용되거나 아예 취소
GlobalSnapshot은 전역 상태를 유지하는 가변적인 스냅샷 타입 중 하나
Jetpack Compose에서는 스냅샷 시스템을 초기화하는 동안 전역 스냅샷이 생성됨
이후 각 composition은 자체적으로 중첩된 가변 스냅샷을 생성하고 트리에 연결함.
-> 외부에서는 GlobalSnapshot을 참조할 수 없음 (private, internal)
다중버전 동시성 제어의 성능상 이점 (N은 상태 개체의 수)
내부적으로 스냅샷의 상태 객체는 StateObject로 모델링되며, 해당 객체에 대해 저장된 각각의 여러 버전은 StateRecord로 모델링 되어있음
-> 트리 구조니까 스냅샷에 대한 여러 복사된 스냅샷들이 존재함
스냅샷에 표시되는 버전(기록)은 스냅샷이 생성되었을 때 사용 가능한 가장 최신의 유효한 버전
기록된 ID가 스냅샷 ID(즉, 현재 또는 이전 스냅샷에서 생성됨)보다 작거나 같으며, 스냅샷의 유효하지 않은 객체를 담는 Set에 속해있지 않은 경우 스냅샷에 대해 유효한 상태 기록으로 간주
Q. 파생된 스냅샷인데 원본 스냅샷보다 ID가 같거나 작다? 커야 하는 게 아닌지...
유효하지 않은 스냅샷?
• 현재 스냅샷 이후 에 생성된 기록은 생성된 스냅샷이 이 스냅샷 이후에 생성되었기 때문에 유효하지 않은 것으로 간주됨.
• 해당 스냅샷이 생성될 당시 이미 열려있던 스냅샷에 대해 생성된 기록은 유효하지 않은 Set에 추가되므로, 이 역시 유효하지 않은 것으로 간주됨.
• 적용되기 전에 폐기된 스냅샷에서 생성된 기록에도 유효하지 않은 것으로 명시적으로 플래그가 지정됨.
읽기
쓰기
다중 버전 동시성 제어는 동일한 상태의 여러 버전을 저장할 수 있다는 사실(기록)로 인해
더 이상 사용되지 않으며, 절대 읽을 수 없는 버전을 제거
열려있는 스냅샷(open snapshots)?
재사용
1. 가장 낮은 순서의 열려있는 스냅샷을 추적.
2. 기록이 유효하지만 가장 낮은 순서의 열린 스냅샷에 표시되지 않는 경우, 다른 스냅샷에서 선택 되지 않으므로 안전하게 재사용할 수 있음
스냅샷을 닫으면 열려 있는 스냅샷 ID를 담는 Set에서 해당 ID가 효과적으로 제거되며, 그 결과 생성된 모든 새 스냅샷에서 모든 상태 기록(ID와 연결된 기록)을 보고 읽을 수 있게 됨.
-> 즉 효과적인 상태 변경 전파 방법
스냅샷을 닫을 때, 즉시 생성된 새 스냅샷으로 교체하고 싶은 경우 이를 스냅샷 “고급화 (advancing)“ 라고 함.
글로벌 스냅샷은 적용(apply)되지 않고 항상 고급화되어 생성된 새로운 글로벌 스냅샷에 모든 변경 사항이 표시.
활성 스냅샷을 담는 Set 목록에서 스냅샷을 제거