Skip to content

Instantly share code, notes, and snippets.

@audrl1010
Last active November 8, 2018 09:23
Show Gist options
  • Save audrl1010/89794d8a295bfd10d234c3f9ea339194 to your computer and use it in GitHub Desktop.
Save audrl1010/89794d8a295bfd10d234c3f9ea339194 to your computer and use it in GitHub Desktop.
RxDataSources 해부 해보기

Differentiator

public protocol IdentifiableType {
  associatedtype Identity: Hashable
  var identity: Identity { get }
}

public struct IdentifiableValue<Value: Hashable> {
  public let value: Value
}

extension IdentifiableValue: IdentifiableType {
  public typealias Identity = Value
  public var identity: Identity {
    return value
  }
}

extension IdentifiableValue: Equatable, CustomStringConvertible, CustomDebugStringConvertible {
  public var description: String { return "\(value)" }
  public var debugDescription: String { return "\(value)" }
}

public func == <V>(lhs: IdentifiableValue<V>, rhs: IdentifiableValue<V>) -> Bool {
  return lhs.value == rhs.value
}

public protocol SectionModelType {
  associatedtype Item
  
  var items: [Item] { get }
  
  init(original: Self, items: [Item])
}

public struct SectionModel<Section, ItemType> {
  public var model: Section
  public var items: [Item]
  
  public init(mode: Section, items: [Item]) {
    self.model = model
    self.items = items
  }
}

extension SectionModel: SectionModelType {
  public typealias Identity = Section
  public typealias Item = ItemType
  
  public var identity: Section {
    return model
  }
  
  public init(original: SectionModel<Section, Item>, items: [Item]) {
    self.model = original.model
    self.items = items
  }
}

extension SectionModel: CustomStringConvertible {
  public var description: String { return "\(self.model) > \(self.items)"
}

public struct ItemPath {
  public let sectionIndex: Int
  public let itemIndex: Int
  
  public init(sectionIndex: Int, itemIndex: Int) {
    self.sectionIndex = sectionIndex
    self.itemIndex = itemIndex
  }
}

extension ItemPath: Equatable {}
public func == (lhs: ItemPath, rhs: ItemPath) -> Bool {
  return lhs.sectionIndex == rhs.sectionIndex && lhs.itemIndex == rhs.itemIndex
}

extension ItemPath: Hashable {
  public var hashValue: Int {
    return sectionIndex.byteSwapped.hashValue ^ itemIndex.hashValue 
  }
}

public protocol AnimatableSectionModelType
  : SectionModelType
  , IdentifiableType where Item: IdentifiableType, Item: Equatable { }

extension Array where Element: AnimatableSectionModelType {
  subscript(index: ItemPath) -> Element.Item {
    return self[index.sectionIndex].items[index.itemIndex]
  }
}

public struct AnimatableSectionModel<Section: IdentifiableType, ItemType: IdentifiableType & Equatable> {
  public var model: Section
  public var items: [Item]
  
  public init(model: Section, items: [ItemType]) {
    self.model = model
    self.items = items
  }
}

extension AnimatableSectionModel: AnimatableSectionModelType {
  public typealias Item = ItemType
  public typealias Identity = Section.Identity
  
  public var identity: Identity {
    return model.identity
  }
  
  public init(original: AnimatableSectionModel, items: [Item]) {
    self.model = original.model
    self.items = items
  }
  
  public var hashValue: Int {
    return self.model.identity.hashValue
  }
}

extension AnimatableSectionModel: CustomStringConvertible {
  public var description: String {
    return "HashableSectionModel(model: \"\(self.model)\", items: \(items))"
  }
}

enum DifferentiatorError: Error {
  case unwrappingOptional
  case preconditionFailed(message: String)
}

func precondition(_ condition: Bool, _ message: @autoclosure () -> String) throws -> Void {
  if condition { return }
  debugFatalError("Precondition failed")
  throw DifferentiatorError.preconditionFailed(message: message())
}

func debugFatalError(_ error: Error) {
  debugFatalError("\(error)")
}

func debugFatalError(_ message: String) {
  #if DEBUG
    fatalError(message)
  #else
    print(message)
  #endif
}

extension Optional {
  func unwrap() throws -> Wrapped {
    if let unwrapped = self {
      return unwrapped
    } else {
      debugFatalError("Error during unwrapping optional")
      throw DifferentiatorError.unwrappingOptional
    }
  }
}

fileprivate extension AnimatableSectionModelType {

}


public struct Changeset<S: SectionModelType> {
  public typealias I = S.Item
  
  public let reloadData: Bool
  
  public let originalSections: [S]
  public let finalSections: [S]
  
  public let insertedSections: [Int]
  public let deletedSections: [Int]
  public let movedSections: [(from: Int, to: Int)]
  public let updatedSections: [Int]
  
  public let insertedItems: [ItemPath]
  public let deletedItems: [ItemPath]
  public let movedItems: [(from: ItemPath, to: ItemPath)]
  public let updatedItems: [ItemPath]
  
  init(
    reloadData: Bool = false,
    originalSections: [S] = [],
    finalSections: [S] = [],
    insertedSections: [Int] = [],
    deletedSections: [Int] = [],
    movedSections: [(from: Int, to: Int)] = [],
    updatedSections: [Int] = [],

    insertedItems: [ItemPath] = [],
    deletedItems: [ItemPath] = [],
    movedItems: [(from: ItemPath, to: ItemPath)] = [],
    updatedItems: [ItemPath] = []
  ) {
    self.reloadData = reloadData

    self.originalSections = originalSections
    self.finalSections = finalSections

    self.insertedSections = insertedSections
    self.deletedSections = deletedSections
    self.movedSections = movedSections
    self.updatedSections = updatedSections

    self.insertedItems = insertedItems
    self.deletedItems = deletedItems
    self.movedItems = movedItems
    self.updatedItems = updatedItems
  }
}


public enum Diff {
  public enum Error: Swift.Error, CustomDebugStringConvertible {
    case duplicateItem(item: Any)
    case duplicateSection(section: Any)
    case invalidInitializerImplementation(section: Any, expectedItems: Any, expectedIdentifier: Any)
    
    public var debugDescription: String {
      switch self {
      case let .duplicateItem(item):
        return "Duplication item \(item)"
      case let .duplicateSection(section):
        return "DuplicateSection section \(section)"
      case let .invalidInitializerImplementation(section, expectedItems, expectedIdentifier):
        return "Wrong initializer implementation for: \(section)\n" +
            "Expected it should return items: \(expectedItems)\n" +
        "Expected it should have id: \(expectedIdentifier)"
      }
    }
  }
  
  ...
  
}

RxDataSources

enum RxDataSourceError: Error {
  case preconditionFailed(message: String)
}

func rxPrecondition(_ condition: Bool, _ message: @autoclosure () -> String) throws -> () {
  if condition { return }
  
  rxDebugFatalError("Precondition failed")
  
  throw RxDataSourceError.preconditionFailed(message: message())
}

func rxDebugFatalError(_ message: String) {
 #if DEBUG
  fatalError(message)
 #else
  print(message)
 #endif
}


public protocol SectionedViewDataSourceType {
  func model(at indexPath: IndexPath) throws -> Any
}

public RxCollectionViewDataSourceType {
  associatedtype Element
  func collectionView(_ collectionView, observedEvent: Event<Element>) -> Void
}

open class CollectionViewSectionedDataSource<S: SectionModelType>
  : NSObject
  , UICollectionViewDataSource
  , SectionedViewDataSourceType {
  
  public typealias I = S.Item
  public typealias Section = S
  public typealias ConfigureCell = (CollectionViewSectionedDataSource<S>, UICollectionView, IndexPath, I) -> UICollectionViewCell
  public typealias ConfigureSupplementaryView = (CollectionViewSectionedDataSource<S>, UICollectionView, String, IndexPath) -> UICollectionReusableView
  public typealias MoveItem = (CollectionViewSectionedDataSource<S>, _ sourceIndexPath: IndexPath, _ destinationIndexPath: IndexPath) -> Void
  public typealias CanMoveItemAtIndexPath = (CollectionViewSectionedDataSource<S>, IndexPath) -> Bool
  
  open var configureCell: ConfigureCell
  open var configureSupplementaryView: ConfigureSupplementaryView
  open var moveItem: MoveItem
  open var canMoveItemAtIndexPath: CanMoveItemAtIndexPath?
  
  public init(
    configureCell: @escaping ConfigureCell,
    configureSupplementaryView: ConfigureSupplementaryView? = nil,
    moveItem: @escaping MoveItem = { _, _, _ in () },
    canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPath = { _, _ in false }
  ) {
    self.configureCell = configureCell
    self.configureSupplementaryView = configureSupplementaryView
    self.moveItem = moveItem
    self.canMoveItemAtIndexPath = canMoveItemAtIndexPath
  }
  
  public typealias SectionModelSnapshot = SectionModel<S, I>
  
  private var _sectionModels: [SectionModelSnapshot] = []
  
  open var sectionModels: [S] {
    return _sectionModels.map { Section(original: $0.model, items: $0.items) }
  }
  
  open subscript(section: Int) -> S {
    let sectionModel = self._sectionModels[section]
    return S(original: sectionModel.model, items: sectionModel.items)
  }
  
  open subscript(indexPath: IndexPath) -> I {
    get {
      return self._sectionModels[indexPath.section].items[indexPath.item]
    }
    set {
      var section = self._sectionModels[indexPath.section]
      section.items[indexPath.item] = item
      self._sectionModels[indexPath.section] = section
    }
  }
  
  open func setSections(_ sections: [S]) {
    self._sectionModels = sections.map { SectionModelSnapshot(model: $0, items: $0.items) }
  }

  // SectionedViewDataSourceType
  open func model(at indexPath: IndexPath) throws -> Any {
    return self[indexPath]
  }
  
  // UICollectionViewDataSource
  open func numberOfSections(in collectionView: UICollectionView) -> Int {
    return _sectionModels.count
  }
  
  open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    return _sectionModels[section].items.count
  }
  
  open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
     precondition(indexPath.item < _sectionModels[indexPath.section].items.count)
     return configureCell(self, collectionView, indexPath, self[indexPath])
  }
  
  open func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
    return configureSupplementaryView!(self, collectionView, kind, indexPath)
  }
  
  open func collectionView(_ collectionView: UICollectionView, canMoveItemAt indexPath: IndexPath) -> Bool {
    guard let canMoveItem = canMoveItemAtIndexPath?(self, indexPath) else { return false }
  }
  
  open func collectionView(_ collectionView: UICollectionView, moveItemAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
    self._sectionModels.moveFromSourceIndexPath(sourceIndexPath, destinationIndex: destinationIndexPath)
    self.moveItem(self, sourceIndexPath, destinationIndexPath)
  }
  
  override open func responds(to aSelector: Selector!) -> Bool {
    if aSelector == #selector(UICollectionViewDataSource.collectionView(_:viewForSupplementaryElementOfKind:at:)) {
       return configureSupplementaryView != nil
    } else {
       return super.responds(to: aSelector)
    }
  }
}

open class RxCollectionViewSectionedReloadDataSource<S: SectionModelType>
  : CollectionViewSectionedDataSource<S>
  , RxCollectionViewDataSourceType {
  
  public typealias Element = [S]
  
  open func collectionView(_ collectionView: UICollectionView, observedEvent: Event<Element>) {
    Binder(self) { dataSource, element in
      dataSource.setSections(element)
      collectionView.reloadData()
      collectionView.collectionViewLayout.invalidateLayout()
    }.on(observedEvent)
  }
}

public enum ViewTransition {
  case animated // animated transition
  case reload // refresh view without animations
}

open class RxCollectionViewSectionAnimatedDataSource<S: AnimatableSectionModelType>
  : CollectionViewSectionedDataSource<S>
  , RxCollectionViewDataSourceType {
  
  public typealias Element = [S]
  public typealias DecideViewTransition = (CollectionViewSectionedDataSource<S>, UICollectionView, [Changeset<S>]) -> ViewTransition

  public var animationConfiguration: AnimationConfiguration
  
  public var decideViewTransition: DecideViewTransition
  
  public init(
    animationConfiguration: AnimationConfiguration = AnimationConfiguration(),
    decideViewTransition: @escaping DecideViewTransition = { _, _, _ in .animated },
    configureCell: @escaping ConfigureCell,
    configureSupplementaryView: ConfigureSupplementaryView? = nil,
    moveItem: @escaping MoveItem = { _, _, _ in () },
    canMoveItemAtIndexPath: @escaping CanMoveItemAtIndexPath = { _, _ in false }
  ) {
    self.animationConfiguration = animationConfiguration
    self.decideViewTransition = decideViewTransition
    super.init(
        configureCell: configureCell,
        configureSupplementaryView: configureSupplementaryView,
        moveItem: moveItem,
        canMoveItemAtIndexPath: canMoveItemAtIndexPath
    )

    self.partialUpdateEvent
        // so in case it does produce a crash, it will be after the data has changed
        .observeOn(MainScheduler.asyncInstance)
        // Collection view has issues digesting fast updates, this should
        // help to alleviate the issues with them.
        .throttle(0.5, scheduler: MainScheduler.instance)
        .subscribe(onNext: { [weak self] event in
            self?.collectionView(event.0, throttledObservedEvent: event.1)
        })
        .disposed(by: disposeBag)
  }
  // For some inexplicable reason, when doing animated updates first time
  // it crashes. Still need to figure out that one.
  var dataSet = false

  private let disposeBag = DisposeBag()
  
  // This subject and throttle are here
  // because collection view has problems processing animated updates fast.
  // This should somewhat help to alleviate the problem.
  private let partialUpdateEvent = PublishSubject<(UICollectionView, Event<Element>)>()
  
  /**
   This method exists because collection view updates are throttled because of internal collection view bugs.
   Collection view behaves poorly during fast updates, so this should remedy those issues.
  */
  open func collectionView(_ collectionView: UICollectionView, throttledObservedEvent event: Event<Element>) {
    Binder(self) { dataSource, newSections in
      let oldSections = dataSource.sectionModels
      do {
        // if view is not in view hierarchy, performing batch updates will crash the app.
        if collectionView.window == nil {
          dataSource.setSections(newSections)
          collectionView.reloadData()
          return
        }
        
        let differences = try Diff.differencesForSectionedView(initialSections: oldSections, finalSections: newSections)
        switch self.decideViewTransition(self, collectionView, differences) {
        case .animated:
          for difference in differences {
            dataSource.setSections(difference.finalSections)
            collectionView.performBatchUpdates(difference, animationConfiguration: self.animationConfiguration)
          }
        case .reload:
          self.setSections(newSections)
          collectionView.reloadData()
        }
      } catch let e {
        self.setSections(newSections)
        collectionView.reloadData()
      }
    }.on(event)
  }
    
  open func collectionView(_ collectionView: UICollectionView, observedEvent: Event<Element>) {
    Binder(self) { dataSource, newSections in
      if !self.dataSet {
        self.dataSet = true
        dataSource.setSections(newSections)
        collectionView.reloadData()
      }
      else {
        let element = (collectionView, observedEvent)
        dataSource.partialUpdateEvent.on(.next(element))
      }
    }.on(observedEvent)
  }
}


public typealias UITableViewRowAnimation = UITableView.RowAnimation

public protocol SectionedViewType {
  func insertItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation)
  func deleteItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation)
  func moveItemAtIndexPath(_ from: IndexPath, to: IndexPath)
  func reloadItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation)
  
  func insertSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)
  func deleteSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)
  func moveSection(_ from: Int, to: Int)
  func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation)

  func performBatchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration)
}

func _performBatchUpdates<V: SectionedViewType, S>(
  _ view: V, 
  changes: Changeset<S>, 
  animationConfiguration:AnimationConfiguration
) {
  typealias I = S.Item
  view.deleteSections(changes.deletedSections, animationStyle: animationConfiguration.deleteAnimation)
  
  // Updated sections doesn't mean reload entire section, somebody needs to update the section view manually
  // otherwise all cells will be reloaded for nothing.
  // view.reloadSections(changes.updatedSections, animationStyle: rowAnimation)
  
  view.insertSections(changes.insertedSections, animationStyle: animationConfiguration.insertAnimation)
  for (from, to) in changes.movedSections {
      view.moveSection(from, to: to)
  }
  
  view.deleteItemsAtIndexPaths(
    changes.deletedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
    animationStyle: animationConfiguration.deleteAnimation
  )
  view.insertItemsAtIndexPaths(
    changes.insertedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
    animationStyle: animationConfiguration.insertAnimation
  )
  view.reloadItemsAtIndexPaths(
    changes.updatedItems.map { IndexPath(item: $0.itemIndex, section: $0.sectionIndex) },
    animationStyle: animationConfiguration.reloadAnimation
  )
  for (from, to) in changes.movedItems {
    view.moveItemAtIndexPath(
      IndexPath(item: from.itemIndex, section: from.sectionIndex),
      to: IndexPath(item: to.itemIndex, section: to.sectionIndex)
    )
  }
}

func indexSet(_ values: [Int]) -> IndexSet {
  let indexSet = NSMutableIndexSet()
  for i in values {
      indexSet.add(i)
  }
  return indexSet as IndexSet
}

extension UITableView : SectionedViewType {
  public func insertItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
     self.insertRows(at: paths, with: animationStyle)
  }
  public func deleteItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
     self.deleteRows(at: paths, with: animationStyle)
  }
  public func moveItemAtIndexPath(_ from: IndexPath, to: IndexPath) {
     self.moveRow(at: from, to: to)
  }
  public func reloadItemsAtIndexPaths(_ paths: [IndexPath], animationStyle: UITableViewRowAnimation) {
     self.reloadItems(at: paths)
  }
  public func insertSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
     self.insertSections(indexSet(sections))
  }
  public func deleteSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
    self.deleteSections(indexSet(sections))
  }
  public func moveSection(_ from: Int, to: Int) {
      self.moveSection(from, toSection: to)
  }
  public func reloadSections(_ sections: [Int], animationStyle: UITableViewRowAnimation) {
      self.reloadSections(indexSet(sections))
  } 
  public func performBatchUpdates<S>(_ changes: Changeset<S>, animationConfiguration: AnimationConfiguration) {
      self.performBatchUpdates({ () -> Void in
          _performBatchUpdates(self, changes: changes, animationConfiguration: animationConfiguration)
      }, 
      completion: { (completed: Bool) -> Void in })
    }
  }
}


extension ObservableType {
  func subscribeProxyDataSource<DelegateProxy: DelegateProxyType>(
    ofObject object: DelegateProxy.ParentObject,
    dataSource: DelegateProxy.Delegate,
    retainDataSource: Bool, binding: @escaping (DelegateProxy, Event<E>) -> Void
  ) {
    let proxy = DelegateProxy.proxy(for: object)
    let unregisterDelegate = DelegateProxy.installForwardDelegate(
      dataSource,
      retainDelegate: retainDataSource,
      onProxyForObject: object
    )
    // this is needed to flush any delayed old state (https://github.com/RxSwiftCommunity/RxDataSources/pull/75)
    object.layoutIfNeeded()
    let subscription = self.asObservable()
        .observeOn(MainScheduler())
        .catchError { error in
            bindingError(error)
            return Observable.empty()
        }
        // source can never end, otherwise it would release the subscriber, and deallocate the data source
        .concat(Observable.never())
        .takeUntil(object.rx.deallocated)
        .subscribe { [weak object] (event: Event<E>) in
            if let object = object {
                assert(proxy === DelegateProxy.currentDelegate(for: object),
                "Proxy changed from the time it was first set.\nOriginal: \(proxy)\nExisting: \(String(describing: DelegateProxy.currentDelegate(for: object)))")
            }

            binding(proxy, event)

            switch event {
            case .error(let error):
                bindingError(error)
                unregisterDelegate.dispose()
            case .completed:
                unregisterDelegate.dispose()
            default:
                break
            }
        }

    return Disposables.create { [weak object] in
        subscription.dispose()
        object?.layoutIfNeeded()
        unregisterDelegate.dispose()
    }
  }
}


/// MARK: Items
extension Reactive where Base: UICollectionView {
  
  /*
   let dataSource = RxCollectionViewSectionedReloadDataSource<SectionModel<String, Double>>()
   let items = Observable.just([
       SectionModel(model: "First section", items: [1.0, 2.0, 3.0]),
       SectionModel(model: "Second section", items: [1.0, 2.0, 3.0]),
       SectionModel(model: "Third section", items: [1.0, 2.0, 3.0])
   ])

   dataSource.configureCell = { (dataSource, collectionView, indexPath, element) in
       let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell
       cell.value?.text = "\(element) @ row \(indexPath.row)"
       return cell
   }
   
   items
     .bind(to: collectionView.rx.items(dataSource: dataSource))
     .disposed(by: disposeBag)
  */


  public func items<DataSource: RxCollectionViewDataSourceType & UICollectionViewDataSource, O: ObservableType>(
    dataSource: DataSource
  ) -> (_ source: O) -> Disposable where DataSource.Element == O.E {
    return { source in
      _ = self.delegate
      return source.subscribeProxyDataSource(
        ofObject: self.base,
        dataSource: dataSource,
        retainDataSource: true
       ) { [weak collectionView = self.base] (_: RxCollectionViewDataSourceProxy, event) -> Void in
        guard let collectionView = collectionView else { return }
        dataSource.collectionView(collectionView, observedEvent: event)
      }
    }
  }
  
  /*
     let items = Observable.just([1, 2, 3])
     
     items
     .bind(to: collectionView.rx.items) { (collectionView, row, element) in
        let indexPath = IndexPath(row: row, section: 0)
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! NumberCell
         cell.value?.text = "\(element) @ \(row)"
         return cell
     }
     .disposed(by: disposeBag)
  */
  public func items<S: Sequence, O: ObservableType>(_ source: O) -> (_ cellFactory: @escaping (UICollectionView, Int, S.Iterator.Element) -> UICollectionViewCell -> Disposable where O.E == S {
    return { cellFactory in
      let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S>(cellFactory: cellFactory)
      return self.items(dataSource: dataSource)(source)
    }
  }
  
  public func items<S: Sequence, Cell: UICollectionViewCell, O: ObservableType>(
    cellIdentifier: String, cellType: Cell.Type = Cell.self
  ) -> (_ source: O) -> (_ configureCell: @escaping (Int, S.Iterator.Element, Cell) -> Void) -> Disposable where O.E == S {
    return { source in
      return { configureCell in
        let dataSource = RxCollectionViewReactiveArrayDataSourceSequenceWrapper<S> { collectionView, index, item in
          let indexPath = IndexPath(item: index, section: 0)
          let cell = collectionView.dequeueReusableCell(withReuseIdentifier: cellIdentifier, for: indexPath) as! Cell
          configureCell(index, item, cell)
          return cell
        }
        return self.items(dataSource: dataSource)(source)
      }
    }
  }
}





open class RxCollectionViewDataSourceProxy
  : DelegateProxy<UICollectionView, UICollectionViewDataSource>
  , DelegateProxyType
  , UICollectionViewDataSource {
  
  // Typed parent object.
  public weak private(set) var collectionView: UICollectionView?
  
  /// - parameter collectionView: Parent object for delegate proxy.
  public init(collectionView: ParentObject) {
    self.collectionView = collectionView
    super.init(parentObject: collectionView, delegateProxy: RxCollectionViewDataSourceProxy.self)
  }
  
  // Register known implementations
  public static func registerKnownImplementations() {
    self.register { RxCollectionViewDataSourceProxy(collectionView: $0) }
  }
  
  private weak var _requiredMethodsDataSource: UICollectionViewDataSource? = collectionViewDataSourceNotSet
  
  // MARK: delegate

  /// Required delegate method implementation.
  public func collectionView(
    _ collectionView: UICollectionView,
    numberOfItemsInSection section: Int
  ) -> Int {
    return (_requiredMethodsDataSource ?? collectionViewDataSourceNotSet)
      .collectionView(collectionView, numberOfItemsInSection: section)
  }

  /// Required delegate method implementation.
  public func collectionView(
    _ collectionView: UICollectionView,
    cellForItemAt indexPath: IndexPath
  ) -> UICollectionViewCell {
    return (_requiredMethodsDataSource ?? collectionViewDataSourceNotSet)
      .collectionView(collectionView, cellForItemAt: indexPath)
  }
  open override func setForwardToDelegate(_ forwardToDelegate: UICollectionViewDataSource?, retainDelegate: Bool) {
    _requiredMethodsDataSource = forwardToDelegate ?? collectionViewDataSourceNotSet
    super.setForwardToDelegate(forwardToDelegate, retainDelegate: retainDelegate)
  }
}

extension Reactive where Base: UICollectionView {
  public typealias DisplayCollectionViewCellEvent = (cell: UICollectionViewCell, at: IndexPath)
  public typealias DisplayCollectionViewSupplementaryViewEvent = (supplementaryView: UICollectionReusableView, elementKind: String, at: IndexPath)
  
  public var dataSource: DelegateProxy<UICollectionView, UICollectionViewDataSource> {
    return RxCollectionViewDataSourceProxy.proxy(for: base)
  }
}


/// RxCocoa errors.
public enum RxCocoaError
    : Swift.Error
    , CustomDebugStringConvertible {
    /// Unknown error has occurred.
    case unknown
    /// Invalid operation was attempted.
    case invalidOperation(object: Any)
    /// Items are not yet bound to user interface but have been requested.
    case itemsNotYetBound(object: Any)
    /// Invalid KVO Path.
    case invalidPropertyName(object: Any, propertyName: String)
    /// Invalid object on key path.
    case invalidObjectOnKeyPath(object: Any, sourceObject: AnyObject, propertyName: String)
    /// Error during swizzling.
    case errorDuringSwizzling
    /// Casting error.
    case castingError(object: Any, targetType: Any.Type)
}


// MARK: Debug descriptions

extension RxCocoaError {
    /// A textual representation of `self`, suitable for debugging.
    public var debugDescription: String {
        switch self {
        case .unknown:
            return "Unknown error occurred."
        case let .invalidOperation(object):
            return "Invalid operation was attempted on `\(object)`."
        case let .itemsNotYetBound(object):
            return "Data source is set, but items are not yet bound to user interface for `\(object)`."
        case let .invalidPropertyName(object, propertyName):
            return "Object `\(object)` doesn't have a property named `\(propertyName)`."
        case let .invalidObjectOnKeyPath(object, sourceObject, propertyName):
            return "Unobservable object `\(object)` was observed as `\(propertyName)` of `\(sourceObject)`."
        case .errorDuringSwizzling:
            return "Error during swizzling."
        case .castingError(let object, let targetType):
            return "Error casting `\(object)` to `\(targetType)`"
        }
    }
}



// MARK: Error binding policies

func bindingError(_ error: Swift.Error) {
    let error = "Binding error: \(error)"
#if DEBUG
    rxFatalError(error)
#else
    print(error)
#endif
}

/// Swift does not implement abstract methods. This method is used as a runtime check to ensure that methods which intended to be abstract (i.e., they should be implemented in subclasses) are not called directly on the superclass.
func rxAbstractMethod(message: String = "Abstract method", file: StaticString = #file, line: UInt = #line) -> Swift.Never {
    rxFatalError(message, file: file, line: line)
}

func rxFatalError(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) -> Swift.Never  {
    // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
    fatalError(lastMessage(), file: file, line: line)
}

func rxFatalErrorInDebug(_ lastMessage: @autoclosure () -> String, file: StaticString = #file, line: UInt = #line) {
    #if DEBUG
        fatalError(lastMessage(), file: file, line: line)
    #else
        print("\(file):\(line): \(lastMessage())")
    #endif
}

// MARK: casts or fatal error

// workaround for Swift compiler bug, cheers compiler team :)
func castOptionalOrFatalError<T>(_ value: Any?) -> T? {
    if value == nil {
        return nil
    }
    let v: T = castOrFatalError(value)
    return v
}

func castOrThrow<T>(_ resultType: T.Type, _ object: Any) throws -> T {
    guard let returnValue = object as? T else {
        throw RxCocoaError.castingError(object: object, targetType: resultType)
    }

    return returnValue
}

func castOptionalOrThrow<T>(_ resultType: T.Type, _ object: AnyObject) throws -> T? {
    if NSNull().isEqual(object) {
        return nil
    }

    guard let returnValue = object as? T else {
        throw RxCocoaError.castingError(object: object, targetType: resultType)
    }

    return returnValue
}

func castOrFatalError<T>(_ value: AnyObject!, message: String) -> T {
    let maybeResult: T? = value as? T
    guard let result = maybeResult else {
        rxFatalError(message)
    }
    
    return result
}

func castOrFatalError<T>(_ value: Any!) -> T {
    let maybeResult: T? = value as? T
    guard let result = maybeResult else {
        rxFatalError("Failure converting from \(value) to \(T.self)")
    }
    
    return result
}

// MARK: Error messages

let dataSourceNotSet = "DataSource not set"
let delegateNotSet = "Delegate not set"

// MARK: Shared with RxSwift

func rxFatalError(_ lastMessage: String) -> Never  {
    // The temptation to comment this line is great, but please don't, it's for your own good. The choice is yours.
    fatalError(lastMessage)
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment