Swift: Should NSError now be considered as legacy? - ios

The docs and popular blogs suggest Swift error handling be done with do-catch and to handle an ErrorType enum or an NSError instance.
Are ErrorType enum and NSError instances mutually exclusive in a try catch block? If not, how do you implement a function that throws both?
I have associated an NSError instance to an enum like so, which seems to work, but is this the de facto way of returning detailed error information?
enum Length : ErrorType {
case NotLongEnough(NSError)
case TooLong(NSError)
}
func myFunction() throws {
throw Length.NotLongEnough(NSError(domain: "domain", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey: "Not long enough mate"]))
}
do {
try myFunction()
} catch Length.NotLongEnough(let error) {
print("\(error)")
}
This example shows how ErrorType can be cast to NSError.
do {
let str = try NSString(contentsOfFile: "Foo.bar",
encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
print(error.localizedDescription)
}
I can't find an error enum that conforms to ErrorType for NSString so should we assume it will be an NSError instance ? Granted we could run the code to be sure, but surely the docs should let us know. (I appreciate I might have mis-read the docs)

Any ErrorType can be successfully cast to an NSError, which means that if you want to handle ErrorType as a priority, or specific ErrorType conformant Swift types, you should have those cases before a case where you cast to NSError (which, again, will succeed for all ErrorType conform ant types).
I'm not sure if there are any canonical opinions expressed by Apple at this stage on how to deal with this duality right now, but personally I try to stick to ErrorType conforming Swift types for my own errors and only involve casting to NSError when I want to make conditional behaviour based on some Cocoa or 3rd party NSError based code where the domain & code combo is somehow meaningful to my logic (for instance if specific errors can be filtered out from either logging or from responses).
Problems with the existing facilities in Swift for error handling arise mostly from not being able to tell what kind of errors are thrown by a method, combined with ErrorType lacking some key contextual information included in NSError that gives extra bespoke work every time you want to present ErrorTypes in UI to the user somehow. Unlike NSError, ErrorType does not have a way to pass UI intended information such as a "reason", or "recovery suggestion", or indeed a "description". It looks like this also will not get addressed yet for Swift 3 (there has been some recent discussion on this on the Swift development mailing list).
Regarding "I can't find an error enum that conforms to ErrorType for NSString" I'm really not sure I understood this phrase correctly or whether it is worded correctly overall, but maybe the following is relevant:
I personally follow the convention of making my ErrorType implementations CustomStringConvertible and using the description property to describe a human (UI intended) readable description of the error. This is by no means perfect and especially on OSX where NSResponder nicely enough gives you the presentError method which makes a very nice and clear error dialog if you fill in the description & recovery suggestion info.

NSError class adopts ErrorType interface and any ErrorType-conformant class can be casted to NSError. These features are described here in the docs.
You can safely stick to ErrorType, especially if you're planning to interoperate with Swift only.
enum CommonError: ErrorType {
case InternalInconsistency(String)
}
func boom() throws {
throw CommonError.InternalInconsistency("Boom!")
}
do {
try boom()
} catch {
print(error) // InternalInconsistency("Boom!")
print(error as NSError) // Error Domain=CommonError Code=0 "(null)"
}
do {
try boom()
} catch let CommonError.InternalInconsistency(msg) {
print("Error: \(msg)") // Error: Boom!
}

Related

Returning void in PromiseKit 6

This is what I had working with PromiseKit 4.5
api.getUserFirstName().then { name -> Void in
print(name)
}
getUserFirstName() returns a Promsise<String>. I updated to PromiseKit 6 and this now throws an error:
Cannot convert value of type '(_) -> Void' to expected argument type '(_) -> _'
This error message makes little sense to me. How do I fix this?
EDIT: So this seems to fix it, but I have little understanding as to what's happening with this:
api.getUserFirstName().compactMap { name in
print(name)
}
What's the difference now between then() and compactMap()?
In according with PromiseKit 6.0 Guide then was split into then, done and map
then is fed the previous promise value and requires you return a promise.
doneis fed the previous promise value and returns a Void promise (which is 80% of chain usage)
map is fed the previous promise value and requires you return a non-promise, ie. a value.
Why that was happend? As said developers:
With PromiseKit our then did multiple things, and we relied on Swift to infer the correct then from context. However with multiple line thens it would fail to do this, and instead of telling you that the situation was ambiguous it would invent some other error. Often the dreaded cannot convert T to AnyPromise. We have a troubleshooting guide to combat this but I believe in tools that just work, and when you spend 4 years waiting for Swift to fix the issue and Swift doesn’t fix the issue, what do you do? We chose to find a solution at the higher level.
So probably in your case needs to use done
func stackOverflowExample() {
self.getUserFirstName().done { name -> Void in
print(name)
}
}
func getUserFirstName() -> Promise<String> {
return .value("My User")
}
compactMap lets you get error transmission when nil is returned.
firstly {
URLSession.shared.dataTask(.promise, with: url)
}.compactMap {
try JSONDecoder().decode(Foo.self, with: $0.data)
}.done {
//…
}.catch {
// though probably you should return without the `catch`
}
See more info at release guide
compactMap was renamed to flatMap see discussions here

Swift model class error handling

I have come with below approach for Model class initialisation with error handling. But whenever make init method as throwable in swift, I am not able to access from Obj C code. Declaration doesn't get create in projectname-Swift.h header file. Without "throws" it works perfect.
init?(dictionary: NSDictionary?)throws {
if let dictionary = dictionary {
// Parsing
}
else {
throw MyError.DictionaryNil("Nil Dictionary")
}
}
Am I missing anything here? My model class is subclass of NSObject and it has only one init method.
Well nullable & throwing isn't supported. You can remove ? and it will give you an error parameter.
Also you can't overload a throwing init as failable.
So it leaves you with the one with error parameter. Maybe you can call that from a convenience initializer in objective-c to send a nil error parameter.
If you'll want to use this with Objective C, I think you'll have to resort to the lowest common denominator in error handling: an NSError parameter.

Swift - Error propagation while maintaining api clarity

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:
Generic parameters in functions
Any enum can conform to and implement it
Works better with the catch/throw paradigm
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:
Fetch the relevant groups from your server.
Fetch the already persisted groups on disk (Realm, CoreData, etc)
Update the local copy with the remote copy just fetched.
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.
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> {
...
}

Swift Error Handling - Struct/Class vs Enum

I am trying to model the possible errors that can be returned by the API I'm consuming and also possible network errors.
I'm struggling to figure out if I should use the Struct/Class approach, where each type of error is modeled as a Struct/Class, or the Enum approach.
Here are some notes to take into consideration:
Depending on what goes wrong, the same API call can return different errors with different media-types (so different JSON). So my errors have different properties. I don't want to expose the request response as a simple JSON Object / Dictionary.
I want the errors caused by a Client/Server issue to have a httpStatusCode property as well (it will be nil for errors that are related to no connectivity)
I want to have an error that indicates a connectivity issue with a proper localized message.
I want to use the same errors for both sync calls (used with throws) and async calls (used as a parameter in the completion block)
Mainly because of the first requirement it's pretty hard to figure out how to go down the Enum way. But maybe I'm missing something, maybe there still is some power that lays in the Enum types that I am not aware of.
Any suggestions would be highly appreciated.
Often enum is the best approach to deal with errors on Swift. To deliver different types you can use associated values and for a localized description you can implement LocalizedError protocol for your enum e.g:
enum NetworkError : Error, LocalizedError {
case noInternet
case httpStatus(Int)
case unknown(Error)
public var errorDescription: String? {
switch self {
case .noInternet:
return "No Internet"
case .httpStatus(let code):
return "HTTP status code: \(code)"
case .unknown(let error):
return "Error: \(error)"
}
}
}
let err1 = NetworkError.noInternet
print(err1.localizedDescription)
let err2 = NetworkError.httpStatus(404)
print(err2.localizedDescription)
let err3 = NetworkError.unknown(error)
print(err3.localizedDescription)
/*
Prints:
No Internet
HTTP status code: 404
Error: error
*/
But if you have common data for your errors you should implement struct that holds needed properties with a type of your error:
struct ResponseError: Error {
enum ErrorType {
case badStatusCode
case noJson
case invalidJson(Error)
}
let type: ErrorType
let statusCode: Int
}
let err = ResponseError(type: .badStatusCode, statusCode: 404)
Read more from the documentation about: https://developer.apple.com/documentation/swift/error
Depending on what your end goal is (a little unclear to me), you can do combinations of Struct/Class, and Enum.
E.g. you could create a Struct that holds your information, and then create an Enum which then has a value of that Struct. Plus you can create functions in your Enum, if needed. There's a lot of functionality in Enums in Swift.
If you would like, you can also make a specific Enum value dependent on another Enum, or even change which Struct/Class each value is dependent on.
For more information on Enums look at apples documentation.

Understanding Syntax issues about Parse latest SDK / Swift 1.2

Why does the 2nd snippet work and the first not work?
This code does not work:
func logIn() {
PFUser.logInWithUsernameInBackground(tv_username.text, password:tv_password.text) {
(user: PFUser!, error: NSError!) -> Void in
if user != nil {
// Yes, User Exists
//self.loginInitialLabel.text = "User Exists"
} else {
// No, User Doesn't Exist
}
}
}
This code works:
func logIn() {
PFUser.logInWithUsernameInBackground(tv_username.text, password:tv_password.text) {
(user, error) -> Void in
if user != nil {
// Yes, User Exists
//self.loginInitialLabel.text = "User Exists"
} else {
// No, User Doesn't Exist
}
}
}
Below is the error message. I am looking for a clear explanation of why some online docs have hte first example but only the 2nd one works. Did Parse change their SDK without changing documentation or is this some artifact of Swift 1.2 change? I am using XCode 6.3 and Swift 1.2.
Zoom of the error message:
In the first example, you specify the types of user and error explicitly (PFUser! and NSError!) respectively.
In the second example, you permit the type of user and error to be supplied implicitly.
Thus, the fact that the first example gives a compile error must mean that your explicit types are no longer correct. It could be the exclamation marks; try removing them.
The real way to figure out what types they are, though, is to use the second example, compile it, and then to put the cursor inside user and then inside error and read off the types from Quick Help on the right side of the Xcode window, as I do here:
That little trick has solved many Swift type mysteries for me!

Resources