Error Handling - Async Call - ios

I am creating a framework for web services used in my project. I have uploaded template in GitHub. https://github.com/vivinjeganathan/ErrorHandling
It has various layers. Layer 1 for validation. Layer 2 for formation of request. Layer 3 for the actual network call.
View Controller <----> Layer 1 <---> Layer 2 <---> Layer 3
Data flows between layers through closures, if error happens at any layer it needs to be gracefully passed to the ViewController.
I have referred to this link for error handling in async calls - http://appventure.me/2015/06/19/swift-try-catch-asynchronous-closures/
Created a branch in the same repo - name - ErrorHandling-Method1.
I was able to transfer error from layer 3 to layer 2(Single Level - Returning response through functions in closures - as mentioned in the link). But face difficulties in transferring back across multi layers.
Can anyone assist with the sample application provided in public GitHub?

First of all, I don't think it's necessary to stack the layers the way you did, for example, by adding the validation functionality as a layer you are increasing coupling making that layer dependant of the layers below (parsing, networking, etc.), instead, why don't you separate validation to make it only dependant of the data?:
class ViewController: UIViewController {
var validator = InputValidator()
override func viewDidLoad() {
super.viewDidLoad()
do {
try validator.validateInput("INPUT")
try Fetcher.requestDataWithParams("INPUT")
}
catch {
handleError(error)
}
}
}
Now the validation functionality is not dependant of the other layers, so communication would flow like this:
View Controller <---> ParsingLayer <---> NetworkingLayer
I did rename the layers but they are not necessarily have to be like this, you can add or remove layers.
I think is going to be kind of complicated if I try to explain my approach, so I'm going to give an example using the previous layers, first the bottom layer:
class NetworkingLayer {
class func requestData(params: AnyObject, completion: (getResult: () throw -> AnyObject) -> Void) -> Void {
session.dataTaskWithURL(url) { (data, urlResponse, var error) in
if let error = error {
completion(getResult: { throw error })
} else {
completion(getResult: { return data })
}
}
}
}
I have omitted some sections of code, but the idea is to do any necessary step to make the layer work (create session, etc.) and to always communicate back through the completion closure; a layer on top would look like this:
class ParsingLayer {
class func requestObject(params: AnyObject, completion: (getObject: () throw -> CustomObject) -> Void) -> Void {
NetworkingLayer.requestData(params, completion: { (getResult) -> Void in
do {
let data = try getResult()
let object = try self.parseData(data)
completion(getObject: { return object })
}
catch {
completion(getObject: { throw error })
}
})
}
}
Notice that the completion closures are not the same, since every layer adds functionality, the returned object can change, also notice that the code inside the do statement can fail in two ways, first if the network call fails and then if the data from the networking layer cannot be parsed; again the communication to the layer on top is always done through the completion closure.
Finally the ViewController can call the next layer using the closure expected by the Parsing layer in this case, and is able to handle errors originated in any layer:
override func viewDidLoad() {
super.viewDidLoad()
do {
try validator.validateInput("INPUT")
try ParsingLayer.requestObject("INPUT", completion: { (getObject) in
do {
let object = try getObject()
try self.validator.validateOutput(object)
print(object)
}
catch {
self.handleError(error)
}
})
catch {
handleError(error)
}
}
Notice that there is a do catch inside the completion closure, this is necessary since the call is made asynchronously, now that the response has gone through all the layers and have actually change to be of a more specialised type you can even validate the result without having the necessity to make a layer for the validation functionality.
Hope it helps.

Personally I would use notifications passing the NSError as the object of the notification in the layers and observe the notification in the view controller.
In the layers:
NSNotificationCenter.defaultCenter().postNotificationName("ErrorEncounteredNotification", object: error)
In the view controller
NSNotificationCenter.defaultCenter().addObserver(self, selector: "errorEncountered:", name: "ErrorEncounteredNotification", object: nil)
the selector method:
func errorEncountered(notification: NSNotification!) {
let error: NSError! = notification.object as! NSError
NSLog("error: \(error)")
}

You correctly identified a nasty problem with error handling in asynchronous code.
It seems to be easy with synchronous functions - which just return an error code, or have an extra error parameter, or use the new Swift throws syntax. Here is an synchronous function:
func computeSome() throws -> Some
And this is a viable function signature for an asynchronous function:
func computeSomeAsync(completion: (Some?, NSError?) -> ())
The asynchronous function returns Void and does not throw. If it fails, it calls its completion function with the error parameter set.
However, completion handlers become quickly cumbersome, especially in nested code.
The solution is to use a Future:
func computeSomeAsync() -> Future<Some>
This function is asynchronous and does not throw - and returns a Future. So, what's a future?
Well a future represents the eventual result of an asynchronous function. When you call the asynchronous function, it immediately returns and you get a placeholder for the result. This, called a future, will be eventually completed by the underlying background task that computes the value.
When the underlying task eventually succeeded, this future contains the computed value of the function. When it failed it will contain the error.
Depending on the actual implementation and the API of a Future Library, you can obtain the result by registering continuations:
let future = computeSomeAsync()
future.onSuccess { value in
print("Value: \(value)")
}
future.onFailure { error in
print("Error: \(error)")
}
It may look weird at first, but you can do awesome things with futures:
fetchUser(id).flatMap { user in
fetchProfileImage(user.profileImageUrl).flatMap { image in
cacheImage(image)
}
}
.onFailure { error in
print("Something went wrong: \(error)")
}
The above statement is asynchronous - as well as function fetchUser, fetchProfileImage and cacheImage. Error handling included.

Why declare your method throws if you never throw or even try to catch? You could throw the errors using the throwable declaration through all the layers, and even change the throwable type at each level.
UPDATE: Didnt think of throwing dont work in async operations. Using NSNotification is one good route, or you could take a look at RXSwift or similar to solve it too. My personal recommendation would be to use RxSwift. This keeps you out of callback hell, which you are currently travelling down into.

Related

What is the appropriate strategy for using #MainActor to update UI?

Suppose you have a method that executes asynchronously in a global context. Depending on the execution you need to update the UI.
private func fetchUser() async {
do {
let user = try await authService.fetchCurrentUser()
view.setUser(user)
} catch {
if let error = error {
view.showError(message: error.message)
}
}
}
Where is the correct place to switch to the main thread?
Assign #MainActor to the fetchUser() method:
#MainActor
private func fetchUser() async {
...
}
Assign #MainActor to the setUser(_ user: User) and showError(message: String) view's methods:
class SomePresenter {
private func fetchUser() async {
do {
let user = try await authService.fetchCurrentUser()
await view.setUser(user)
} catch {
if let error = error {
await view.showError(message: error.message)
}
}
}
}
class SomeViewController: UIViewController {
#MainActor
func setUser(_ user: User) {
...
}
#MainActor
func showError(message: String) {
...
}
}
Do not assign #MainActor. Use await MainActor.run or Task with #MainActor instead to run setUser(_ user: User) and showError(message: String) on the main thread (like DispatchQueue.main.async):
private func fetchUser() async {
do {
let user = try await authService.fetchCurrentUser()
await MainActor.run {
view.setUser(user)
}
} catch {
if let error = error {
await MainActor.run {
view.showError(message: error.message)
}
}
}
}
Option 2 is logical, as you are letting functions that must run on the main queue, declare themselves as such. Then the compiler can warn you if you incorrectly call them. Even simpler, you can declare the class that has these functions to be #MainActor, itself, and then you don't have to declare the individual functions as such. E.g., because a well-designed view or view controller limits itself to just view-related code, it is safe for that whole class to be declared as #MainActor and be done with it.
Option 3 (in lieu of option 2) is brittle, requiring the app developer to have to remember to manually run them on the main actor. You lose compile-time warnings should you fail to do the right thing. Compile-time warnings are always good. But WWDC 2021 video Swift concurrency: Update a sample app points out that even if you adopt option 2, you might still use MainActor.run if you need to call a series of MainActor methods and you might not want to incur the overhead of awaiting one call after another, but rather wrap the group of main actor functions in a single MainActor.run block. (But you might still consider doing this in conjunction with option 2, not in lieu of it.)
In the abstract, option 1 is arguably a bit heavy-handed, designating a function that does not necessarily have to run on the main actor to do so. You should only use the main actor where it is explicitly needed/desired. That having been said, in practice, I have found that there is often utility in having presenters (or controllers or view models or whatever pattern you adopt) run on the main actor, too. This is especially true if you have, for example, synchronous UITableViewDataSource or UICollectionViewDataSource methods grabbing model data from the presenter. If you have the relevant presenter using a different actor, you cannot always return to the data source synchronously. So you might have your presenter methods running on the main actor, too. Again, this is best considered in conjunction with option 2, not in lieu of it.
So, in short, option 2 is prudent, but is often married with options 1 and 3 as appropriate. Routines that must run on the main actor should be designated as such, rather than placing that burden on the caller.
The aforementioned Swift concurrency: Update a sample app covers many of these practical considerations and is worth watching if you have not already.

RXSwift Not subscribing on Main Thread

I am trying to make several API calls and populate a Realm Database.
Everything works fine. However when I try to run performSegue() on subscribe() method an exception is raised, informing that I can't do this on a background thread, which is perfectly reasonable.
But since I am subscribing to MainScheduler.instance shouldn't the subscribe() method run on UI Thread?
Single.zip(APIClient.shared.getSchools(), APIClient.shared.getPointsOfInterest())
.observeOn(SerialDispatchQueueScheduler(qos: .background))
.flatMap { zip in return Single.zip(SchoolDao.shared.insertSchools(schoolsJson: zip.0), PointOfInterestDao.shared.insertPointsOfInterest(poisJson: zip.1))}
.flatMap{ _ in Single.zip(SchoolDao.shared.countSchools(), PointOfInterestDao.shared.countPointsOfInterest())}
.subscribeOn(MainScheduler.instance)
.subscribe(onSuccess: { tableCounts in
let (schoolsCount, poisCount) = tableCounts
if(schoolsCount != 0 && poisCount != 0){
print(Thread.isMainThread) //Prints False
self.performSegue(withIdentifier: "splashToLogin", sender: nil)
}
}, onError: {
error in return
}).disposed(by: disposeBag)
Am I making a wrong assumption on how does RXSwift works?
Edit: If I add this line .observeOn(MainScheduler.instance) after .subscribeOn(MainScheduler.instance) the subscribe method runs on Main thread. Is this correct behavior? What is .subscribeOn(MainScheduler.instance) even doing?
Your edit explains all. Your initial assumption on what subscribeOn and observeOn were backwards.
The subscribeOn operator refers to how the observable above the operator in the chain subscribes to the source of events (and likely doesn't do what you think it does in any case. Your two network calls likely set up their own background thread to emit values on regardless of how they are subscribed to.)
For example, look at this:
extension ObservableType {
func subscribeOnMain() -> Observable<Element> {
Observable.create { observer in
let disposable = SingleAssignmentDisposable()
DispatchQueue.main.async {
disposable.setDisposable(self.subscribe(observer))
}
return disposable
}
}
}
It makes it obvious why the operator is called subscribeOn. It's because the subscribe is happening on the scheduler/thread in question. And this helps you understand better what is happening when you stack subscribeOn operators...
The observeOn operator refers to the scheduler that will be emitting elements to the observer (which is the block(s) of code that are passed to the subscribe operator.)
Which would look like this:
extension ObservableType {
func observeOnMain() -> Observable<Element> {
Observable.create { observer in
self.subscribe { event in
DispatchQueue.main.async {
observer.on(event)
}
}
}
}
}
From this you can see that the subscribe is happening on the original scheduler, while the observer is being called on the new scheduler.
Here is a great article explaining the whole thing: http://rx-marin.com/post/observeon-vs-subscribeon/

How can I transform a signal with errors into a NoError one with ReactiveSwift? (and be elegant)

What is the most elegant way to transform my ReactiveSwift's SignalProducer<A, NetworkError> into a Signal<A, NoError>?
Most of the time, my signal producer is the result of a network call, so I want to split the results into two cases:
if a value is available, send a Signal<A, NoError>
if an error happened, send a Signal<String, NoError> with the error's localized description
(why? because i'm trying to be as MVVM as possible)
So far, I end up writing a lot of boilerplate like the following:
let resultsProperty = MutableProperty<SearchResults?>(nil)
let alertMessageProperty = MutableProperty<String?>(nil)
let results = resultsProperty.signal // `Signal<SearchResults?, NoError>`
let alertMessage = alertMessageProperty.signal // `Signal<String?, NoError>`
// ...
searchStrings.flatMap(.latest) { string -> SignalProducer<SearchResults, NetworkError> in
return MyService.search(string)
}
.observe { event in
switch event {
case let .value(results):
resultsProperty.value = results
case let .failed(error):
alertMessageProperty.value = error
case .completed, .interrupted:
break
}
}
ie:
using MutableProperty instances, that I have to set as optional to be able to initialize them
creating signals from those, ie getting a signal sending optionals as well
it feels dirty and makes the code so intertwined it kind of ruins the point of being reactive
Any help on (A) keeping my signals non optional and (B) splitting them into 2 NoError signals elegantly would be greatly appreciated.
Edit - Second Attempt
I will try to answer all your questions / comments here.
The errors = part doesn't work as flatMapError expects a SignalProducer (ie your sample code works just because searchStrings is a Signal string, which coincidently is the same as the one we want for errors: it does not work for any other kind of input)
You are correct, this is because flatMapError does not change the value type. (Its signature is func flatMapError<F>(_ transform: #escaping (Error) -> SignalProducer<Value, F>) -> SignalProducer<Value, F>). You could add another call to map after this if you need to change it into another value type.
the results = part behaves weirdly as it terminates the signal as soon as an error is met (which is a behavior I don't want) in my real-life scenario
Yes, this is because the flatMap(.latest) forwards all errors to the outer signal, and any error on the outer signal will terminate it.
Okay so here's an updated version of the code, with the extra requirements that
errors should have different type than searchStrings, let's say Int
Any error from MyService.search($0) will not terminate the flow
I think the easiest way to tackle both these issues is with the use of materialize(). What it does is basically "wrap" all signal events (new value, error, termination) into a Event object, and then forward this object in the signal. So it will transform a signal of type Signal<A, Error> into a Signal<Event<A, Error>, NoError> (you can see that the returned signal does not have an error anymore, since it is wrapped in the Event).
What it means in our case is that you can use that to easily prevent signals from terminating after emitting errors. If the error is wrapped inside an Event, then it will not automatically terminate the signal who sends it. (Actually, only the signal calling materialize() completes, but we will wrap it inside the flatMap so the outer one should not complete.)
Here's how it looks like:
// Again, I assume this is what you get from the user
let searchStrings: Signal<String, NoError>
// Keep your flatMap
let searchResults = searchStrings.flatMap(.latest) {
// Except this time, we wrap the events with `materialize()`
return MyService.search($0).materialize()
}
// Now Since `searchResults` is already `NoError` you can simply
// use `filterMap` to filter out the events that are not `.value`
results = searchResults.filterMap { (event) in
// `event.value` will return `nil` for all `Event`
// except `.value(T)` where it returns the wrapped value
return event.value
}
// Same thing for errors
errors = searchResults.filterMap { (event) in
// `event.error` will return `nil` for all `Event`
// except `.failure(Error)` where it returns the wrapped error
// Here I use `underestimatedCount` to have a mapping to Int
return event.error?.map { (error) in
// Whatever your error mapping is, you can return any type here
error.localizedDescription.characters.count
}
}
Let me know if that helps! I actually think it looks better than the first attempt :)
First Attempt
Do you need to access the state of you viewModel or are you trying to go full state-less? If state-less, you don't need any properties, and you can just do
// I assume this is what you get from the user
let searchStrings: Signal<String, NoError>
// Keep your flatMap
let searchResults = searchStrings.flatMap(.latest) {
return MyService.search($0)
}
// Use flatMapError to remove the error for the values
results = searchResults.flatMapError { .empty }
// Use flatMap to remove the values and keep the errors
errors = searchResults.filter { true }.flatMapError { (error) in
// Whatever you mapping from error to string is, put it inside
// a SignalProducer(value:)
return SignalProducer(value: error.localizedDescription)
}

ReactiveX RxSwift get first non error from concat of observables

I am using RxSwift for caching in my iOS app and have a piece of code like this:
let observable = Observable.of(cache.getItem(itemID), network.getItem(itemID)).concat().take(1)
observable.subscribeNext // and do some stuff
I have the cache.getItem method doing an onError if it has no value, and would like it to then defer to the network, but for some reason the network is never run. I assume its because I am using the take(1), but I would like the observable to stop emitting once the cache finds something (or continue to the network if it does not).
Any ideas on how to do this?
I've been following this guide but he does not go into detail about his cache's behavior when it fails to find something.
You shouldn't be using .Error like that. That's not really conceptually an error case. There's just nothing in the cache. That's a common situation. Nothing went "wrong" out of the ordinary. Instead, just send a .Completed event.
As for why your code isn't working, it's because an error coming from an Observable included in the concat will become an error on the final concat Observable. The thing to remember with Rx is that once there's a .Completed event or (in your case) an .Error event, that's it, it's over, no more .Next events (or any events).
So instead, if you use .Completed, your code would work as so:
class Cache {
func getItem(itemID: Int) -> Observable<Item> {
return Observable<Item>.create { observer in
// if not found...
observer.onCompleted() // you would of course really try to get it
// from the cache first.
return NopDisposable.instance
}
}
}
class Network {
func getItemN(itemID: Int) -> Observable<Item> {
return Observable<Item>.create { observer in
// get some `item` from the network and then..
observer.onNext(item)
return NopDisposable.instance
}
}
}
let observable = Observable.of(cache.getItem(itemID), network.getItem(itemID)).concat().take(1)
observable.subscribeNext { item in
print(item)
}

Concern about memory when choosing between notification vs callback closure for network calls?

Many posts seem to advise against notifications when trying to synchronize functions, but there are also other posts which caution against closure callbacks because of the potential to inadvertently retain objects and cause memory issues.
Assume inside a custom view controller is a function, foo, that uses the Bar class to get data from the server.
class CustomViewController : UIViewController {
function foo() {
// Do other stuff
// Use Bar to get data from server
Bar.getServerData()
}
}
Option 1: Define getServerData to accept a callback. Define the callback as a closure inside CustomViewController.
Option 2: Use NSNotifications instead of a callback. Inside of getServerData, post a NSNotification when the server returns data, and ensure CustomViewController is registered for the notification.
Option 1 seems desirable for all the reasons people caution against NSNotification (e.g., compiler checks, traceability), but doesn't using a callback create a potential issue where CustomViewController is unnecessarily retained and therefore potentially creating memory issues?
If so, is the right way to mitigate the risk by using a callback, but not using a closure? In other words, define a function inside CustomViewController with a signature matching the getServerData callback, and pass the pointer to this function to getServerData?
I'm always going with Option 1 you just need to remember of using [weak self] or whatever you need to 'weakify' in order to avoid memory problems.
Real world example:
filterRepository.getFiltersForType(filterType) { [weak self] (categories) in
guard let strongSelf = self, categories = categories else { return }
strongSelf.dataSource = categories
strongSelf.filteredDataSource = strongSelf.dataSource
strongSelf.tableView?.reloadData()
}
So in this example you can see that I pass reference to self to the completion closure, but as weak reference. Then I'm checking if the object still exists - if it wasn't released already, using guard statement and unwrapping weak value.
Definition of network call with completion closure:
class func getFiltersForType(type: FilterType, callback: ([FilterCategory]?) -> ()) {
connection.getFiltersCategories(type.id).response { (json, error) in
if let data = json {
callback(data.arrayValue.map { FilterCategory(attributes: $0) } )
} else {
callback(nil)
}
}
}
I'm standing for closures in that case. To avoid unnecessary retains you just need to ensure closure has proper capture list defined.

Resources