The local Xcoders has begun running this as a way to let those without a 60+ minute presentation bring new ideas into the local developer community. I think it's great and if your developer meetup is not using them, then give them a try.
--
Adventures
in API Design
My Point
NSError's userInfo
dictionary is the right way to return errors from your API. Let me elaborate.
Designing an API
Everything is a
trade off. When designing your API there are multiple things to consider:
- How much of the internal API should I expose?
- Should I provide logging?
- How long is too long for a method name?
- How should I return errors?
Let's focus on errors. Those are easy, right?
Throw an Exception?
We have several
options available and if you are coming to Cocoa (Objective-C) programming from other
languages then you might think using NSException to throw exceptions is an
option, right?
Wrong!
Wrong!
Apple has made it
clear through it's use of NSError that exception throwing is not the answer.
Exceptions are things you should fix/not handle in code and continue on.
With Swift they have
now codified this desire. Swift has done away with the ability to catch
exceptions.
Ways of Using NSError
Ok, so exceptions
are definitely not the way. So how can we pass errors back to our users? And how can we do that if we want to provide more info than NSError provides?
A quick review of
some popular 3rd Party APIs shows that we have several options for error handling:
- Subclass NSError.
- Use Categories to extend NSError.
- Use NSError and it's userInfo NSDictionary.
Option 1: Subclassing
This Sounds like a
great idea if you come from other OO languages. But with Objective-C we run
into a corners that can catch us up.
Here are some problems:
- Friction.
- Devs need to handle NSError and YourNSError.
- Crashes
- Crashes make 3rd party dev mad!
- Why crashes? If you have an API call that says it returns YourNSError but you return NSError thinking no problem (or by mistake) then what happens the the 3rd Party Dev calls one of your new methods that you added to YourNSError? Their app will crash since the selector (method) is not available on the NSError you actually returned.
- Slowed code
- After a crash due the scenario listed above the 3rd Party Dev must use reflection using NSObject's respondsToSelector: to verify that your returned error objects contain the listed methods. Reflection is slow, why add the overhead to a 3rd Party Devs App?
Option 2: Add a Category
Ok, so subclassing
is perhaps not the answer but what about just adding a Category to NSError and
adding your new properties and methods to this category?
First, if you do not
know that you can add properties to an existing class using a category, then
look up Associated Objects. I don't recommend you try this. You warp the very
foundations of the classes you are using and cause more harm than good in most
cases.
But, given that some
popular APIs use just this method. What are the pros to extending NSError like
this?
- Less Up-Front Friction for Devs
- No messy switching between a subclass and NSError.
What are the cons?
- Crashes
- Ok, so you decided to add some new properties using the magic of the Objective-C runtime. You learned about Associated Objects ability to extend a class with a property and found a "back door" around subclassing.
- The thing about adding a property through object association is that if the API does not set the property then it has not been associated with the object. Again, we have a missing selector and we have caused a crash.
- Dev no like crash!
- Slower code
- We are back to having to use reflection (respondsToSelector:) to verify your error objects support the properties you listed. Boo!!
Option 3: It's So Not OO
A common complaint I hear about Apple APIs is the reliance on NSDictionary. NSError is one of those culprits with it's userInfo. It does not feel OO so many poo-poo it as an aberration.
So what? It's how
it's done and it has good reason. So why use a pure NSError and pass back
additional details in userInfo?
- Less Friction
- No learning a new NSError class/category.
- No Reflection = Faster Code
- No reflection needed to validate your error objects support your listed properties or methods.
- Reduced Crash Vector
- If the user makes a call to [userInfo objectForKey:] on a non-existing key, then instead of a crash they will receive nil which in Objective-C is a no-op (meaning no crash). This is unlike a call to a non-existing property/method (when misusing a Category or subclass) which will crash with an NSInvalidArgumentException with "unrecognized selector sent to instance".
- For the high runner case you don't need more than NSErrors (domain, code, and localizedDescription).
- For the low runner cases you can extend NSError with additional details in the userInfo dictionary by adding new keys, publishing them in your API docs, and then let the app developer decide if they want that added detail.
- If the dev doesn't really care, they can just print the entire userInfo and get all the details of your error.
Avoid friction.
Don't slow down code.
Avoid skip crashes.
The right way to expose your API errors is with NSError.
Don't slow down code.
Avoid skip crashes.
The right way to expose your API errors is with NSError.
References
For further reading, start here:
No comments:
Post a Comment