Transform Reactive Cocoa SignalProducer into enum - swift

I'm trying to transform my API SignalProducers into an enum so I get Loading, Failed and Loaded states. Enum is:
enum DataLoadState<DataType>{
case Loading
case Failed
case Loaded(DataType)
}
To do it I transform my SignalProducer to a NoError producer and start with loading state:
extension SignalProducerType{
func materializeToLoadState() -> SignalProducer<DataLoadState<Value>,NoError>{
let producer = self
.map(DataLoadState.Loaded)
.startWithValue(DataLoadState.Loading)
return producer.ignoreErrors(replacementValue:DataLoadState<Value>.Failed)
}
}
extension SignalProducerType {
func startWithValue(value:Value)->SignalProducer<Value,Error>{
return SignalProducer(value:value).concat(self.producer)
}
public func ignoreErrors(replacementValue replacementValue: Self.Value? = nil) -> SignalProducer<Self.Value, NoError> {
return self.flatMapError { error in
return replacementValue.map(SignalProducer.init) ?? .empty
}
}
}
It works but I don't want to ignore Errors, instead I want to include it in the failed case:
enum DataLoadState<DataType>{
case Loading
case Failed(APIError?)
case Loaded(DataType)
}
Any idea on how can I use flatMapError (like in ignoreError) so errors are fired as DataLoadState.Failed(error) instead of being ignored? When error isn't display error is could be just nil. Is it even possible?

Ok, fixed it just with:
producer.flatMapError { error in SignalProducer(value:.Failed(error as? APIError)) }

Related

Swift Combine framework setFailureType error operator

For scientific reasons I've created a Publisher and a Subscriber so I can dive into Combine.
The Publisher has been converted from a never failing to the failing one.
enum IntegerError: String, Error {
case miltupleOf2 = "We are sorry but the number is a multiple of 2, therefore cannot be used in the process"
}
let integerPublisher = [1,3,3,3,3,3,5,6,7,7].publisher
.setFailureType(to: IntegerError.self)
let subscribtion = integerPublisher
.tryMap { intValue in
if intValue.isMultiple(of: 2) {
throw IntegerError.miltupleOf2
} else {
return intValue
}
}
.sink { completion in
switch completion {
case .finished:
print("success")
case .failure(let error):
if let error = error as? IntegerError {
print(error.rawValue)
} else {
print(error)
}
}
} receiveValue: { value in
print(value)
}
My question is: when using sink, the error type is Error. Why is it not the custom IntegerError that I've used within the .setFailureType modifier?
The need of casting my error to the type that I specified earlier seems a little redundant.
Thank you.
The reason for this is quite straightforward. tryMap returns a Publishers.TryMap<Upstream, Output>, which is a specific kind of publisher with Failure == Error:
typealias Failure = Error
So as soon as you use tryMap, that undoes what setFailureType did.
The reason why Publishers.TryMap has Error as its Failure type is because in Swift, you can't specify what specific type of error a closure can throw (unlike Java for example). Once you mark a closure as throws, like tryMap has done with its transform parameter:
func tryMap<T>(_ transform: #escaping (Self.Output) throws -> T) -> Publishers.TryMap<Self, T>
any Error can be thrown inside the transform closure. You can try changing throw IntegerError.miltupleOf2 to throwing another type of error. Your code will still compile.
Hypothetically, if Swift allowed you to specify what type of error you are allowed to throw in the closure, then tryMap could have been declared as (fake syntax):
func tryMap<T, E: Error>(_ transform: #escaping (Self.Output) throws E -> T) -> Publishers.TryMap<Self, T, E>
and you wouldn't even need setFailureType.
As a workaround, you can use mapError to cast the error to your desired type:
let subscribtion = integerPublisher
.tryMap { intValue -> Int in
if intValue.isMultiple(of: 2) {
throw IntegerError.miltupleOf2
} else {
return intValue
}
}.mapError { $0 as! IntegerError }
.sink { completion in
switch completion {
case .finished:
print("success")
case .failure(let error):
print(error.rawValue)
}
} receiveValue: { value in
print(value)
}
You need to reassure the compiler that you haven't thrown any other type of errors in tryMap.

Variable 'theData' used before being initialized, How should I fix

I am trying to Apollo framework and a graphql api to obtain the data then return it. Once I have the data in another swift file, I want to call on certain parts of the data and assign it to a variable. The errors I get is variable used before it is initialized. and if try to return the variable from within the closure I get "Unexpected Non-Void Return Value In Void Function ". I heard of ways to get around that error but I don't completely understand it and how it works with my code. If you need more code or context you can message me and I can share my GitHub repo. Sorry if the code is bad, please don't roast me. I am still a beginner.
import Foundation
import Apollo
struct AniListAPI {
let aniListUrl = "https://graphql.anilist.co"
func ObtainData(AnimeID: Int)-> QueryQuery.Data{
var theData: QueryQuery.Data
let theInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: theInfo) { result in
switch result {
case .failure(let error):
print("A big No no happened \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
theData = Info
}
}
return theData
}
}
Unexpected Non-Void Return Value In Void Function.
The reason you're getting this warning is because you can't return value from inside the closure. Use closure instead of returning value.
func ObtainData(AnimeID: Int, completion: #escaping (Data) -> Void) {
var TheData: QueryQuery.Data
let TheInfo = QueryQuery(id: AnimeID)
GraphClient.fetch(query: TheInfo) { result in
switch result {
case .failure(let error):
print("A big no no happened retard \(error)")
case .success(let GraphQLResult):
guard let Info = GraphQLResult.data else {return}
TheData = Info
completion(TheData)
}
}
}
and call it like..
ObtainData(AnimeID: 123) { (anyData) in
print (anyData)
// continue your logic
}

How to avoid re-parsing Error when using enum that conforms to Error protocol in Swift

There's an error enum:
#objc public enum MyError: Int, Error {
case good
case bad
case ugly
}
Note that it's available to objective C - that's part of the challenge. For instance I cannot change enum to have associated values (like case good(Error))
A decent solution is to have a constructor that can parse from some arbitrary error:
init(from error: Error?) {
// parse it and set self to corresponding code, e.g.
guard let error = error else {
self = .good
return
}
if let uglyError = error as NSError? {
self = .ugly
return
}
self = .bad
}
But one of the cases this constructor has to handle is that provided error is already one of MyError values, i.e. Error(MyError.ugly) - this is simplification of course. More likely it's a result of lower-level error bubbling up.
In that case I want to avoid re-parsing it. How is that possible?
In pseudo-code I want to implement condition like this:
if error is [one of MyError values] {
self = error
return
}
Tried to do that with CaseIterable and allCases, but seems cannot convert Error to something that can be compared
for value in MyError.allCases {
if error == value { // <-- Error: Binary operator '==' cannot be applied to operands of type 'Error' and 'MyError'
}
}
Any way to accomplish something like that? (I am not stuck on CaseIterable, anything else works too).
If you are aiming to keep the same initializer as init(from error: Error?), what you could do is to add
if let castedError = error as? MyError {
self = castedError
return
}
at the beginning (before doing anything).
As a full implementation, it should be similar to:
#objc public enum MyError: Int, Error, CaseIterable {
case good
case bad
case ugly
init(from error: Error?) {
if let castedError = error as? MyError {
print("Already MyError!")
self = castedError
return
}
guard let error = error else {
self = .good
return
}
if let _ = error as NSError? {
self = .ugly
return
}
self = .bad
}
}
Therefore, the output would be:
let givenError: MyError = .good
let resultError = MyError(from: givenError)
print(resultError.rawValue) // 0 (which is .good raw value)
Note that it should also log "Already MyError!" because of the print statement in the first check to confirm that it has been reached.
It should also behave as expected with NSErrors:
let nsError = NSError(domain: "", code: 101, userInfo: nil)
let myError = MyError(from: nsError)
print(myError.rawValue) // 2 (which is .ugly raw value)

Specifying custom error type within the Result type in Swift 5

I am trying to create a Result variable with a custom error type with the builtin Result type in Foundation for Swift 5, but I can't quite get the type system to understand the kind of error I want to throw.
The code below does not compile.
import Foundation
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw CustomError.somethingBadHappened
} else {
return Model(value: number)
}
}
// compiler complains here about: Cannot convert value of type 'Result<Model, Error>' to expected argument type 'Result<Model, CustomError>'
completion(result)
}
}
let request = Request()
request.execute(number: 19) { result in
switch result {
case .success(let value): print("Succeded with \(value)")
case .failure(let error): print("Failed with \(error)")
}
}
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
How can I make the type system understand I would like to use the custom error type?
Apologies for giving a second answer, but there needs to be a corrective for fphilipe's answer.
You can use init(catching:) to form the Result and yet return it as a Result<Model, CustomError>. That is what mapError is for! Like this:
enum CustomError: String, Error {
case somethingBadHappened
}
struct Model {
let value: Int
}
class Request {
func execute(number: Int, completion: #escaping (Result<Model, CustomError>) -> Void) {
let result = Result { () throws -> Model in
if (number < 20) {
throw NSError()
} else {
return Model(value: number)
}
}.mapError { err in
return CustomError.somethingBadHappened
}
completion(result)
}
}
I have to throw something in order to form the initial Result<Model, Error>, so I just throw an NSError as a kind of placeholder. But then mapError comes along and transforms this into a Result<Model, CustomError>. The power of mapError is that it changes only what happens in case of failure.
Thus we are able to keep the original form of the code.
Changing the signature of the completion closure to completion: #escaping (Result<Model, Error>) -> Void works, but then I am not using the custom error type.
Yes, you are! Change the signature in exactly that way, so that you compile, and then run your code. When we get to this line:
case .failure(let error): print("Failed with \(error)")
... we print "Failed with somethingBadHappened". That proves that your CustomError.somethingBadHappened instance came through just fine.
If the problem is that you want to separate out your CustomError explicitly, then separate it out explicitly as you catch it:
case .failure(let error as CustomError): print(error)
default : fatalError("oops, got some other error")
Or if you want to winnow it down still further and catch only the .somethingBadHappened case, specify that:
case .failure(CustomError.somethingBadHappened): print("Something bad happened")
default : fatalError("oops, got some other error")
Those examples are artificial, but they demonstrate what they are intended to demonstrate — that your CustomError instance is coming through with full integrity.
Just create Result manually:
let result: Result<Model, CustomError>
if (number < 20) {
result = .failure(.somethingBadHappened)
} else {
result = .success(Model(value: number))
}
completion(result)

Error handling in Swift: Error from one enum to another

I have this code in a ServiceClient. It handles service-level calls, like signIn(user, password, completion), listObjects(completion), addObject(objectID, content, completion), getObject(id, completion) etc. It contains (but doesn't subclass) an APIClient, which performs only basic HTTPS services like perform(request, completion).
I don't really want the controller that sits above this to deal with 404s as success, which means trapping the error in ServiceClient. So the idea is APIClient deals with networking errors whereas ServiceClient deals with unexpected HTTP results.
So I end up with this in ServiceClient, where errors like invalidURL are converted from an APIClient enum to a ServiceClient enum:
apiClient.perform(request) {result in
switch result {
case .success(let data):
guard data.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
completion(.success(data))
case .failure(let error):
switch error {
case .invalidURL:
completion(.failure(.invalidURL))
case .requestFailed:
completion(.failure(.requestFailed))
case .decodingFailure:
completion(.failure(.decodingFailure))
}
}
}
I think in this case I'll just make APIClient handle invalid HTTP status codes, but what's the more general solution to this? At some point I'll want different error codes for different service clients, at which point this becomes a problem again.
I suggest using Int type enumeration for both ServiceClient and APIClient.
As I understood this is your custom enumerations.
So, assuming you have ServiceClientError and APIClientError you can implement them using this way:
enum ServiceClientError: Int {
case invalidURL, requestFailed, decodingFailure
}
enum APIClientError: Int {
case invalidURL, requestFailed, decodingFailure
}
You can create your custom conversion method:
extension ServiceClientError {
static func create(from apiClientError: APIClientError) -> ServiceClientError {
return ServiceClientError(rawValue: apiClientError.rawValue)
}
}
Wanted function:
apiClient.perform(request) {result in
switch result {
case .success(let data):
guard data.statusCode == 200 else {
completion(.failure(.badResponse))
return
}
completion(.success(data))
case .failure(let error):
guard let serviceClientError = ServiceClientError.create(from: error) else {
/// Handle incorrect behavior
fatalError("Wrong enumeration mapping")
return
}
completion(.failure(serviceClientError))
}
}