본문 바로가기

Combine

[Combine] ViewModel 의 상태변화를 View 에게 알려주고 싶다면 어떻게 해야할까?

 

 

오늘은

업무를 진행할때 겪은 어려움을 해결했던 과정에 대해

글을 작성해보려 합니다.

 

제목 그대로 ViewModel 의 상태변화를 View 에게 알려주고 싶다면

어떻게 해야할까요?

 

@ObservedObject private var contentViewModel: ContentViewModel

 

이런식으로 @ObservedObject 를 붙여주게 된다면

ViewModel 의 값이 바뀔때 마다 

View 가 알 수 있게 됩니다.

 

그렇다면, ViewModel 안에 또 다른 ViewModel 의 

상태 변화를 알고 싶다면 어떻게 해야할까요?

 

final class ContentViewModel: ObservableObject {
    @ObservedObject var detailViewModel: DetailViewModel
}

 

전 이전과 같은 방법으로

@ObservedObject 를 detailViewModel 앞에 작성하여

빌드를 진행했습니다.

 

하지만 의도했던것과는 다르게 

View 가 업데이트가 되질 않더라구요.

 

그 이유는 ContentView 는 ContentViewModel 을 상태변화를

감지할 수 있지만

ContentViewModel 안에서 감지하는 detailViewModel 의

상태변화를 알 수 없기 때문이였습니다.

 

그렇기 때문에 ContentViewModel 에서 

detailViewModel 의 랜덤값이 변화할 때 감지할 수 있는

Publisher 를 하나 만들어

View 업데이트 문제를 해결했습니다.

 

final class ContentViewModel: ObservableObject {
    
    var detailViewModel: DetailViewModel
    private var cancellables: Set<AnyCancellable>
    
    init(detailViewModel: DetailViewModel) {
        
        self.detailViewModel = detailViewModel
        cancellables = .init()
        
        bind()
    }
    
    private func bind() {
        
        detailViewModel
            .$randomValue
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
}

 

sink 를 사용하여 randomValue 가 변화하면 

objectWillChange 메서드를 호출하게 되는데

이는 ContentView 에서 

"@ObservedObject private var contentViewModel: ContentViewModel"

로 선언된 contentViewModel 의 변경을 요청하게 됩니다.

따라서 의도했던 대로 ContentView 의 UI 가 업데이트 되는 것을

확인할 수 있습니다.

 

아래는 작성했지만 의도했던대로 동작하지 않았던 코드와

의도한 대로 동작했던 코드를 올려두도록 하겠습니다.

읽어주셔서 감사합니다!

 

 

1. 의도 대로 동작하지 않았던 코드

 

struct ContentView: View {
    
    @ObservedObject private var contentViewModel: ContentViewModel
    
    init(contentViewModel: ContentViewModel) {
        self.contentViewModel = contentViewModel
    }
    
    var body: some View {
        VStack(spacing: 30) {
            Button {
                self.contentViewModel.detailViewModel.requestRandomValue()
            } label: {
                Text("클릭하면 아래의 숫자가 업데이트 됩니다.")
            }
            Text(String(self.contentViewModel.detailViewModel.randomValue))
        }
    }
}

final class ContentViewModel: ObservableObject {
    
    @ObservableObject var detailViewModel: DetailViewModel
    
    init(detailViewModel: DetailViewModel) {
        self.detailViewModel = detailViewModel
    }
}

final class DetailViewModel: ObservableObject {
    
    @Published var randomValue = Int.random(in: 1...100)
    
    func requestRandomValue() -> Int {
        randomValue = Int.random(in: 1...100)
        print("\(randomValue)")
        return randomValue
    }
}

 

2. 의도 대로 동작한 코드

 

struct ContentView: View {
    
    @ObservedObject private var contentViewModel: ContentViewModel
    
    init(contentViewModel: ContentViewModel) {
        self.contentViewModel = contentViewModel
    }
    
    var body: some View {
        VStack(spacing: 30) {
            Button {
                self.contentViewModel.detailViewModel.requestRandomValue()
            } label: {
                Text("클릭하면 아래의 숫자가 업데이트 됩니다.")
            }
            Text(String(self.contentViewModel.detailViewModel.randomValue))
        }
    }
}

final class ContentViewModel: ObservableObject {
    
    var detailViewModel: DetailViewModel
    private var cancellables: Set<AnyCancellable>
    
    init(detailViewModel: DetailViewModel) {
        
        self.detailViewModel = detailViewModel
        cancellables = .init()
        
        bind()
    }
    
    private func bind() {
        
        detailViewModel
            .$randomValue
            .receive(on: DispatchQueue.main)
            .sink { [weak self] _ in
                self?.objectWillChange.send()
            }
            .store(in: &cancellables)
    }
}

final class DetailViewModel: ObservableObject {
    
    @Published var randomValue = Int.random(in: 1...100)
    
    func requestRandomValue() -> Int {
        randomValue = Int.random(in: 1...100)
        print("\(randomValue)")
        return randomValue
    }
}