티스토리 뷰

제가 지금 사용하는 방법이고, 패턴에 대해서 적용할때 괜찮은 방법인것 같아서 공유 합니다.


저의 이야기를 먼저 하자면 IBAction에서 들어오는 값들을 viewModel에 일반 객체의 형태로 넣어주고 이것을 ViewModel에서 Variable을 이용하여 값의 변화를 시켜서 사용하고 있었습니다.


그런게 어느 순간부터 이것이 과연 Rx를 잘 이용하고 있는것인가에 대한 궁금함이 있었고요.

이것저것 뒤져보다가 사용하면 좋을것 같은 방법이라 공유합니다.


지금은 Reactive 클래스에서 만들어지는 Observable만을 이용하여서 입력을 만들고 그 입력을 통하여 데이터를 조작하고 난 이후의 데이터들을 bind혹은 drive 시키는 형태의 구조로 개발을 하고 있습니다.


1
2
3
4
protocol ViewModelType {
    associatedtype Input
    associatedtype Output
}
cs


위와 같이 ViewModel에서 사용할 Input, Output를 protocol로 만들어줍니다.


두가지 정도의 방법을 소개하려고 합니다.

꼭 이렇게 쓰라는 이야기는 아닙니다.


1. transform을 이용하여 입력을 넣어주고 출력을 받는다.


1
2
3
4
5
6
protocol ViewModelType {
    associatedtype Input
    associatedtype Output
 
    func transform(input: Input) -> Output
}
cs


이렇게 하여주고, ViewModel에 적용을 하여 주게 되면 자신이 정한 class type에 대한 In, Out을 만들어줄수 있게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
final class ViewModel: ViewModelType {
 
    struct Input {
        let click: ControlEvent<Void>
    }
 
    struct Output {
        let text: Driver<String>
    }
 
    func transform(input: Input) -> Output {
        let text = input.click
            .map { _ in return "Hello world!" }
            .asDriver(onErrorJustReturn: "")
 
        return Output(text: text)
    }
}
cs


간단히 설명을 하자면 ViewController에서 발생한 click event를 전달받게 되면 map을 이용하여 output에 넣어줄 데이터를 만들고 이것을 return 하여 주게 됩니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final class ViewController: UIViewController {
  
    @IBOutlet weak var actionButton: UIButton!
    @IBOutlet weak var printLabel: UILabel!
 
    private let viewModel = ViewModel()
    private let disposeBag = DisposeBag()
  
    override func viewDidLoad() {
        super.viewDidLoad()
 
        bindViewModel()
    }
  
    private func bindViewModel() {
        let input = ViewModel.Input(click: actionButton.rx.tap)
 
        let output = viewModel.transform(input: input)
 
        output.text
            .drive(printLabel.rx.text)
            .disposed(by: disposeBag)
    }
}
 
cs


그렇다면 위처럼 만들면 입출력이 구분이 되어서, ViewController는 단순히 View의 역할만 하게 됩니다.

이해가 되셨나요?


2. ViewModel 내부에 Input, Output을 만들고 이것을 변수로 노출하여 줍니다.


1
2
3
4
5
6
7
protocol ViewModelType {
    associatedtype Input
    associatedtype Output
 
    var input: Input { get }
    var output: Output { get }
}
cs


1번의 예와는 다르게 input, output을 밖으로 노출하여 줍니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
struct MainViewModel: ViewModelType {
    
    struct Input {
        let inText: AnyObserver<String>
    }
    
    struct Output {
        let text: Driver<String>
    }
    
    let input: Input
    let output: Output
    private let data = ReplaySubject<String>.create(bufferSize: 1)
    
    init() {
        let text = data
            .map { data in return "\(data): Hello world!" }
            .asDriver(onErrorJustReturn: "")
        
        output = Output(text: text)
        input = Input(inText: data.asObserver())
    }
}
cs


ViewController에서는 이렇게 사용하면 되겠죠.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
final class ViewController: UIViewController {
  
    @IBOutlet weak var actionButton: UIButton!
    @IBOutlet weak var printLabel: UILabel!
 
    private let viewModel = ViewModel()
    private let disposeBag = DisposeBag()
  
    override func viewDidLoad() {
        super.viewDidLoad()
 
        bindViewModel()
    }
  
    private func bindViewModel() {
        viewModel.input.inText.onNext("DH")
 
        viewModel.output.text
            .drive(printLabel.rx.text)
            .disposed(by: disposeBag)
    }
}
 
cs



댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/03   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
글 보관함