[iOS] MVP Architecture

Emily·2025년 8월 18일

MVP 패턴은 Model-View-Presenter로 이루어져 있다. 지금까지 접해본 아키텍처는 모두 MV* 형태였기 때문에, 이제는 ModelView가 어떤 책임을 찾는 객체인지 모르면 바보다.

MVP 아키텍처 다이어그램

그냥 보면 ViewModel을 중재하는 객체가 존재한다는 점에서 MVVM 아키텍처와 다를 게 없어보인다. (UIKit 프로젝트에서) MVP 패턴에서도 MVVM처럼 ControllerView에 속하여 사용자의 상호작용에 따른 이벤트를 처리한다. 둘 다 UI 코드와 비즈니스 로직을 분리하는 아키텍처로, 그냥 봤을 때 차이라고는 ViewModel의 존재가 Presenter로 대체된 걸로 밖에 안보이는데, 이 두 아키텍처의 차이는 어디서 발생할까?

우선 ViewModelView를 모르지만(참조하지 않지만) PresenterViewProtocol로 추상화하여 간접 의존한다(이 때, 약한 참조 필요). MVVM에서는 바인딩을 통해 ViewModel의 상태 변경이 View에 자동 반영되지만 MVP에서는 PresenterView에 접근하여 수동으로 업데이트를 요청한다.

코드 생김새를 직접 보고 차이를 체감해보자.

  • MVVM에서의 데이터-UI 바인딩 코드
class ExampleViewModel: ObservableObject {
	@Published var examples: [Example] = []
    
    private let service: ExampleServiceProtocol
    private var cancellables = Set<AnyCancellable>()
    
    init(service: ExampleServiceProtocol) {
        self.service = service
    }
    
    func loadExamples() {
    	service.fetchExamples()
        	.sink { [weak self] examples in
            	self?.examples = examples
            }
            .store(in: &cancellables)
    }
}

class ExampleViewController: UIViewController, UITableViewDataSource {
	private let viewModel = ExampleViewModel(service: ExampleService())
    private var cancellables = Set<AnyCancellable>()
    
    private let tableView = UITableView()
    
    private var examples: [Example] = []
    
    override func viewDidLoad() {
        super.viewDidLoad()
        bind()		// 바인딩 메소드를 스스로 호출
    }
    
    private func bind() {
    	viewModel.$examples
        	.receive(on: DispatchQueue.main)
            .sink { [weak self] examples in
            	self?.examples = examples
            	self?.tableView.reloadData()
            }
            .store(in: &cancellables)
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = examples[indexPath.row].text
        return cell
    }
}
  • MVP의 UI 갱신 코드
protocol ExampleProtocol: AnyObject {
	func showExamples(_ examples: [Example])
}

class ExamplePresenter: NSObject {
	private weak var viewController: ExampleProtocol?	// presenter가 controller 의존
    private let service: ExampleServiceProtocol
    
    init(viewController: ExampleProtocol, service: ExampleServiceProtocol) {
    	self.viewController = viewController
        self.service = service
    }
    
    func loadExamples() {
    	service.fetchExamples { [weak self] examples in
        	self?.viewController?.showExamples(examples)	// view에 접근하여 UI 업데이트 메소드 호출
        }
    }
}

class ExampleViewController: UIViewController, ExampleProtocol, UITableViewDataSource {
	private lazy var presenter = ExamplePresenter(viewController: self, service: ExampleService())
    private var examples: [Example] = []
    
    private let tableView = UITableView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.loadExamples()	// presenter를 통해 UI 업데이트에 필요한 동작 호출
    }
    
    // UI update 메소드
    func showExamples(_ examples: [Example]) {
    	self.examples = examples
        tableView.reloadData()
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = UITableViewCell()
        cell.textLabel?.text = examples[indexPath.row].text
        return cell
    }
}

viewDidLoad에서 호출하는 코드를 주목해보면, MVVM에서는 바인딩 메소드를 컨트롤러가 스스로 구현하고 호출하지만 MVP에서는 컨트롤러가 UI 업데이트 메소드를 구현은 하지만 호출은 Presenter를 통해 데이터 업데이트와 함께 이루어진다.

사실 MVP 아키텍처를 공부해보기로 마음먹고 처음 이 부분을 실습했을 때 viewController-presenter-viewController로 이어지는 호출 흐름이 다소 복잡하고 비효율적으로 느껴졌다. (솔직히 지금도 그 생각은 크게 바뀌지 않았다. 그래서 MVP보다 MVVM이 인기가 많은 게 아닐까?)

하지만 이렇게 되는 이유는 Presenter의 역할이 View이벤트를 받고, Model에서 데이터를 가져와 가공 후 다시 View전달하는 것이기 때문이다. 반면 ViewModel의 역할은 View가 필요한 데이터와 상태를 가공해서 제공하는 데에 있다. ViewModel이벤트를 받지 않고, ViewViewModel을 관찰하며 UI를 업데이트한다.

MVVM 아키텍처는 SwiftUI와 궁합이 좋다고 알려져있지만 UIKit과도 나쁘지 않게 작용한다. 하지만 MVP 아키텍처는 UIKit에만 최적화되어 있다. 데이터 변화에 따른 UI 갱신을 Presenter가 명시적으로 호출하기 때문에 명령형 UI 업데이트 패턴에 적합하고 선언적인 SwiftUI의 철학과는 맞지 않는 것이다.

💡 정리

  • UIKit + 이벤트 중심 → MVP가 구조 명확
  • SwiftUI + 상태 중심 → MVVM이 자연스러움
  • UIKit에서도 RxSwift나 Combine 같은 리액티브 툴을 쓰면 MVVM 구현이 쉬움
  • MVP는 UI 갱신 흐름을 명시적으로 제어해야 하고, MVVM은 상태 변화를 데이터 바인딩으로 자동 처리함
profile
iOS Junior Developer

0개의 댓글