관리 메뉴

김종권의 iOS 앱 개발 알아가기

[iOS - swift] RxSwift - 결합 연산자 (withLatestFrom vs CombineLatest, merge vs zip vs concat) 본문

RxSwift/RxSwift 기본

[iOS - swift] RxSwift - 결합 연산자 (withLatestFrom vs CombineLatest, merge vs zip vs concat)

jake-kim 2022. 1. 27. 03:35

예제 코드에서 사용한 프레임워크

  • 코드로 UI 작성에서 편리를 위해 사용
pod 'SnapKit'
pod 'Then'
  • Rx 프레임워크
pod 'RxSwift'
pod 'RxCocoa'

WithLatestFrom vs CombineLatest

withLatest와 combineLatest를 이용한 UI

  • 공통점
    • 결합하는 요소들의 타입들이 달라도 사용 가능
  • 차이점
    • a.withLatestFrom(b): a이벤트가 발생했을 때만 b 이벤트와 같이 방출
    • combineLatest(a, b): a나 b이벤트 둘 중 하나 발생했을 때 방출
    • 주의사항: combineLatest(a, b)는 바로 방출
  • 사용처
    • withLatestFrom: 버튼을 탭한 경우, 입력했던 이메일 값을 서버에 전송하여 유효성 검정 실시
    • combineLatest: 이메일과 패스워드 입력할 때마다 카운트하여, 로그인 버튼 활성화

* withLatestFrom, combineLatestFrom 전체 코드

import UIKit
import RxSwift
import RxCocoa
import Then
import SnapKit

final class ViewController: UIViewController {

  private let emailTextField = UITextField().then {
    $0.borderStyle = .roundedRect
    $0.placeholder = "abc@abc.com"
  }
  private let passwordTextField = UITextField().then {
    $0.borderStyle = .roundedRect
    $0.isSecureTextEntry = true
  }
  private let confirmButton = UIButton().then {
    $0.setTitleColor(.systemBlue, for: .normal)
    $0.setTitleColor(.blue, for: .highlighted)
    $0.setTitleColor(.gray, for: .disabled)
    $0.setTitle("로그인", for: .normal)
    $0.isEnabled = false
  }
  private let withLatestFromLabel = UILabel().then {
    $0.textColor = .black
    $0.numberOfLines = 0
  }
  private let combineLatestLabel = UILabel().then {
    $0.textColor = .black
    $0.numberOfLines = 0
  }
  
  private let disposeBag = DisposeBag()
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    [
      self.confirmButton,
      self.emailTextField,
      self.passwordTextField,
      self.withLatestFromLabel,
      self.combineLatestLabel
    ]
      .forEach(self.view.addSubview(_:))
    
    self.emailTextField.snp.makeConstraints {
      $0.left.top.equalTo(self.view.safeAreaLayoutGuide).inset(56)
      $0.width.equalTo(230)
    }
    self.passwordTextField.snp.makeConstraints {
      $0.left.equalTo(self.emailTextField)
      $0.top.equalTo(self.emailTextField.snp.bottom).offset(4)
      $0.width.equalTo(230)
    }
    self.confirmButton.snp.makeConstraints {
      $0.left.equalTo(self.emailTextField.snp.right).offset(8)
      $0.centerY.equalTo(self.emailTextField)
    }
    self.withLatestFromLabel.snp.makeConstraints {
      $0.top.equalTo(self.confirmButton.snp.bottom).offset(56)
      $0.left.right.equalToSuperview()
    }
    self.combineLatestLabel.snp.makeConstraints {
      $0.top.equalTo(self.withLatestFromLabel.snp.bottom).offset(24)
      $0.left.right.equalToSuperview()
    }
    
    self.confirmButton.rx.tap
      .withLatestFrom(self.emailTextField.rx.text)
      .bind { [weak self] in self?.withLatestFromLabel.text = "<withLatestFrom> 버튼 탭 -> request: \($0)" }
      .disposed(by: self.disposeBag)
    
    Observable
      .combineLatest(
        self.emailTextField.rx.text,
        self.passwordTextField.rx.text
      )
      .map { email, password -> Bool in
        let email = email ?? ""
        let password = password ?? ""
        return email.count > 2 && password.count > 2
      }
      .bind(to: self.confirmButton.rx.isEnabled)
      .disposed(by: self.disposeBag)
  
  }
}

zip vs merge vs concat

  • 공통점
    • 결합 기능
  • 차이점
    • Observable.merge(a, b, c): a, b, c 모두 같은 타입이어야 가능, 방출은 하나씩 $0
      let a = Observable.of(1, 2, 3)
      let b = Observable.of("1", "2", "3")
      let c = Observable.of(4, 5, 6)
      
      Observable
        .merge(a, c)
        .bind { print($0) }
        .disposed(by: self.disposeBag)​
    • zip: 다른 타입이라도 가능, 방출은 들어간 만큼  $0, $1, $2 
      • withLatestFrom, combineLatest와 동일하게 방출은 들어간 만큼 $0, $1, $2
        Observable
          .zip(a, b, c)
          .bind { print($0, $1, $2) }
          .disposed(by: self.disposeBag)​
    • concat: merge와 유사하게 모두 같은 타입이고 방출은 하나씩 $0, 단 순서 보장
      Observable
        .concat(a, c)
        .bind { print($0) }
        .disposed(by: self.disposeBag)​
  • 사용처
    • merge: (단순 결합) 여러개의 이벤트들을 순서에 상관없이 모두 처리
    • zip: (모두 기다림) 여러개의 api를 request하고 모두 response를 받은 경우 다음 처리를 할 때
    • concat: (순서 보장) 방출 순서 보장
Comments