본문 바로가기

iOS/SwiftUI

SwiftUI) ObservableObject와 상속

ViewModel 객체 분리

뷰에서 표현해야할 데이터를 네트워킹을 통해 외부에서 가져와야할 때 ViewModel 객체로 분리시켜서 데이터를 업데이트 했습니다. 네트워킹을 통해 데이터를 가져와야 하는 뷰가 많아서 각 뷰모델이 겹치는 경우가 많아서 전체적으로 사용할 기본 뷰모델 MiniVibeViewModel을 만들었습니다.

class MiniVibeViewModel: ObservableObject {

    private let network = NetworkService(session: URLSession.shared)
    private var cancellabes = Set<AnyCancellable>()

    func internalFetch(endPoint: MiniVibeType, id: Int? = nil, filterQuery: String? = nil, limitQuery: String? = nil,  completion: @escaping (Data) -> Void) {
        let url = URLBuilder(pathType: .api, endPoint: endPoint, id: id, filterQuery: nil, limitQuery: nil).create()

        guard let request = RequestBuilder(url: url,
                                           body: nil,
                                           headers: nil).create() else { return }
        network.request(request: request)
            .sink { result in
                switch result {
                case .failure(let error):
                    print(error)
                case .finished:
                    print("success")
                }
            } receiveValue: { data in
                completion(data)
            }
            .store(in: &cancellabes)
    }
}

각 뷰모델마다 다른 부분은 네트워킹을 통해 받아온 데이터를 처리하는 부분입니다. 따라서 그 부분만 클로저로 분리시켜서 넘겨주는 방식으로 작성하였습니다.

그 다음 구체적인 뷰모델을 작성할 때 앞서 작성한 MiniVibeViewModel을 상속받았습니다.

class ThumbnailListViewModel: MiniVibeViewModel {
    @Published var thumbnails = [Thumbnailable]()

    private let network = NetworkService(session: URLSession.shared)
    private var cancellabes = Set<AnyCancellable>()

    func fetch(type: MiniVibeType, id: Int) {

        internalFetch(endPoint: type, id: id) { [weak self] data in
            switch type {
            case .magazines:
                if let decodedData = try? JSONDecoder().decode(Magazines.self, from: data) {
                    DispatchQueue.main.async {
                        self?.thumbnails = decodedData.magazines
                    }
                }
            default:
                if let decodedData = try? JSONDecoder().decode(Playlists.self, from: data) {
                    DispatchQueue.main.async {
                        self?.thumbnails = decodedData.playlists
                    }
                }
            }
        }

    }
}

에러 발생

이때 문제가 발생하였습니다. 뷰모델을 분리시켜서 상속받기 전에는 잘 동작하던 뷰 업데이트가 동작하지 않았습니다.🤦‍♀️

스크린샷 2020-12-02 오후 7 12 45

디버깅을 해보니 네트워킹을 통해 데이터를 잘 받아오지만 업데이트 되었다는 것을 뷰에게 알리지 못하는 것 같았습니다.(추측)🧐

스크린샷 2020-12-02 오후 7 12 19

뷰가 onAppear할 때 제대로 데이터가 업데이트 되었다면 해당 데이터를 출력하기 위한 부분이 동작해야하는데 이 부분이 동작되지 않는 것을 볼 수 있습니다.

스크린샷 2020-12-02 오후 7 12 45

해결

혹시나 해서 부모 뷰모델에서 ObservableObject 프로토콜을 제외하고 각 뷰모델마다 따로 상속받도록 하였습니다.

class MiniVibeViewModel {
    ...
class ThumbnailListViewModel: MiniVibeViewModel, ObservableObject {
    @Published var thumbnails = [Thumbnailable]()
    ...

이렇게 했더니 제대로 동작합니다. 👀

스크린샷 2020-12-02 오후 7 12 45

왜인지는 모르겠습니다.

원인 분석

1. 부모객체가 상속받은 프로토콜은 상속이 안된다.

그랬으면 애초에 각 뷰모델에 @Published를 포함하고 있어서 ObservableObject을 따로 상속받지 않으면 오류가 발생했어야 합니다.

2. @Published 프로퍼티가 변화했을 때 이를 사용하는 부분에 알려주는 기능만 상속이 안된다.

적어놓고도 이해가 안가네요. 부분적인 기능만 상속되기도 하나요? 🤭

3. 그냥 Xcode가 고장났다.

왠지모르게 가장 이해가 되...읍..