Notice
Recent Posts
Recent Comments
Link
관리 메뉴

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

[iOS - swift] 1. ReactorKit 샘플 앱 - RxDataSources 사용 방법 본문

iOS framework

[iOS - swift] 1. ReactorKit 샘플 앱 - RxDataSources 사용 방법

jake-kim 2021. 12. 10. 23:41

1. ReactorKit 샘플 앱 - RxDataSources 사용 방법

2. ReactorKit 샘플 앱 - RxDataSources을 이용한 Section, Item 모델 구현 패턴 (with 동적 사이즈 셀)

기초

ReactorKit + RxDataSources 사용 방향

  • RxDataSources를 사용하면 extension으로 따로 tableView의 cellForRowAt 메소드를 따로 빼지 않아도 간결한 코드 작성이 가능
  • Cell에 사용될 상태 관리 전용 Reactor 생성
  • Cell에 UI를 업데이트 할 때 이 Reactor 인스턴스를 받아서 사용
    • 정의한 Cell 클래스에 bind(reactor:) 함수를 정의하여, 이곳에 데이터들을 넘기도록 설계

Cell 전용 Reactor를 두는 이유

  • Cell 전용 Reactor를 두고 사용할 때 아래와 같이 cell.reactor로 reactor를 바로 대입하여 사용 가능
    var dataSource = RxTableViewSectionedReloadDataSource<MainSection> { dataSource, tableView, indexPath, reactor in
      let cell = tableView.dequeueReusableCell(for: indexPath) as MainTableViewCell
      cell.reactor = reactor
      return cell
    }
  • Cell 전용 Reactor가 없다면 별도의 bind(model:) 메소드를 번거롭게 Cell쪽에 만들어야 하는 단점 존재
    var dataSource = RxTableViewSectionedReloadDataSource<MainSection> { dataSource, tableView, indexPath, reactor in
      let cell = tableView.dequeueReusableCell(for: indexPath) as MainTableViewCell
      let item = dataSource[indexPath]
      cell.bind(model: item)
      return cell
    }​

하나의 TableView에 다수의 Cell들을 처리 과정

하나의 TableView에 2개의 Cell 존재

1. Cell에서 사용할 Model 정의

 

2. ViewCell과 ViewCellReactor 정의

  • ViewCell은 데이터가 표출될 UI 요소
  • ViewCellReactor는 Model을 state값으로 가지고 있는 컴포넌트

3. ViewSection 정의

  • RxDataSources에서 사용될 모델이고, Section별, Item별로 데이터 구조화

1. Cell에서 사용할 Model 정의

  • 정의할 Cell들이 사용할 때 공통으로 갖을 protocol 정의
    • Then - SwiftUI와 유사하게 클로저를 통해 편리하게 접근 가능
    • Codable - decode & encode
    • Equatable - RxDataSources의 모델에 들어가면 필수로 준수해야하는 프로토콜
      // ModelType.swift
      
      import Then
      
      protocol ModelType: Then, Codable, Equatable { }​
  • Cell에 들어갈 Model 정의 (2가지 셀을 사용할것이므로, 2개의 모델 정의 Main, Sub)
    • ModelType을 준수하고, `==` 연산자 구현
      // Main.swift
      
      import RxDataSources
      
      struct Main: ModelType {
        var message: String
        var isDone = false
      }
      
      extension Main: Equatable {
        
        static func == (lhs: Main, rhs: Main) -> Bool {
          lhs.message == rhs.message
        }
      }​
    • Sub 모델도 동일하게 구현
      // Sub.swift
      
      import UIKit
      
      struct Sub: ModelType {
        var message: String
        var isDone = false
      }
      
      extension Sub: Equatable {
        
        static func == (lhs: Sub, rhs: Sub) -> Bool {
          lhs.message == rhs.message
        }
      }​

2. ViewCell과 ViewCellReactor 정의

  • ViewCellReactor 정의
    • state타입에 사용할 모델 선언 let initialState: {사용할 모델 타입}
      // MainTableViewCellReactor.swift
      
      import ReactorKit
      
      class MainTableViewCellReactor: Reactor {
        typealias Action = NoAction
        
        let initialState: Main
        
        init(main: Main) {
          self.initialState = main
        }
      }​
  • ViewCell 정의
    • UITableViewCel, View, Reusable을 준수
    • bind(reactor:) 메소드 구현
      // MainTableViewCell.swift
      
      import UIKit
      import Reusable
      import SnapKit
      import ReactorKit
      
      class MainTableViewCell: UITableViewCell, View, Reusable {
        ...
        
        // MARK: Binding
        func bind(reactor: MainTableViewCellReactor) {
          titleLabel.text = "(메인) " + reactor.currentState.message
        }
      }​
  • SubTableViewCellReactor, SubTableViewCell도 동일하게 구현

3. ViewSection 정의

  • SomeViewSection은 RxDataSources의 모델에 사용
  • Section과 Item에서 사용할때는 enum으로 선언하고, enum으로 구분 후 associative type으로 모델을 선언
    • SomeViewSection - Section에 사용될 타입
    • SomeViewSectionItem - Item에 사용될 타입
      // SomeViewSection.swift
      
      import RxDataSources
      
      enum SomeViewSection {
        case first([SomeViewSectionItem])
      }​
      
      enum SomeViewSectionItem {
        case main(MainTableViewCellReactor)
        case sub(SubTableViewCellReactor)
      }
  • RxDataSources의 모델로 사용을 위해서, SectionModelType이라는 프로토콜을 준수
    extension SomeViewSection: SectionModelType {
      var items: [SomeViewSectionItem] {
        switch self {
        case .first(let items): return items
        }
      }
      
      init(original: SomeViewSection, items: [SomeViewSectionItem]) {
        switch original {
        case .first: self = .first(items)
        }
      }
    }​
  • 전체 SomeViewSection 코드
    enum SomeViewSection {
      case first([SomeViewSectionItem])
    }
    
    enum SomeViewSectionItem {
      case main(MainTableViewCellReactor)
      case sub(SubTableViewCellReactor)
    }
    
    extension SomeViewSection: SectionModelType {
      var items: [SomeViewSectionItem] {
        switch self {
        case .first(let items): return items
        }
      }
      
      init(original: SomeViewSection, items: [SomeViewSectionItem]) {
        switch original {
        case .first: self = .first(items)
        }
      }
    }​

RxDataSources 사용

  • 핵심은 ViewController에서 RxTableViewSectinoedReloadDataSource<SomeVIewSection>으로 사용
    // MainViewController.swift
    
    import ReactorKit
    import RxDataSources
    import Reusable
    
    final class MainViewController: UIViewController, View {
    
    ...
    
      var dataSource = RxTableViewSectionedReloadDataSource<SomeViewSection> { dataSource, tableView, indexPath, sectionItem in
        switch sectionItem {
        case .main(let reactor):
          let cell = tableView.dequeueReusableCell(for: indexPath) as MainTableViewCell
          cell.reactor = reactor
          return cell
        case .sub(let reactor):
          let cell = tableView.dequeueReusableCell(for: indexPath) as SubTableViewCell
          cell.reactor = reactor
          return cell
        }
      }
    
    ...
    
    }

* 전체 소스 코드: https://github.com/JK0369/RxReactorKit-RxDataSources

Comments