Swift/iOS의 MVVM 패턴 샘플

8795 단어 learningswiftios
MVVM은 점점 더 많은 인기를 얻고 있는 패턴이며 이벤트 지향적인 애플리케이션이 점점 더 많아지고 있습니다. iOS 개발에서 MVVM과 클래식 MVC를 사용하면 비즈니스 로직을 프레젠테이션 레이어로 완전히 나누는 것부터 시작하여 많은 이점이 있습니다. 너무 많은 일을 담당하는 "거대한 ViewController"대신 네트워크 또는 로컬 데이터베이스에서 데이터 요청과 같은 일을 다른 엔티티에 위임할 수 있습니다.



이 모든 애플리케이션 로직은 뷰가 무엇인지 또는 뷰가 무엇을 하는지 결코 알지 못하는 ViewModel 내에 있습니다. 이 아키텍처를 상당히 테스트 가능하게 만들고 보기에서 복잡성을 제거하여 가능한 한 멍청하게 만듭니다.

뷰는 ViewModel을 소유하고 UI 업데이트를 위한 변경 사항을 항상 "수신"합니다. 여기에 유명한 "양방향 데이터 바인딩"이 있습니다. 뷰에 변경 사항이 있으면 모델이 업데이트되고 모델에 변경 사항이 있으면 뷰가 업데이트되며 항상 ViewModel을 매개로 합니다.

글쎄, 우리가 이것을 실제로 본다면 어떨까요? 다음 시나리오를 상상해 보십시오. Github의 나머지 API와 연결하고 리포지토리를 검색하고 그 중 하나를 선택하고 가장 최근 커밋을 볼 수 있는 앱을 개발하려고 합니다. 시작하자!

검색 기능부터 시작하겠습니다. 간결함을 위해 많은 항목을 무시하지만 마지막에 모든 소스 코드를 볼 수 있습니다.

ViewModel을 충족하는 프로토콜을 코딩해 보겠습니다.

protocol SearchRepositoriesDelegate {
    func searchResultsDidChanged()
}

protocol SearchViewModelType {
    var results: [SearchResult] {get}

    var query: String {get set}

    var delegate: SearchRepositoriesDelegate? {get set }
}


결과에 대한 속성, 해당 결과를 검색하는 쿼리에 대한 속성 및 변경 사항이 있음을 뷰에 알리는 대리자. 간단하죠?

다른 시나리오에서 'SearchRepositoriesDelegate'는 일부 데이터 바인딩 메커니즘으로 대체되거나 관찰 가능 항목을 구현해야 하지만 이는 다른 게시물에 대한 것입니다.

그러면 ViewModel이 다음과 같은 방식으로 작동합니다.

1- 보기에는 ViewModel에 대한 참조가 있습니다.
2- 사용자가 검색창에 입력하는 동안 뷰는 쿼리를 기반으로 ViewModel의 '쿼리' 속성을 업데이트합니다.
3- 'query' 속성이 업데이트될 때마다 ViewModel은 Github API rest에 요청하고 결과를 업데이트합니다.
4- 서버 응답이 있으면 ViewModel은 결과에 변경 사항이 있음을 뷰에 알립니다(델리게이트를 통해).
5- 'searchResultsDidChanged' 함수가 호출될 때마다 뷰가 UI를 업데이트합니다.

구현 방법을 살펴보겠습니다.

모델 보기:

    class SearchViewModel : SearchViewModelType {
    var delegate: SearchRepositoriesDelegate?

    var results : [SearchResult] = [] {//0 results by default
        didSet{
            delegate?.searchResultsDidChanged() //notify
        }
    }

    var searchService: SearchService

    var query: String = "" {
        didSet {
            if query == "" {
                results = []
            }else {
                performSearch()
            }
        }
    }

    init(service: SearchService) {
        self.searchService = service
    }

    private func performSearch() {
        searchService.search(query: self.query)
            .onSuccess { results in
                self.results = results
            }.onFailure { error in
                //do nothing
        }
    }
}


보다:

class SearchViewController: UIViewController {

    ...

    var searchViewModel: SearchViewModelType!

    override func viewDidLoad() {
        super.viewDidLoad()

        searchViewModel.delegate = self

        ...
    }
}

extension SearchViewController: UISearchBarDelegate {
    func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
        searchViewModel.query = searchText
    }
}

extension SearchViewController: SearchRepositoriesDelegate {
    func searchResultsDidChanged() {
        self.tableView.reloadData()
    }
}

...


사용자가 검색 창에 입력하는 동안 'query' 속성이 업데이트되고 ViewModel이 서버 측에 요청합니다. 요청이 완료되면 뷰에 알리고 UI의 변경 사항을 반영하기 위해 UITableView 'reloadData()' 함수를 호출합니다. 보시다시피, SearchViewModel 클래스는 제대로 작동하는지 확인하기 위해 모의 'SearchService' 객체를 생성하기만 하면 되므로 테스트하기가 매우 쉽습니다. ViewModel에는 뷰에 대한 참조가 없으며 뷰는 모든 비즈니스 로직에서 제외됩니다.

그게 다야! 두려워하지 말고 this link의 모든 소스 코드를 보고 질문에 대해 의견을 말하십시오.

좋은 웹페이지 즐겨찾기