SniffMEET. Interactor가 인프라를 직접 알아도 될까

Interactor에서 직접 매니저 호출하지 않기 매니저가 변하면 인터랙터를 직접 변경해야한다. 매니저와 인터랙터 간의 의존성을 줄이고, 인터랙터는 “비즈니스 로직”만, 그리고 비즈니스 로직의 구현체인 유즈케이스에서 네트워크 요청이나 데이터 관리와 같은 인프라 로직을 담당하는 것이 좋다고 생각한다. 또한 싱글톤 객체여도 현재 주입하는 방식으로 사용중인데, 인터랙터에서 사용하면 직접 매니저의 싱글톤 객체를 호출해서 사용해야 한다: func sendWalkRequest(message: String, latitude: Double, longtitude: Double, location: String) { Task { do { let myInfo = try loadUserInfoUseCase.execute() let id = try SessionManager.shared.userID.get() ... try await requestWalkUseCase.execute(walkNoti: walkNoti) } catch { ... } } presenter?.didSendWalkRequest() } 위 코드에서 id를 얻기 위해 SessionManager의 싱글톤 객체를 호출해야 한다. 싱글톤 객체 호출 자체는 크게 문제가 되지 않을 수 있지만, 다음과 같이 생각해볼 주제가 있다. ...

February 10, 2025

SniffMEET. NI·MPC 프로필 드랍 흐름 소유권 고민하기

들어가며 SniffMeet의 프로필 드랍 기능은 주변 사용자를 찾고, 기기 간 연결을 만들고, 거리와 방향 조건을 만족했을 때 프로필을 전달하는 흐름이다. 겉으로 보면 “주변 사람 찾기"와 “프로필 보내기"를 나누면 될 것 같았다. 하지만 실제 흐름을 펼쳐보니 MPC, NI, 유즈케이스, 인터랙터가 서로 맞물려 있었고, 단순히 기능 이름으로 유즈케이스를 나누기 어려웠다. 이 글은 프로필 드랍 흐름을 유즈케이스로 나누면서 고민했던 지점을 정리한 기록이다. 기존에 생각한 흐름 당시 프로필 드랍은 대략 다음 순서로 동작한다고 봤다. ...

January 14, 2025

SniffMEET. 여러 VIPER 구현에서 Router 구조 비교하기

VIPER에서 Router가 화면 전환을 담당한다는 원칙은 분명하다. 하지만 현재 화면을 어떻게 참조하는지, 다음 모듈은 누가 만드는지, AppRouter가 반드시 필요한지는 구현마다 달랐다. SniffMEET의 화면 구조를 정하기 전에 여러 VIPER 프로젝트를 비교하며 Router의 책임 범위를 확인했다. 무엇을 확인했나 분석 기준은 다음 세 가지였다. Router가 현재 ViewController를 어떻게 참조하는가 화면 전환 시 다음 VIPER 모듈을 누가 조립하는가 여러 모듈에 걸친 전환을 AppRouter가 담당하는가 구현 비교 구현 모듈 조립 화면 전환 AppRouter iOS-Viper-Architecture WireFrame의 정적 메서드 출발 View를 인자로 전달 없음 ios-architecture 정적 팩토리 메서드 전환 사례가 적음 없음 Viperit 프레임워크가 모듈을 관리 모듈이 직접 자신을 표시 AppModules로 모듈 관리 iOS-Architecture-Sample 모듈별 생성 앱 단위 Router가 화면 계층 관리 싱글톤 AppRouter 구현은 달랐지만 공통점도 있었다. View나 Presenter가 직접 다음 화면을 생성하지 않았고, 화면 조립과 전환을 UI 바깥의 객체로 분리하고 있었다. ...

November 12, 2024

Software Engineering. VIPER 아키텍처

VIPER란 VIPER는 화면을 View, Interactor, Presenter, Entity, Router로 나누는 아키텍처 패턴이다. 각 객체의 역할과 의존 방향을 제한해 UI, 비즈니스 로직, 화면 전환을 분리한다. 구성 요소 View 화면을 그리고 사용자 입력을 Presenter에 전달한다. Presenter가 전달한 값을 UI에 반영하며, 비즈니스 로직은 직접 처리하지 않는다. protocol ExampleViewProtocol: AnyObject { func displayMessage(_ message: String) func displayError(_ message: String) } final class ExampleViewController: UIViewController { var presenter: (any ExamplePresenterProtocol)? override func viewDidLoad() { super.viewDidLoad() presenter?.viewDidLoad() } @IBAction private func buttonTapped(_ sender: UIButton) { presenter?.handleButtonTapped() } } extension ExampleViewController: ExampleViewProtocol { func displayMessage(_ message: String) { label.text = message } func displayError(_ message: String) { errorLabel.text = message } } Presenter View의 입력을 해석해 Interactor나 Router에 작업을 요청한다. Interactor가 반환한 Entity는 화면에 표시할 값으로 가공해 View에 전달한다. ...

November 11, 2024

SniffMEET. UITabBarController VIPER 컨테이너로 다루기

VIPER 모듈을 사용하더라도, 직접 탭 바를 구현하지 않는 이상 UITabBarController는 사용해야 한다. 일반적인 VIPER 화면처럼 View, Presenter, Interactor, Router를 모두 갖춘 독립 모듈로 보기엔 애매한 것 같다. 특히 Interactor는 여기서 아무 의미가 없어보이고, Router는 UITabBarController에 내장되어 있으니까… 모듈 빌더 router, interactor를 생성하고 이를 가지고 있는 presenter를 생성해서 뷰에 집어넣는 방식 HomeModuleBuilder는 Home 화면에 필요한 VIPER 구성 요소를 한 곳에서 조립한다. ViewController를 만들고, Router와 Interactor를 생성한 뒤 Presenter에 주입한다. 마지막으로 NavigationFactory를 통해 화면을 UINavigationController로 감싸서 반환한다. ...

November 11, 2024