Reputation: 7173
With the addition of ErrorType
to Swift, it's now possible to express error and failure events in a cleaner, more concise manner. We're no longer bound as iOS developers to the old way of NSError
, clunky and hard to use.
ErrorType
is great for a few reasons:
There are however some problems and one problem in particular that I've been running into lately and I'm curious to see how others solve this.
Say for example, you're build a social networking application akin to Facebook and you have Group
model. When the user first loads your application you want to do two/three things:
Throughout this process, you can break the types of errors up into two distinct categories: PersistenceError
and NetworkError
where the ErrorType
conforming enums might look like
enum PersistenceError: ErrorType {
case CreateFailed([String: AnyObject)
case FetchFailed(NSPredicate?)
}
enum NetworkError: ErrorType {
case GetFailed(AnyObject.Type) // where AnyObject is your Group model class
}
There are several ways/design patterns you can use for delivering errors. The most common of course is try/catch.
func someFunc() throws {
throw .GetFailed(Group.self)
}
Here, because functions that throw can't yet specify what type of error they're throwing, although I suspect that will change, you can easily throw a NetworkError
or a PersistenceError
.
The trouble comes in when using a more generic or functional approach, such as ReactiveCocoa or Result.
func fetchGroupsFromRemote() -> SignalProducer<[Group], NetworkError> {
// fetching code here
}
func fetchGroupsFromLocal() -> SignalProducer<[Group], PersistenceError> {
// fetch from local
}
Then wrapping the two calls:
func fetch() -> SignalProducer<Group, ???> {
let remoteProducer = self.fetchGroupsFromRemote()
.flatMap(.Concat) ) { self.saveGroupsToLocal($0) // returns SignalProducer<[Group], PersistenceError> }
let localProducer = self.fetchGroupsFromLocal()
return SignalProducer(values: [localProducer, remoteProducer]).flatten(.Merge)
}
What error type goes in the spot marked ???
? Is it NetworkError
or PersistenceError
? You can't use ErrorType
because it can't be used as a concrete type and if you try using it as a generic constraint, <E: ErrorType>
, the compiler will still complain saying it expects an argument list of type E
.
So the issue becomes, less so with try/catch and more so with functional approaches, how to maintain an error hierarchy structure so that error information can be preserved throughout different ErrorType
conformances while still having that descriptive error api.
The best I can come up with so far is:
enum Error: ErrorType {
// Network Errors
case .GetFailed
// Persistence Errors
case .FetchFailed
// More error types
}
which is essentially one long error enum so that any and all errors are of the same type and even the deepest error can be propagated up the chain.
How do other people deal with this? I enjoy the benefits of having one universal error enum but the readability and api clarify suffer. I'd much rather have each function describe what specific error cluster they return rather than have each return Error
but again, I don't see how to do that without losing error information along the way.
Upvotes: 1
Views: 153
Reputation: 3052
Just trying a solution to your problem, may not be a perfect one. Using protocol to easily pass around the error objects :
//1.
protocol Error {
func errorDescription()
}
//2.
enum PersistenceError: ErrorType, Error {
case CreateFailed([String: AnyObject)
case FetchFailed(NSPredicate?)
func errorDescription() {
}
}
//3.
enum NetworkError: ErrorType, Error {
case GetFailed(AnyObject.Type) // where AnyObject is your Group model class
func errorDescription() {
}
}
//5.
func fetch() -> SignalProducer<Group, Error> {
...
}
Upvotes: 1