[swift] PromiseKit와 함께 사용되는 디자인 패턴 소개 (ex. MVVM, VIPER 등)

안녕하세요! 이번에는 Swift 언어에서 PromiseKit 라이브러리를 사용하면서 유용하게 적용할 수 있는 디자인 패턴들에 대해 알아보겠습니다. PromiseKit는 비동기 작업을 처리하는데 도움이 되는 라이브러리로, 코드를 깔끔하게 구성하고 유지하기 위해 다양한 디자인 패턴들과 함께 사용될 수 있습니다.

1. MVVM (Model-View-ViewModel)

MVVM은 애플리케이션의 코드를 보다 재사용 가능하고 테스트하기 쉬운 구조로 만들기 위한 디자인 패턴입니다. PromiseKit와 함께 MVVM 패턴을 사용하면 비동기 작업의 결과를 ViewModel에서 처리하고, View는 ViewModel에 바인딩하여 UI를 업데이트할 수 있습니다.

PromiseKit의 .then 메서드를 사용하여 비동기 작업의 결과를 ViewModel에 전달하고, ViewModel은 해당 결과를 가공하여 View에 반영하는 역할을 수행합니다. 이 방식으로 코드를 구성하면 UI와 비즈니스 로직이 분리되어 개발과 유지보수가 훨씬 용이해집니다.

func fetchUser() -> Promise<User> {
    return Promise { resolver in
        APIClient.shared.getUser { result in
            switch result {
            case .success(let user):
                resolver.fulfill(user)
            case .failure(let error):
                resolver.reject(error)
            }
        }
    }
}

// ViewModel
class UserViewModel {
    private var user: User?
    
    func fetchUser() {
        fetchUser()
            .done { user in
                self.user = user
            }
            .catch { error in
                print(error.localizedDescription)
            }
    }
    
    // UI에 바인딩되는 속성들
    var userName: String? {
        return user?.name
    }
}

2. VIPER (View, Interactor, Presenter, Entity, Router)

VIPER는 애플리케이션을 각각의 역할에 따라 컴포넌트로 분리하여 개발하는 디자인 패턴입니다. PromiseKit와 함께 사용하면 비동기 작업을 각 컴포넌트에 분산시켜 코드를 간결하게 유지할 수 있습니다.

각각의 컴포넌트는 독립적인 역할과 책임을 가지며, 비동기 작업부터 UI 업데이트까지의 흐름이 한눈에 들어옵니다. PromiseKit에서 제공하는 .map이나 .done 메서드를 활용하면 각 컴포넌트에서 동작을 처리하고 다음 컴포넌트로 결과를 전달할 수 있습니다.

// Interactor
class UserInteractor {
    func fetchUser() -> Promise<User> {
        return Promise { resolver in
            APIClient.shared.getUser { result in
                switch result {
                case .success(let user):
                    resolver.fulfill(user)
                case .failure(let error):
                    resolver.reject(error)
                }
            }
        }
    }
}

// Presenter
class UserPresenter {
    private var user: User?
    
    func fetchUser() {
        interactor.fetchUser()
            .done { user in
                self.user = user
                self.view?.updateUI(with: user)
            }
            .catch { error in
                self.view?.showError(error)
            }
    }
}

// View
class UserViewController: UIViewController {
    private var presenter: UserPresenter!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        presenter.fetchUser()
    }
    
    func updateUI(with user: User) {
        // UI 업데이트 로직
    }
    
    func showError(_ error: Error) {
        // 에러 처리 로직
    }
}

참고 자료