VIPER 아키텍처 - Team-HGD/SniffMEET GitHub Wiki
개요
View, Interactor, Presenter, Entity, Router의 구성 요소로 이루어진 아키텍쳐 패턴
장점
- 모듈 간의 명확한 역할 분리
- 테스트 용이성이 높다.
단점
- 초기 설계 및 구현 시 코드가 많아지며 복잡해진다.
- 간단한 화면에 VIPER를 적용할 경우 오히려 과도한 설계가 된다.
구조
View
사용자 인터페이스 요소, Presenter와만 통신한다.
protocol ExampleViewProtocol: AnyObject {
func displayData(_ data: String)
}
class ExampleViewController: UIViewController {
var presenter: ExamplePresenterProtocol!
override func viewDidLoad() {
super.viewDidLoad()
presenter.viewDidLoad()
}
// 버튼 액션 메서드
@IBAction func buttonTapped(_ sender: UIButton) {
// 버튼 클릭 이벤트를 Presenter에 전달
presenter.handleButtonTapped()
}
}
// ExampleViewProtocol을 구현한 익스텐션
extension ExampleViewController: ExampleViewProtocol {
func displayData(_ data: String) {
// Presenter로부터 데이터를 받아서 UI 업데이트
label.text = data
}
}
Presenter
View와 Interactor 사이의 데이터 흐름을 관리하는 컴포넌트, 비즈니스 로직을 처리하지 않는다.
역할
- UI업데이트 요청: Interactor로부터 받은 데이터를 View에 전달하여 UI를 업데이트 한다.
- 사용자 이벤트 처리: View에서 발생한 이벤트를 Interactor에 보내서 처리하도록 한다.
// Presenter의 역할을 담당하는 프로토콜
protocol ExamplePresenterProtocol: AnyObject {
func viewDidLoad() // View가 로드되었을 때 호출되는 메서드
func handleButtonTapped() // 버튼 클릭 이벤트 처리
}
class ExamplePresenter: ExamplePresenterProtocol {
weak var view: ExampleViewProtocol? // View에 대한 약한 참조
var interactor: ExampleInteractorProtocol // 비즈니스 로직 처리
var router: ExampleRouterProtocol // 화면 전환 관리
// 의존성 주입 (Presenter는 View, Interactor, Router를 의존성으로 가짐)
init(view: ExampleViewProtocol, interactor: ExampleInteractorProtocol, router: ExampleRouterProtocol) {
self.view = view
self.interactor = interactor
self.router = router
}
// View가 로드되었을 때 호출되는 메서드
func viewDidLoad() {
// Interactor에 데이터 요청 (비즈니스 로직은 Interactor에서 처리)
interactor.fetchData()
}
// 버튼 클릭 이벤트 처리
func handleButtonTapped() {
// 버튼 클릭 시 Interactor에게 작업을 요청
interactor.performAction()
}
}
// Interactor에서 데이터를 성공적으로 받아왔을 때 호출되는 메서드
extension ExamplePresenter: ExampleInteractorOutputProtocol {
func dataFetchedSuccessfully(data: String) {
// 데이터를 View에 전달하여 UI를 업데이트
view?.displayData(data)
}
// 데이터 로드 실패 시 호출되는 메서드
func dataFetchFailed(withError error: Error) {
// 에러 메시지를 View에 전달
view?.displayError(error.localizedDescription)
}
}
특징
- Interactor, View, Router를 의존성으로 가지고 있다.
- Presenter는 View를
weak
참조한다(순환 참조 방지). - View에서 발생한 사용자 액션에 대응하는 메소드를 통해 액션을 Interactor에 전달한다.
Interactor
비즈니스 로직을 담당하는 컴포넌트, 데이터와 관련된 작업을 수행한다.
역할
- 비즈니스 로직 처리
- 데이터 요청: 데이터 베이스나 네트워크에서 필요한 데이터를 요청하거나 저장함
- 결과 전달: Presenter에게 데이터 처리 결과를 전달하여 UI를 업데이트 할 수 있도록 한다.
protocol ExampleInteractorProtocol {
func fetchData() // 데이터를 가져오는 메서드
func performAction() // 특정 작업을 수행하는 메서드
}
protocol ExampleInteractorOutputProtocol: AnyObject {
func dataFetchedSuccessfully(data: String) // 데이터 로드 성공 시 호출
func dataFetchFailed(withError error: Error) // 데이터 로드 실패 시 호출
}
class ExampleInteractor: ExampleInteractorProtocol {
weak var presenter: ExampleInteractorOutputProtocol? // Presenter와의 약한 연결
// 데이터를 가져오는 메서드
func fetchData() {
// 데이터 로직 처리 (예: 네트워크 요청 또는 데이터베이스 접근)
// 성공 시:
presenter?.dataFetchedSuccessfully(data: "데이터가 성공적으로 로드되었습니다.")
// 실패 시:
// presenter?.dataFetchFailed(withError: someError)
}
// 특정 작업을 수행하는 메서드
func performAction() {
// 비즈니스 로직 처리
// 결과에 따라 Presenter에 알림
presenter?.dataFetchedSuccessfully(data: "작업이 성공적으로 완료되었습니다.")
}
}
특징
- Presenter를
weak
참조한다. - 데이터를 처리하고, 그 처리 결과를 Presenter의 메소드를 호출하여 Presenter에 전달
Entity
데이터 모델을 정의하는 부분, 앱의 주요 데이터 구조를 나타낸다.
역할
- Interactor가 비즈니스 로직을 처리할 때 필요한 데이터 구조나 모델을 정의함
- 비즈니스 로직의 데이터 구조만 정의하며, 로직을 처리하지 않음
// 예시 사용자(Entity) 모델 정의
struct User: Codable {
let id: Int
let name: String
let email: String
let age: Int
}
// 예시 제품(Entity) 모델 정의
struct Product: Codable {
let id: Int
let name: String
let price: Double
let stock: Int
}
설계 기준
- Simple and Focused: 데이터만 표현하고 로직을 포함하지 않음
데이터 흐름
- Interactor는 네트워크 요청을 통해 받아온 데이터를 Entity 구조로 변경시킨다.
- Presenter는 Entity 데이터를 View에 맞는 형식으로 가공해서 전달한다.
Router
화면 전환이나 모듈 간의 이동을 처리한다.
역할
- 특정화면에서 다른 화면으로 이동하거나 팝업을 띄우는 등의 작업을 수행한다.
- 화면을 이동할 때, 필요한 데이터를 다음 화면으로 전달하는 작업을 수행한다.
- 각 모듈을 초기화하고 필요한 의존성을 주입하여 각 컴포넌트를 연결한다.
protocol ExampleRouterProtocol {
func navigateToDetail(from view: ExampleViewProtocol, with data: String) // 상세 화면으로 이동
}
class ExampleRouter: ExampleRouterProtocol {
// Detail 화면으로의 네비게이션 메서드 구현
func navigateToDetail(from view: ExampleViewProtocol, with data: String) {
// 현재 View를 UIViewController로 변환
guard let viewController = view as? UIViewController else { return }
// DetailViewController 인스턴스 생성 및 데이터 설정
let detailViewController = DetailViewController()
detailViewController.data = data
// 현재 ViewController에서 DetailViewController로 푸시 네비게이션
viewController.navigationController?.pushViewController(detailViewController, animated: true)
}
// 모듈 초기화 메서드 - VIPER 각 구성 요소 설정 및 연결
static func createModule() -> UIViewController {
let view = ExampleViewController()
let interactor = ExampleInteractor()
let router = ExampleRouter()
let presenter = ExamplePresenter(view: view, interactor: interactor, router: router)
view.presenter = presenter
interactor.presenter = presenter
return view
}
}
동작 흐름
- Presenter에서 화면 전환 요청이 발생하면, Router의 화면 이동 메서드 호출
- Router는 ViewController를 참조하여 내비게이션 작업 수행, 필요한 데이터를 다음 화면에 전달
- 모듈을 생성하여, 독립적인 화면 초기화
의존성 주입