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)"
}
}
}
...
}
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)
}