Last active
April 14, 2020 11:54
-
-
Save JaviSoto/9ea2205d4216629e2926669b97039c01 to your computer and use it in GitHub Desktop.
DataLoadState
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// DataLoadState.swift | |
// Fabric | |
// | |
// Created by Javier Soto on 3/16/16. | |
// Copyright © 2016 Fabric. All rights reserved. | |
// | |
import Foundation | |
import ReactiveCocoa | |
import enum Result.NoError | |
protocol DataLoadStateProtocol { | |
associatedtype IdentifierType | |
associatedtype DataType | |
var dataLoadState: DataLoadState<IdentifierType, DataType> { get } | |
} | |
/// Identifier allows us to specify a value that allows us to know something from the data that we're loading before we load it. | |
enum DataLoadState<Identifier, Data>: DataLoadStateProtocol { | |
typealias IdentifierType = Identifier | |
typealias DataType = Data | |
case Loading(Identifier) | |
case Failed(Identifier) | |
case Loaded(Identifier, Data) | |
var identifier: Identifier { | |
switch self { | |
case let .Loading(identifier): return identifier | |
case let .Failed(identifier): return identifier | |
case let .Loaded(identifier, _): return identifier | |
} | |
} | |
var loading: Bool { | |
switch self { | |
case .Loading: return true | |
case .Failed, .Loaded: return false | |
} | |
} | |
var success: Bool { | |
switch self { | |
case .Loading, .Failed: return false | |
case .Loaded: return true | |
} | |
} | |
var data: Data? { | |
switch self { | |
case .Loading, .Failed: return nil | |
case let .Loaded(_, data): return data | |
} | |
} | |
var dataLoadState: DataLoadState<IdentifierType, DataType> { | |
return self | |
} | |
func map<NewDataType>(f: Data -> NewDataType) -> DataLoadState<Identifier, NewDataType> { | |
switch self { | |
case let .Loading(identifier): return DataLoadState<Identifier, NewDataType>.Loading(identifier) | |
case let .Failed(identifier): return DataLoadState<Identifier, NewDataType>.Failed(identifier) | |
case let .Loaded(identifier, data): return DataLoadState<Identifier, NewDataType>.Loaded(identifier, f(data)) | |
} | |
} | |
} | |
func ==<Identifier, DataType where Identifier: Equatable, DataType: Equatable>(lhs: DataLoadState<Identifier, DataType>, rhs: DataLoadState<Identifier, DataType>) -> Bool { | |
switch (lhs, rhs) { | |
case let (.Loading(identifier1), .Loading(identifier2)) where identifier1 == identifier2: return true | |
case let (.Failed(identifier1), .Failed(identifier2)) where identifier1 == identifier2: return true | |
case let (.Loaded(identifier1, data1), .Loaded(identifier2, data2)) where identifier1 == identifier2 && data1 == data2: return true | |
default: return false | |
} | |
} | |
/// These extension methods are helpers to create `DataLoadState`s without an `Identifier` (the common case) | |
extension DataLoadState { | |
static func loadingData<T>() -> DataLoadState<(), T> { | |
return DataLoadState<(), T>.Loading(()) | |
} | |
static func failedLoad<T>() -> DataLoadState<(), T> { | |
return DataLoadState<(), T>.Failed(()) | |
} | |
static func loadedData<T>(data: T) -> DataLoadState<(), T> { | |
return DataLoadState<(), T>.Loaded((), data) | |
} | |
} | |
extension SignalProducerType { | |
@warn_unused_result(message="Did you forget to call `start` on the producer?") | |
func materializeToLoadState<IdentifierType>(identifier identifier: IdentifierType, redirectErrorsToObserver errorObserver: Observer<Self.Error, Result.NoError>? = nil) -> SignalProducer<DataLoadState<IdentifierType, Self.Value>, NoError> { | |
let producer = self | |
.map { DataLoadState.Loaded(identifier, $0) } | |
.startWithValue(DataLoadState.Loading(identifier)) | |
if let errorObserver = errorObserver { | |
return producer.redirectErrorsToObserver(errorObserver, replacementValue: DataLoadState.Failed(identifier)) | |
} | |
else { | |
return producer.ignoreErrors(replacementValue: DataLoadState.Failed(identifier)) | |
} | |
} | |
func materializeToLoadState(redirectErrorsToObserver errorObserver: Observer<Self.Error, Result.NoError>? = nil) -> SignalProducer<DataLoadState<(), Self.Value>, NoError> { | |
return self.materializeToLoadState(identifier: ()) | |
} | |
} | |
extension SignalProducerType where Value: DataLoadStateProtocol { | |
@warn_unused_result(message="Did you forget to call `start` on the producer?") | |
func ignoreLoadingAndErrorsAfterSuccessfulLoad() -> SignalProducer<DataLoadState<Self.Value.IdentifierType, Self.Value.DataType>, Self.Error> { | |
return SignalProducer { observer, compositeDisposable in | |
var hasSuccededOnce: Bool = false | |
self.map { $0.dataLoadState } | |
.filter { value in | |
defer { | |
if value.success { | |
hasSuccededOnce = true | |
} | |
} | |
return !hasSuccededOnce || value.success | |
} | |
.startWithSignal { signal, signalDisposable in | |
compositeDisposable += signalDisposable | |
signal.observe(observer) | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import ReactiveCocoa | |
extension SignalProducerType { | |
@warn_unused_result(message="Did you forget to call `start` on the producer?") | |
public func startWithValue(value: Value) -> SignalProducer<Self.Value, Self.Error> { | |
return SignalProducer(value: value).concat(self.producer) | |
} | |
@warn_unused_result(message="Did you forget to call `start` on the producer?") | |
public func ignoreErrors(replacementValue replacementValue: Self.Value? = nil, andDo block: (Self.Error -> ())? = nil) -> SignalProducer<Self.Value, NoError> { | |
return self.flatMapError { error in | |
log.debug("Ignoring error: \(error)") | |
block?(error) | |
return replacementValue.map(SignalProducer.init) ?? .empty | |
} | |
} | |
@warn_unused_result(message="Did you forget to call `start` on the producer?") | |
public func redirectErrorsToObserver(observer: Observer<Self.Error, NoError>, replacementValue: Self.Value? = nil) -> SignalProducer<Self.Value, NoError> { | |
return self.flatMapError { error in | |
observer.sendNext(error) | |
if let value = replacementValue { | |
return SignalProducer(value: value) | |
} | |
else { | |
return SignalProducer.empty | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
final class SampleViewModel { | |
typealias ElementsDataLoadState = DataLoadState<(), [Element]> | |
struct SampleViewData { | |
let elements: ElementsDataLoadState | |
} | |
let viewData: AnyProperty<SampleViewData> | |
private let viewDataMutableProperty = MutableProperty<SampleViewData>(SampleViewData(elements: .loadingData())) | |
let errors: Signal<FabricAPIError, NoError> | |
private let errorsObserver: Observer<FabricAPIError, NoError> | |
init(api: AuthenticatedFabricAPI) { | |
(self.errors, self.errorsObserver) = Signal<FabricAPIError, NoError>.pipe() | |
self.viewDataMutableProperty <~ api.requestElements() | |
.materializeToLoadState(redirectErrorsToObserver: self.errorsObserver) | |
.map(SampleViewData.init) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment