Notice
Recent Posts
Recent Comments
Link
ยซ   2024/05   ยป
์ผ ์›” ํ™” ์ˆ˜ ๋ชฉ ๊ธˆ ํ† 
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
Archives
Today
Total
๊ด€๋ฆฌ ๋ฉ”๋‰ด

lgvv98

[ReactorKit] ReactorKit ๊ณต๋ถ€ํ•˜๊ธฐ #5 RxTodo ๋”ฐ๋ผ์žก๊ธฐ (3) ๋ณธ๋ฌธ

apple/๐Ÿฆ• UIKit & ReactiveX

[ReactorKit] ReactorKit ๊ณต๋ถ€ํ•˜๊ธฐ #5 RxTodo ๋”ฐ๋ผ์žก๊ธฐ (3)

๐Ÿฅ• ์บ๋Ÿฟ๋งจ 2022. 9. 12. 18:38

ReactorKit ๊ณต๋ถ€ํ•˜๊ธฐ #5 RxTodo ๋”ฐ๋ผ์žก๊ธฐ (3)

 

โœ… ๋ชฉํ‘œ: Service ๋„์ž…์„ ์œ„ํ•ด RxTodo ์ฝ”๋“œ ๋ถ„์„ํ•˜๊ธฐ

 

 

 โœ… ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ ์„œ๋น„์Šค ๋„์ž…ํ•œ ๊ฒฐ๊ณผ ์ฝ”๋“œ

Service ๋„์ž…

 

๐Ÿšจ Realm์—์„œ tableView.rx.itemMoved์‹œ, ๋ ˆ์ฝ”๋“œ ์ˆœ์„œ ์–ด๋–ป๊ฒŒ ๋ฐ”๊ฟ”์•ผํ• ์ง€ ๋ชจ๋ฅด๊ฒ ์Œ.

Realm์— ๋Œ€ํ•œ ์ดํ•ด๊ฐ€ ๋ถ€์กฑํ•ด์„œ ์•ฑ์ด crash ๋‚˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์€๋ฐ, ์ด๋ฅผ ํ•™์Šตํ•ด์„œ ๋ณด์™„ํ•ด์•ผํ•จ.

Realm์— Token์„ ์‚ฌ์šฉํ•ด์„œ ๋ณ€๊ฒฝ๋œ ๋ฐ์ดํ„ฐ์˜ ์‹ฑํฌ๋ฅผ ๋‹ค๋ฅธ View์—์„œ ๋งž์ถœ ์ˆ˜ ์žˆ์Œ.

 

์ฝ”๋“œ๋ฅผ ๋ถ„์„ํ•˜๋ฉด์„œ Realm์„ ์™„๋ฒฝํ•˜๊ฒŒ ์ ์šฉํ•˜์—ฌ ๋งŒ๋“ค์ง€๋Š” ๋ชปํ–ˆ์ง€๋งŒ, BaseViewController๋ž‘ Service๊ฐ€ ์™œ ํ•„์š”ํ•œ์ง€ ํ™•์‹คํ•˜๊ฒŒ ๋Š๋‚€ ์‹œ๊ฐ„์ด์—ˆ์Œ. ๋‹ค์Œ ์Šคํ…์œผ๋กœ Realm์„ ๋” ํ•™์Šตํ•ด์„œ ์™„๋ฒฝํ•˜๊ฒŒ ์ ์šฉํ•ด๋ณด์ž.

 

 

โœ… RxTodo Service์ชฝ ์ฝ”๋“œ ๋ถ„์„ โœ…

 

 

1. RxTodo์˜ ์„œ๋น„์Šค ํด๋” ๊ตฌ์กฐ

์„œ๋น„์Šค ํด๋” ๊ตฌ์กฐ

 

 

โœ… 1. ServiceProvider.swift

protocol ServiceProviderType: class {
  var userDefaultsService: UserDefaultsServiceType { get }
  var alertService: AlertServiceType { get }
  var taskService: TaskServiceType { get }
}

final class ServiceProvider: ServiceProviderType {
  lazy var userDefaultsService: UserDefaultsServiceType = UserDefaultsService(provider: self)
  lazy var alertService: AlertServiceType = AlertService(provider: self)
  lazy var taskService: TaskServiceType = TaskService(provider: self)
}

ServiceProvider๋Š” ServiceProviderType์„ ์ƒ์†๋ฐ›์•„์„œ provider์˜ self๋กœ ์ฃผ์–ด ๊ตฌํ˜„ํ•˜๋Š” ServiceProvider๋ฅผ ์ฑ„ํƒํ•  ํด๋ž˜์Šค์—์„œ ๊ตฌํ˜„ํ•˜๊ณ ์ž ํ•จ.

 

โœ… 2. UserDefaultsService.swift

import Foundation

extension UserDefaultsKey {
  static var tasks: Key<[[String: Any]]> { return "tasks" }
}

protocol UserDefaultsServiceType {
  func value<T>(forKey key: UserDefaultsKey<T>) -> T?
  func set<T>(value: T?, forKey key: UserDefaultsKey<T>)
}

final class UserDefaultsService: BaseService, UserDefaultsServiceType {

  private var defaults: UserDefaults {
    return UserDefaults.standard
  }

  func value<T>(forKey key: UserDefaultsKey<T>) -> T? {
    return self.defaults.value(forKey: key.key) as? T
  }

  func set<T>(value: T?, forKey key: UserDefaultsKey<T>) {
    self.defaults.set(value, forKey: key.key)
    self.defaults.synchronize()
  }

}

์ด ๋ถ€๋ถ„์€ ์ฐธ์‹ ํ–ˆ๋‹ค. 

 

๋‚œ ์•„๋ž˜์ฒ˜๋Ÿผ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉํ–ˆ์—ˆ๋‹ค.

// โœ… ๋‚ด๊ฐ€ ๊ธฐ์กด ํ”„๋กœ์ ํŠธ์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ๋ฐฉ์‹

extension StringSet { 
    public struct UserDefaultKey { 
         private static let `default` = "project.bundle.id"
         
         public static let tasks = "\(`default`).task"
         public static let tempTask = "\(`default`).temp.task"
    }
}

class UserDefaultManager { 
    // NOTE: - โœ… ๊ธฐ๋ณธํ˜•
    static var tasks: [Task] { 
    	get { UserDefaults.standard.object(forKey: StringSet.UserDefaultKey.tasks) }
        set { UserDefaults.standard.set(newValue, forKey: StringSet.UserDefaultKey.tasks) }
    }
    
    // NOTE: - โœ… ํ”„๋กœํผํ‹ฐ ๋ž˜ํผ ์‚ฌ์šฉ - ์ธ์ฝ”๋“ฑ ๋””์ฝ”๋”ฉ ์ œ๊ณต (์ฃผ์˜: ๋ชจ๋ธ์ด Codable ์ƒ์† ํ•„์ˆ˜)
    @UserDefault(key: StringSet.UserDefaultKey.tasks, defaultValue: [], storage: .standard)
    static var tasks: [Task]
    
}

@propertyWrapper
struct UserDefault<T: Codable> {
    let key: String
    let defaultValue: T
    let storage: UserDefaults

    var wrappedValue: T {
        get {
            guard let data = self.storage.object(forKey: self.key) as? Data else { return defaultValue }
            return (try? PropertyListDecoder().decode(T.self, from: data)) ?? self.defaultValue
        }
        set {
            let encodedData = try? PropertyListEncoder().encode(newValue)
            self.storage.set(encodedData, forKey: self.key)
        }
    }

    init(key: String, defaultValue: T, storage: UserDefaults = .standard) {
        self.key = key
        self.defaultValue = defaultValue
        self.storage = storage
    }
}

 

 

โœ… 2-1. UserDefaultKey<T>.Swift

- RxTodo์˜ ์ฝ”๋“œ

/// A generic key for `UserDefaults`. Extend this type to define custom user defaults keys.
///
/// ```
/// extension UserDefaultsKey {
///   static var myKey: Key<String> {
///     return "myKey"
///   }
///
///   static var anotherKey: Key<Int> {
///     return "anotherKey"
///   }
/// }
/// ```
struct UserDefaultsKey<T> {
  typealias Key<T> = UserDefaultsKey<T>
  let key: String
}

extension UserDefaultsKey: ExpressibleByStringLiteral {
  public init(unicodeScalarLiteral value: StringLiteralType) {
    self.init(key: value)
  }

  public init(extendedGraphemeClusterLiteral value: StringLiteralType) {
    self.init(key: value)
  }

  public init(stringLiteral value: StringLiteralType) {
    self.init(key: value)
  }
}

 

โœ… 3. BaseService.swift

class BaseService {
  unowned let provider: ServiceProviderType

  init(provider: ServiceProviderType) {
    self.provider = provider
  }
}

 

Service๋„ BaseService class๋ฅผ ์„ ์–ธํ•ด์„œ ์ œ๊ณตํ•œ๋‹ค.

 

 

โœ… 4. TaskService.swift

์ด๊ฒŒ ์ข€ ํ•ต์‹ฌ์ธ ๊ฒƒ ๊ฐ™์€๋ฐ, ๊ตฌ์กฐ๋ฅผ ๊ผผ๊ผผํ•˜๊ฒŒ ๋ถ„์„ํ•ด๋ณด์ž.

์ฃผ์„์œผ๋กœ ๋‹ฌ์•„๋‘์—ˆ์Œ!

import RxSwift

// 1. TaskEvent๋ฅผ ์ •์˜
enum TaskEvent {
  case create(Task)
  case update(Task)
  case delete(id: String)
  case move(id: String, to: Int)
  case markAsDone(id: String)
  case markAsUndone(id: String)
}

// 2. TaskServiceType์„ ์ •์˜
protocol TaskServiceType {
  var event: PublishSubject<TaskEvent> { get } // ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์„ Subject
  func fetchTasks() -> Observable<[Task]> // tasks๋ฅผ fetchํ•  ๋ฉ”์†Œ๋“œ

  @discardableResult // ๊ฒฐ๊ณผ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ warning์ด ๋‚˜ํƒ€๋‚˜์ง€ ์•Š์Œ
  func saveTasks(_ tasks: [Task]) -> Observable<Void> 

  func create(title: String, memo: String?) -> Observable<Task>
  func update(taskID: String, title: String, memo: String?) -> Observable<Task>
  func delete(taskID: String) -> Observable<Task>
  func move(taskID: String, to: Int) -> Observable<Task>
  func markAsDone(taskID: String) -> Observable<Task>
  func markAsUndone(taskID: String) -> Observable<Task>
}

// 3. TaskService๋ฅผ ๊ตฌํ˜„
final class TaskService: BaseService, TaskServiceType {
  // 3-1. ์ด๋ฒคํŠธ๋ฅผ ๋ฐ›์•„๋“ค์ผ ์„œ๋ธŒ์ ํŠธ
  let event = PublishSubject<TaskEvent>()

  // 3-2. task fetch
  func fetchTasks() -> Observable<[Task]> {
    // ์ €์žฅ๋œ ๋ฐ์ดํ„ฐ๊ฐ€ ์žˆ๋‹ค๋ฉด ๊ฐ’ ๊ฐ€์ ธ์˜ค๊ธฐ
    if let savedTaskDictionaries = self.provider.userDefaultsService.value(forKey: .tasks) {
      let tasks = savedTaskDictionaries.compactMap(Task.init)
      return .just(tasks) // ๊ฐ’์ด ์žˆ๋‹ค๋ฉด ๋ฆฌํ„ดํ•ด์„œ defaultTask๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๊ฒŒ ๋œ๋‹ค.
    }
    
    // ์ €์žฅ๋œ ๊ฐ’์ด ์—†๋‹ค๋ฉด ๊ธฐ๋ณธ task ๊ฐ’์œผ๋กœ ์„ค์ •
    let defaultTasks: [Task] = [
      Task(title: "Go to https://github.com/devxoul"),
      Task(title: "Star repositories I am intersted in"),
      Task(title: "Make a pull request"),
    ]
    
    // ๋””ํดํŠธ ๊ฐ’์œผ๋กœ ๋”•์…”๋„ˆ๋ฆฌ ๋งŒ๋“ค๊ธฐ
    let defaultTaskDictionaries = defaultTasks.map { $0.asDictionary() }
    // ๋””ํดํŠธ ๊ฐ’ ์ €์žฅ
    self.provider.userDefaultsService.set(value: defaultTaskDictionaries, forKey: .tasks)
    // ๋””ํดํŠธ ๊ฐ’์œผ๋กœ ๋ฆฌํ„ด
    return .just(defaultTasks) 
  }

  @discardableResult
  func saveTasks(_ tasks: [Task]) -> Observable<Void> {
    let dicts = tasks.map { $0.asDictionary() } // task๋ฅผ dictionary ํ˜•ํƒœ๋กœ ๋ณ€๊ฒฝ
    self.provider.userDefaultsService.set(value: dicts, forKey: .tasks) // ์ €์žฅ
    return .just(Void()) // ๋นˆ๊ฐ’ ๋‚ด๋ณด๋‚ด๊ธฐ
  }
  
  func create(title: String, memo: String?) -> Observable<Task> { 
  // ์ƒ์„ฑ์˜ ๊ฒฝ์šฐ์—๋Š” ์ƒ์„ฑ ํ›„ UI์—…๋ฐ์ดํŠธ๊ฐ€ ์ผ์–ด๋‚˜์•ผ ํ•˜๋ฏ€๋กœ ์ด๋ ‡๊ฒŒ ์ฒ˜๋ฆฌ
    return self.fetchTasks() 
      .flatMap { [weak self] tasks -> Observable<Task> in // task๋ฅผ ์˜ต์ €๋ฒ„๋ธ” ํ˜•ํƒœ๋กœ ๋ฐฉ์ถœ
        guard let `self` = self else { return .empty() } 
        let newTask = Task(title: title, memo: memo) // ์ƒˆ๋กœ์šด task ๊ฐ’์„ ์ƒ์„ฑ
        return self.saveTasks(tasks + [newTask]).map { newTask } // ์ƒˆ๋กœ์šด ํƒœ์Šคํฌ ๊ฐ’ ์ €์žฅ ํ›„ mapํ•ด์„œ ๋ฐฉ์ถœ
      }
      .do(onNext: { task in
        self.event.onNext(.create(task)) // ์ด๋ฒคํŠธ์— ์ƒ์„ฑ์ด๋ผ๊ณ  ํ•˜๋‚˜ ๋‚ด๋ณด๋ƒ„
      })
  }
  
  func update(taskID: String, title: String, memo: String?) -> Observable<Task> {
    // ์—…๋ฐ์ดํŠธ๋„ ๋˜‘๊ฐ™์ด ์‹ฑํฌ๋ฅผ ๋งž์ถ”๊ธฐ ์œ„ํ•ด fetch
    return self.fetchTasks()
      .flatMap { [weak self] tasks -> Observable<Task> in
        guard let `self` = self else { return .empty() }
        // ๊ฐ™์€ id๊ฐ€ ์žˆ๋Š”์ง€ ์ฐพ์•„์„œ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์ง„ํ–‰
        guard let index = tasks.index(where: { $0.id == taskID }) else { return .empty() }
        
        var tasks = tasks
        // with์˜ ๊ฒฝ์šฐ์—๋Š” Then ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์—์„œ ์„ ์–ธ๋˜์–ด ์žˆ์Œ
        let newTask = tasks[index].with { 
          $0.title = title
          $0.memo = memo
        }
        tasks[index] = newTask
        return self.saveTasks(tasks).map { newTask }
      }
      .do(onNext: { task in
        self.event.onNext(.update(task)) // ์ƒˆ๋กœ์šด ํƒœ์Šคํฌ๋ฅผ ์ด๋ฒคํŠธ๋กœ ๋ฐฉ์ถœ
      })
  }

  func delete(taskID: String) -> Observable<Task> {
    // ์ด๊ฒƒ๋„ ๋˜‘๊ฐ™์ด fetchํ•ด์•ผํ•ด
    return self.fetchTasks()
      .flatMap { [weak self] tasks -> Observable<Task> in
        guard let `self` = self else { return .empty() }
        guard let index = tasks.index(where: { $0.id == taskID }) else { return .empty() }
        var tasks = tasks
        let deletedTask = tasks.remove(at: index) // ํ•ด๋‹น ์ธ๋ฑ์Šค ์‚ญ์ œํ•˜๋ฉด ๋
        return self.saveTasks(tasks).map { deletedTask } // ๊ฐ’ ์ €์žฅ ํ›„์— ๋ฐฉ์ถœ
      }
      .do(onNext: { task in
        self.event.onNext(.delete(id: task.id)) // ์‚ญ์ œ๋œ id ์ด๋ฒคํŠธ๋กœ ์ „๋‹ฌ
      })
  }

  func move(taskID: String, to destinationIndex: Int) -> Observable<Task> {
    return self.fetchTasks()
      .flatMap { [weak self] tasks -> Observable<Task> in
        guard let `self` = self else { return .empty() }
        guard let sourceIndex = tasks.index(where: { $0.id == taskID }) else { return .empty() }
        var tasks = tasks
        let task = tasks.remove(at: sourceIndex) // ์‚ญ์ œํ•˜๊ณ 
        tasks.insert(task, at: destinationIndex) // ์ด๋™๋œ ์œ„์น˜์— ์‚ฝ์ž…
        return self.saveTasks(tasks).map { task }
      }
      .do(onNext: { task in
        self.event.onNext(.move(id: task.id, to: destinationIndex)) // ์•„์ด๋”” ๊ฐ’์ด๋ž‘ ์ด๋™๋œ ์œ„์น˜ ๋ฐ˜ํ™˜
      })
  }

  func markAsDone(taskID: String) -> Observable<Task> {
    // UI์˜†์— ์ฒดํฌํ‘œ์‹œ ์—ฌ๋ถ€
    return self.fetchTasks()
      .flatMap { [weak self] tasks -> Observable<Task> in
        guard let `self` = self else { return .empty() }
        guard let index = tasks.index(where: { $0.id == taskID }) else { return .empty() }
        var tasks = tasks
        let newTask = tasks[index].with {
          $0.isDone = true
          return
        }
        tasks[index] = newTask
        return self.saveTasks(tasks).map { newTask }
      }
      .do(onNext: { task in
        self.event.onNext(.markAsDone(id: task.id)) // ์™„๋ฃŒ๋˜์—ˆ๋‹ค๊ณ  id๋ฅผ ์ด๋ฒคํŠธ๋กœ ์ „๋‹ฌ
      })
  }

  func markAsUndone(taskID: String) -> Observable<Task> {
    return self.fetchTasks()
      .flatMap { [weak self] tasks -> Observable<Task> in
        guard let `self` = self else { return .empty() }
        guard let index = tasks.index(where: { $0.id == taskID }) else { return .empty() }
        var tasks = tasks
        let newTask = tasks[index].with {
          $0.isDone = false
          return
        }
        tasks[index] = newTask
        return self.saveTasks(tasks).map { newTask }
      }
      .do(onNext: { task in
        self.event.onNext(.markAsUndone(id: task.id))
      })
  }


}

 

โœ… TaskListReactor ์‚ดํŽด๋ณด๊ธฐ

import ReactorKit
import RxCocoa
import RxDataSources
import RxSwift

typealias TaskListSection = SectionModel<Void, TaskCellReactor>

final class TaskListViewReactor: Reactor {

  enum Action {
    case refresh
    case toggleEditing
    case toggleTaskDone(IndexPath)
    case deleteTask(IndexPath)
    case moveTask(IndexPath, IndexPath)
  }

  enum Mutation {
    case toggleEditing
    case setSections([TaskListSection])
    case insertSectionItem(IndexPath, TaskListSection.Item)
    case updateSectionItem(IndexPath, TaskListSection.Item)
    case deleteSectionItem(IndexPath)
    case moveSectionItem(IndexPath, IndexPath)
  }

  struct State {
    var isEditing: Bool
    var sections: [TaskListSection]
  }

  // ๐ŸŒผ 1. Properties ์„ ์–ธ
  let provider: ServiceProviderType
  let initialState: State
  
  // ๐ŸŒผ 2. init์˜ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ์ „๋‹ฌํ•˜์—ฌ ์ดˆ๊ธฐํ™”
  init(provider: ServiceProviderType) {
    self.provider = provider 
    self.initialState = State(
      isEditing: false,
      sections: [TaskListSection(model: Void(), items: [])]
    )
  }
  
  // ์•ก์…˜์— ๋”ฐ๋ฅธ ๋ณ€ํ™” 
  func mutate(action: Action) -> Observable<Mutation> {
    switch action {
    case .refresh:
      return self.provider.taskService.fetchTasks() // ๐ŸŒผ 3. Service๋ฅผ ์ด๋ ‡๊ฒŒ ์‚ฌ์šฉ
        .map { tasks in
          let sectionItems = tasks.map(TaskCellReactor.init)
          let section = TaskListSection(model: Void(), items: sectionItems)
          return .setSections([section])
        }

    case .toggleEditing:
      return .just(.toggleEditing)

    case let .toggleTaskDone(indexPath):
      let task = self.currentState.sections[indexPath].currentState // ํ˜„์žฌ state์˜ ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ
      if !task.isDone {
        return self.provider.taskService.markAsDone(taskID: task.id).flatMap { _ in Observable.empty() }
      } else {
        return self.provider.taskService.markAsUndone(taskID: task.id).flatMap { _ in Observable.empty() }
      }

    case let .deleteTask(indexPath):
      let task = self.currentState.sections[indexPath].currentState
      return self.provider.taskService.delete(taskID: task.id).flatMap { _ in Observable.empty() }

    case let .moveTask(sourceIndexPath, destinationIndexPath):
      let task = self.currentState.sections[sourceIndexPath].currentState
      return self.provider.taskService.move(taskID: task.id, to: destinationIndexPath.item)
        .flatMap { _ in Observable.empty() }
    }
  }

  func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
    let taskEventMutation = self.provider.taskService.event
      .flatMap { [weak self] taskEvent -> Observable<Mutation> in
        self?.mutate(taskEvent: taskEvent) ?? .empty()
      }
    return Observable.of(mutation, taskEventMutation).merge()
  }

  private func mutate(taskEvent: TaskEvent) -> Observable<Mutation> {
    let state = self.currentState
    switch taskEvent {
    case let .create(task):
      let indexPath = IndexPath(item: 0, section: 0)
      let reactor = TaskCellReactor(task: task)
      return .just(.insertSectionItem(indexPath, reactor))

    case let .update(task):
      guard let indexPath = self.indexPath(forTaskID: task.id, from: state) else { return .empty() }
      let reactor = TaskCellReactor(task: task)
      return .just(.updateSectionItem(indexPath, reactor))

    case let .delete(id):
      guard let indexPath = self.indexPath(forTaskID: id, from: state) else { return .empty() }
      return .just(.deleteSectionItem(indexPath))

    case let .move(id, index):
      guard let sourceIndexPath = self.indexPath(forTaskID: id, from: state) else { return .empty() }
      let destinationIndexPath = IndexPath(item: index, section: 0)
      return .just(.moveSectionItem(sourceIndexPath, destinationIndexPath))

    case let .markAsDone(id):
      guard let indexPath = self.indexPath(forTaskID: id, from: state) else { return .empty() }
      var task = state.sections[indexPath].currentState
      task.isDone = true
      let reactor = TaskCellReactor(task: task)
      return .just(.updateSectionItem(indexPath, reactor))

    case let .markAsUndone(id):
      guard let indexPath = self.indexPath(forTaskID: id, from: state) else { return .empty() }
      var task = state.sections[indexPath].currentState
      task.isDone = false
      let reactor = TaskCellReactor(task: task)
      return .just(.updateSectionItem(indexPath, reactor))
    }
  }

  func reduce(state: State, mutation: Mutation) -> State {
    var state = state
    switch mutation {
    case let .setSections(sections):
      state.sections = sections
      return state

    case .toggleEditing:
      state.isEditing = !state.isEditing
      return state

    case let .insertSectionItem(indexPath, sectionItem):
      state.sections.insert(sectionItem, at: indexPath)
      return state

    case let .updateSectionItem(indexPath, sectionItem):
      state.sections[indexPath] = sectionItem
      return state

    case let .deleteSectionItem(indexPath):
      state.sections.remove(at: indexPath)
      return state

    case let .moveSectionItem(sourceIndexPath, destinationIndexPath):
      let sectionItem = state.sections.remove(at: sourceIndexPath)
      state.sections.insert(sectionItem, at: destinationIndexPath)
      return state
    }
  }

  private func indexPath(forTaskID taskID: String, from state: State) -> IndexPath? {
    let section = 0
    let item = state.sections[section].items.index { reactor in reactor.currentState.id == taskID }
    if let item = item {
      return IndexPath(item: item, section: section)
    } else {
      return nil
    }
  }

  func reactorForCreatingTask() -> TaskEditViewReactor {
    return TaskEditViewReactor(provider: self.provider, mode: .new)
  }

  func reactorForEditingTask(_ taskCellReactor: TaskCellReactor) -> TaskEditViewReactor {
    let task = taskCellReactor.currentState
    return TaskEditViewReactor(provider: self.provider, mode: .edit(task))
  }

}
Comments