[swift] RxSwift를 사용한 퀴즈 애플리케이션 구현 방법

이번 글에서는 RxSwift를 사용하여 퀴즈 애플리케이션을 구현하는 방법에 대해 알아보겠습니다. RxSwift는 함수형 반응형 프로그래밍 패러다임을 제공하는 라이브러리로, 애플리케이션의 비동기적인 이벤트를 간결하게 처리하고 관찰하는데 유용합니다.

1. 프로젝트 설정

먼저, 프로젝트를 생성하고 RxSwift를 프로젝트에 추가해야합니다. 프로젝트를 생성한 후, Podfile에 다음과 같은 내용을 추가합니다:

pod 'RxSwift'
pod 'RxCocoa'

터미널에서 pod install 명령어를 실행하여 RxSwift와 RxCocoa를 설치합니다.

2. 모델 클래스 생성

퀴즈 애플리케이션에서 사용할 모델 클래스를 생성합니다. 예를 들어, 다음과 같은 Question 클래스를 작성합니다:

import Foundation

struct Question {
  let question: String
  let options: [String]
  let answer: Int
}

3. 데이터 소스 구성

애플리케이션의 퀴즈 데이터는 퀴즈 질문과 정답을 포함하는 배열로 구성됩니다. 이를 위해, 데이터를 관리할 QuizDataSource 클래스를 구현합니다:

import Foundation
import RxSwift

class QuizDataSource {
  private let questions = [
    Question(question: "질문 1", options: ["답안 1", "답안 2", "답안 3"], answer: 1),
    Question(question: "질문 2", options: ["답안 1", "답안 2", "답안 3"], answer: 2),
    Question(question: "질문 3", options: ["답안 1", "답안 2", "답안 3"], answer: 3)
  ]
  
  func getRandomQuestion() -> Single<Question> {
    let randomIndex = Int.random(in: 0..<questions.count)
    let question = questions[randomIndex]
    return Single.just(question)
  }
}

4. View 모델 구성

화면의 로직과 데이터 바인딩을 처리하기 위해 QuizViewModel 클래스를 구현합니다. 이 클래스는 QuizDataSource와 연결하여 랜덤한 퀴즈 질문을 가져오고, 사용자의 정답 선택을 처리할 수 있습니다.

import Foundation
import RxSwift

class QuizViewModel {
  private let dataSource = QuizDataSource()
  private let disposeBag = DisposeBag()
  
  private let _question = BehaviorSubject<Question?>(value: nil)
  var question: Observable<Question?> {
    return _question.asObservable()
  }
  
  func getRandomQuestion() {
    dataSource.getRandomQuestion().subscribe(onSuccess: { [weak self] question in
      self?._question.onNext(question)
    }).disposed(by: disposeBag)
  }
  
  func checkAnswer(selectedOptionIndex: Int) -> Bool {
    guard let question = try? _question.value() else {
      return false
    }
    
    let isCorrect = selectedOptionIndex + 1 == question.answer
    return isCorrect
  }
}

5. View와의 데이터 바인딩

마지막으로, 퀴즈 앱의 화면을 구현하고, QuizViewModel과 데이터 바인딩을 설정합니다. 예를 들어, 다음과 같이 QuizViewController 클래스를 작성합니다:

import UIKit
import RxSwift
import RxCocoa

class QuizViewController: UIViewController {
  private let viewModel = QuizViewModel()
  private let disposeBag = DisposeBag()
  
  @IBOutlet weak var questionLabel: UILabel!
  @IBOutlet weak var optionButtons: [UIButton]!
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    bindViewModel()
    viewModel.getRandomQuestion()
  }
  
  private func bindViewModel() {
    viewModel.question.subscribe(onNext: { [weak self] question in
      self?.questionLabel.text = question?.question
      self?.optionButtons.enumerated().forEach({ (index, button) in
        if let options = question?.options {
          button.setTitle(options[index], for: .normal)
        }
      })
    }).disposed(by: disposeBag)
    
    optionButtons.forEach { button in
      button.rx.tap
        .subscribe(onNext: { [weak self] in
          guard let selectedOptionIndex = self?.optionButtons.firstIndex(of: button) else {
            return
          }
          
          let isCorrect = self?.viewModel.checkAnswer(selectedOptionIndex: selectedOptionIndex) ?? false
          // 정답 여부에 따른 처리를 수행합니다.
        }).disposed(by: disposeBag)
    }
  }
}

이제 위 코드를 사용하여 퀴즈 애플리케이션을 구현할 수 있습니다. RxSwift를 사용하면 비동기적인 이벤트 처리와 데이터 바인딩을 간결하게 관리할 수 있으며, 애플리케이션의 유지보수성과 확장성을 향상시킬 수 있습니다.

참고 자료