이모저모/Swift
Observable과 ViewModel State에 대한 고찰
ARpple
2025. 3. 26. 00:57
대부분 iOS ViewModel에서 상태를 관리하기 내부에 쓰이는 값들을 하나의 구조체로 그 객체가 갖는 상태를 선언해서 관리한다.
@Observable class ViewModel{
var state:State
struct State{
var isFirstAppear = true
var isTipAlertPresented = false
}
...
}
자세한 Observable 동작은 공식문서에...
Migrating from the Observable Object protocol to the Observable macro | Apple Developer Documentation
Update your existing app to leverage the benefits of Observation in Swift.
developer.apple.com
실험 가설
- 2개의 State 내부 변수를 둔다.
- 두 변수 각각
withObservationTracking
검사하는 메서드를 만든다. - 한 변수만 변화하고 두 메서드 모두 실행한다.
- 두 메서드의
withObservationTracking
모두 onChange 핸들러에 print 문이 출력되면 Observable의 고유한 프로퍼티 접근에 위배된다.
ViewModel 코드
@Observable
public final class ViewModel {
public var state: State
public init() {
self.state = State()
}
@MainActor
public struct State: Equatable {
public var isFirstAppear = true
public var isTipAlertPresented = false
}
public func renderIsFirstAppear() {
withObservationTracking {
print("renderIsFirstAppear: ",self.state.isFirstAppear)
} onChange: {
print("Schedule renderer. renderIsFirstAppear")
}
}
public func renderIsTipAlertPresented() {
withObservationTracking {
print("renderIsTipAlertPresented: ",self.state.isTipAlertPresented)
} onChange: {
print("Schedule renderer. renderIsTipAlertPresented")
}
}
}
Timer 실행 코드
Task {
let exploreVM = ViewModel()
Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
exploreVM.state.isFirstAppear.toggle()
exploreVM.renderIsFirstAppear()
exploreVM.renderIsTipAlertPresented()
}
exploreVM.renderIsFirstAppear()
exploreVM.renderIsTipAlertPresented()
}
실행 결과
두 render 함수 모두 호출된다..!
해결법 - State에 Observable을 걸어준다면?
State에 Observable을 주고 class로 바꿈
import Observation
@Observable
public final class ExploreViewModelObservable {
...
@Observable
public class State {
public var isFirstAppear = true
public var isTipAlertPresented = false
}
}
실행 결과
값을 바꾼 renderIsFirstAppear의 render만 호출된다
If) State에 Observable을 안 걸어주고 class로만 한다면?
@Observable
public final class ExploreViewModelObservable {
...
public class State {
public var isFirstAppear = true
public var isTipAlertPresented = false
}
}
실행 결과
두 랜더링 모두 호출이 안된다..! Observer가 없다는 뜻..!
ps. 사실 웬만하면 크게 렌더링 성능이 떨어질까 싶긴한데...