본문 바로가기
TIL(Today I Learned)

2025.02.05 Today I Learned

by 승환파크 2025. 2. 5.

Swift의 Input-Output 패턴

input-output 패턴은 ViewModel에서 사용자의 입력을 받아서 출력을 생성하는 방식으로 주로 Combine 이나 RxSwift를 활용한 비동기 데이터 스트림을 다루는데 사용한다.

Input-Output 패턴

  • Input: View 에서 발생하는 이벤트(사용자 입력, 버튼 클릭, 텍스트 입력 등)
  • Output: ViewModel에서 처리된 결과 값(네트워크 응답, 가공된 데이터 등)

View는 Input 을 ViewModel에 전달하고, ViewModel은 Output을 View에 전달하는 구조로 데이터의 흐름을 단방향으로 유지할 수 있어 유지보수가 용이하다는 장점을 가지고 있다.

 

기본적인 Input-Output 패턴 구조

Input-Output 패턴의 구조는 주로 ViewModel 에서 Input 과 Output을 정의해서 사용한다.

import RxSwift
import RxCocoa

protocol ViewModelType {
    associatedtype Input
    associatedtype Output
    
    func transform(input: Input) -> Output
}

 

위 처럼 Input 과 Output 을 정의하고, transform(input:) 메서드를 사용하여 변환하는 구조를 만든다.

 

ViewModel 구현

import RxSwift
import RxCocoa

class LoginViewModel: ViewModelType {
    struct Input {
        let email: Observerble<String>
        let password: Observerble<String>
        let loginTap: OBserverble<Void>
    }

	struct Output {
    	let isLoginEnabled: Observerble<Bool>
        let loginResult: Observerble<Bool>
    }

	private let disposeBag = DisposeBag()
    
    func transform(input: Input) -> Output {
    	let isLoginEnabled = Observerble.combineLatest(input.email, input.password)
            .map{ !$0.isEmpty && $1.count >= 6 }
        
        let loginResult = input.loginTap
            .flatMapLatest {
            	return Observerble.just(true)
            }
        
        return Output(isLoginEnabled: isLoginEnabled, loginResult: loginResult)
    }
}

 

View 에서 ViewModel 을 사용하는 방법

import UIKit
import RxSwift
import RxCocoa

class LoginViewController: UIViewController {
    private let viewModel = LoginViewModel()
    private let disposeBag = DisposeBag()

    private let emailTextField = UITextField()
    private let passwordTextField = UITextField()
    private let loginButton = UIButton()

    override func viewDidLoad() {
        super.viewDidLoad()

        let input = LoginViewModel.Input(
            email: emailTextField.rx.text.orEmpty.asObservable(),
            password: passwordTextField.rx.text.orEmpty.asObservable(),
            loginTap: loginButton.rx.tap.asObservable()
        )

        let output = viewModel.transform(input: input)

        output.isLoginEnabled
            .bind(to: loginButton.rx.isEnabled)
            .disposed(by: disposeBag)

        output.loginResult
            .subscribe(onNext: { isSuccess in
                print("로그인 결과: \(isSuccess)")
            })
            .disposed(by: disposeBag)
    }
}

 

Input-Output 패턴을 사용하는 이유

  1. 단방향 데이터 흐름 유지
  2. ViewModel 과 View의 결합도를 낮춰 유지보수 용이
  3. 테스트하기 쉬운 구조로 변환 가능
  4. 비동기 이벤트를 명확하게 다룰 수 있음

'TIL(Today I Learned)' 카테고리의 다른 글

2025.02.24 Today I Learned  (0) 2025.02.24
2025.02.14 Today I Learned  (0) 2025.02.14
2025.01.24 Today I Learned  (0) 2025.01.24
2025.01.16 Today I Learned  (1) 2025.01.16
2025.01.13 Today I Learned  (0) 2025.01.13