View를 "멍청하게(Passive)" 만들고 Presenter가 모든 로직을 담당합니다. Protocol로 View와 Presenter를 분리하여 Mock View를 끼울 수 있어 단독 테스트가 가능해집니다.
TaskListViewProtocol로 View 추상화
Observer — Presenter → View Protocol 통신. View가 Presenter를 관찰하는 구조.
Strategy — 정렬 전략(DateSort, PrioritySort)을 런타임에 교체.
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: 전략 교체 후 재정렬
}
}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))
}
}TaskListViewProtocol을 구현하고, Presenter의 지시에만 반응합니다. 직접 판단하지 않아 테스트가 필요 없을 만큼 단순해집니다.TaskSortStrategy protocol로 정렬 알고리즘을 캡슐화합니다. 런타임에 didChangeSortStrategy(_:)로 교체할 수 있어 개방-폐쇄 원칙(OCP)을 따릅니다.weak로 참조해 순환 참조를 방지합니다. View가 해제되면 자동으로 nil이 됩니다.