MVVM 디자인 패턴

도윤·2022년 1월 18일
0

MVVM은 최근에 iOS개발에 많이 이용되는 디자인 패턴이다.

장점!

  • Reduced complexity : MVVM 패턴은 많은 비지니스 로직을 View controller에서 제거하여 view controller를 가볍게 만듬.
  • Expressive: View Model은 View에 대한 비지니스 로직을 더 잘 표현.
  • Testability: View Model은 View Controller보다 테스트하기 더 쉬움.

Terminology

Binding : 하나를 다른것과 mapping하는 것.

  • View Controller : Model과 View사이에서 존재하고, delegate pattern을 사용하여 함께 연동하도록 함. Controller는 protocol을 통해 통신한다. 예를 들어 UITableView는 UITableViewDatasource protocol을 통해 통신한다.

  • Model: 데이터를 조작하는 데이터 및 논리가 저장되는 위치. 모델 객체나 네트워킹 코드가 여기에 저장되어 있을 수 있습니다.

  • View: User에게 정보를 보여주는 화면.

  • ViewModel: 뷰 내에 보여지는 필드들을 포함.

MVVM은 모델을 뷰의 다른 표현으로 변환해야 할 때 적합하며 여러 모델 간 변환이 필요한 뷰 컨트롤러를 가볍게 만든다.
MVVM은 특히 단위 테스트를 통해 테스트할 코드의 성향을 개선한다.
그러나 MVVM은 재사용 가능성을 극대화하기 위해 ViewModel을 초기에 설계하는 복잡하며, 더 많은 파일을 작성해야 한다.

Binding

MVVM 시행을 만드는 옵션이 있다.

  • closure를 사용하여 binding을 만드는 간단한 방법이 있는데 이것은 MVP 구현 방식이다.
  • view와 viewmodel을 바인드하기 위해 RXSwfit를 사용
  • Key-Value Obeserving pattern(KVO) 사용

구성

View

  • UI 컴포넌트를 표현
  • UI component를 ViewModel과 Bind
  • ViewModel을 만들고, binding을 작성하여 view controller가 ViewModel에서 발생하는 변화에 따라 변한다.

ViewModel

  • Error handling을 다룸.
  • view에 인터페이스를 제공
  • 표현되는 로직을 작성.
  • request를 수행하고 view Controller의 변화를 알리고, UIKit과 독립적이여야 한다. View Controller의 데이터와 형식이 맞아야 한다.

Model

  • Business Data + Business Logic + Business Rules


예제

BreachModel

public struct BreachModel{
    var title:String
}

BreachViewModel

class BreachViewModel {
    
    init(model:[BreachModel]? = nil) {
        if let inputModel = model {
            breaches = inputModel
        }
    }
    
    var breaches  = [BreachModel]()
    
    public func configure(_ view:BreachView) {
        view.titleLabel.text = breaches.first?.title
    }
    
}
extension BreachViewModel {
    func fetchBreaches(completion:@escaping(Result<[BreachModel],Error>) -> Void) {
        completion(.success(breaches))
    }
}

ViewController

import UIKit

class simpleViewController: UIViewController {

    var breachesViewModel = BreachViewModel(model: [BreachModel(title: "000webhost")])
    
    override func viewDidLoad() {
        super.viewDidLoad()
        let breachView = BreachView(frame: self.view.frame)
        breachesViewModel.configure(breachView)
        self.view.addSubview(breachView)
        breachView.translatesAutoresizingMaskIntoConstraints = false
        
        NSLayoutConstraint.activate([
            breachView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            breachView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            breachView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            breachView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
        ])
    }
}

networked 시행

viewModel이 api를 호출한다.

  • 지금 모델을 더 크게 만들고, codable하게 한다.
  • HTTP Manager는 API 호출을 하게 코드를 작성하고, closure를 통해 결과 데이터를전달한다.
  • ViewModel이 api로부터 fetching을 하면, JSON으로 decode하고, closure를 통해 ViewController에 전송한다.

FetchBreaches in the ViewModel

func fetchBreaches(completion: @escaping (Result<[BreachModel], Error>) -> Void) {
    HTTPManager.shared.get(urlString: baseUrl + breachesExtensionURL, completionBlock: { [weak self] result in
        guard let self = self else {return}
        switch result {
        case .failure(let error):
            print ("failure", error)
        case .success(let dta) :
            let decoder = JSONDecoder()
            do
            {
                self.breaches = try decoder.decode([BreachModel].self, from: dta)
                completion(.success(try decoder.decode([BreachModel].self, from: dta)))
            } catch {
                // deal with error from JSON decoding if used in production
            }
        }
    })
}

HTTPManager

class HTTPManager {
    static let shared: HTTPManager = HTTPManager()

    enum HTTPError: Error {
        case invalidURL
        case invalidResponse(Data?, URLResponse?)
    }
    
    public func get(urlString: String, completionBlock: @escaping (Result<Data, Error>) -> Void) {
        guard let url = URL(string: urlString) else {
            completionBlock(.failure(HTTPError.invalidURL))
            return
        }
        
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            guard error == nil else {
                completionBlock(.failure(error!))
                return
            }

            guard
                let responseData = data,
                let httpResponse = response as? HTTPURLResponse,
                200 ..< 300 ~= httpResponse.statusCode else {
                    completionBlock(.failure(HTTPError.invalidResponse(data, response)))
                    return
            }

            completionBlock(.success(responseData))
        }
        task.resume()
    }
}

출처: https://stevenpcurtis.medium.com/mvvm-in-swift-19ba3f87ed45

https://github.com/stevencurtis/SwiftCoding/tree/master/MVVM/SimpleMVVMNetwork

0개의 댓글