구조 다이어그램
View <—protocol—> Presenter | Model View는 Passive: - 시키는 대로만 UI 업데이트 - 로직 없음, 테스트 불필요 Presenter: - 모든 비즈니스 로직 소유 - View Protocol로만 통신 - 단독 테스트 가능
MVC와의 차이
  • Controller → Presenter로 로직 이동
  • View가 Passive — 직접 로직 없음
  • TaskListViewProtocol로 View 추상화
  • Mock View 주입으로 단독 테스트 가능
  • Strategy 패턴으로 정렬 교체 가능
내장 디자인 패턴
Observer Strategy

Observer — Presenter → View Protocol 통신. View가 Presenter를 관찰하는 구조.

Strategy — 정렬 전략(DateSort, PrioritySort)을 런타임에 교체.

 02-MVP/Start/TaskListPresenter.swift
import Foundation

// MARK: - MVP Presenter (Start)

// Strategy 패턴: 정렬 전략
protocol TaskSortStrategy {
    func sort(_ tasks: [TaskItem]) -> [TaskItem]
}

// TODO: 구체적인 정렬 전략을 구현하세요
// struct DateSortStrategy: TaskSortStrategy { ... }
// struct PrioritySortStrategy: TaskSortStrategy { ... }

final class TaskListPresenter {
    weak var view: TaskListViewProtocol?
    private var tasks: [TaskItem] = []
    private var sortStrategy: TaskSortStrategy

    init(sortStrategy: TaskSortStrategy) {
        self.sortStrategy = sortStrategy
    }

    func viewDidLoad() {
        // TODO: tasks를 로드하고 정렬 후 view에 전달
    }

    func didTapAdd() {
        // TODO: view?.showAddTaskDialog()
    }

    func didAddTask(title: String) {
        // TODO: 새 TaskItem 생성, 저장, 정렬, view 갱신
    }

    func didToggleTask(at index: Int) {
        // TODO: 완료 상태 토글 후 view 업데이트
    }

    func didDeleteTask(at index: Int) {
        // TODO: 삭제 후 view 업데이트
    }

    func didChangeSortStrategy(_ strategy: TaskSortStrategy) {
        // TODO: 전략 교체 후 재정렬
    }
}
 02-MVP/Finish/TaskListPresenter.swift
import Foundation

// MARK: - MVP Presenter (완성)

// ---- Strategy 패턴 ----

protocol TaskSortStrategy {
    func sort(_ tasks: [TaskItem]) -> [TaskItem]
}

struct DateSortStrategy: TaskSortStrategy {
    func sort(_ tasks: [TaskItem]) -> [TaskItem] {
        tasks.sorted { $0.createdAt > $1.createdAt }
    }
}

struct PrioritySortStrategy: TaskSortStrategy {
    func sort(_ tasks: [TaskItem]) -> [TaskItem] {
        tasks.sorted { $0.priority > $1.priority }
    }
}

struct CompletionSortStrategy: TaskSortStrategy {
    func sort(_ tasks: [TaskItem]) -> [TaskItem] {
        tasks.sorted { !$0.isCompleted && $1.isCompleted }
    }
}

// ---- Presenter ----

final class TaskListPresenter {
    weak var view: TaskListViewProtocol?
    private var tasks: [TaskItem] = []
    private var sortStrategy: TaskSortStrategy
    private let storage = TaskStorage.shared

    init(sortStrategy: TaskSortStrategy = DateSortStrategy()) {
        self.sortStrategy = sortStrategy
    }

    func viewDidLoad() {
        tasks = storage.loadAll()
        refreshView()
    }

    func didTapAdd() { view?.showAddTaskDialog() }

    func didAddTask(title: String) {
        guard !title.trimmingCharacters(in: .whitespaces).isEmpty else {
            view?.showError("Title cannot be empty"); return
        }
        let task = TaskItem(title: title)
        tasks.insert(task, at: 0)
        storage.saveAll(tasks)
        refreshView()
    }

    func didToggleTask(at index: Int) {
        guard index < tasks.count else { return }
        tasks[index].isCompleted.toggle()
        storage.saveAll(tasks)
        refreshView()
    }

    func didDeleteTask(at index: Int) {
        guard index < tasks.count else { return }
        tasks.remove(at: index)
        storage.saveAll(tasks)
        refreshView()
    }

    func didChangeSortStrategy(_ strategy: TaskSortStrategy) {
        self.sortStrategy = strategy
        refreshView()
    }

    private func refreshView() {
        view?.showTasks(sortStrategy.sort(tasks))
    }
}
핵심 개념
Passive View
View는 TaskListViewProtocol을 구현하고, Presenter의 지시에만 반응합니다. 직접 판단하지 않아 테스트가 필요 없을 만큼 단순해집니다.
Strategy 패턴
TaskSortStrategy protocol로 정렬 알고리즘을 캡슐화합니다. 런타임에 didChangeSortStrategy(_:)로 교체할 수 있어 개방-폐쇄 원칙(OCP)을 따릅니다.
Protocol 기반 분리
Presenter는 실제 View가 아닌 Protocol만 알고 있어, 테스트 시 Mock View를 주입할 수 있습니다. UIKit 없이 Presenter 로직만 단독으로 검증 가능합니다.
weak var view
Presenter가 View를 weak로 참조해 순환 참조를 방지합니다. View가 해제되면 자동으로 nil이 됩니다.