Thursday, February 12, 2015

I Ain't Afraid of No NSError: Adventures in API Design

These are the notes from my Lightning Talk at Xcoders Seattle on February 12, 2015. A Lightning Talk is a 5-10 minute presentation that gets to the heart of an idea, problem, solution, quibble, etc. They are popular at conferences (also called Blitz Talks) where multiple will be held over 60-90 minutes.

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!

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:
  1. Subclass NSError.
  2. Use Categories to extend NSError.
  3. 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.

References


For further reading, start here:

Wednesday, February 11, 2015

Adding a HC-06 (JY-MCU) Bluetooth Module To an Arduino or Spark Core Project

JY-MCU Bluetooth Breakout Board
I picked up a HC-06 (Part JY-MCU, ) from Vetco Electronics today to add a Bluetooth serial port to projects I am working on. I wanted to hook this into a Spark Core and so I wired it up and tried to pair with it.

No luck. I could not see the Bluetooth module from my phone or from my Mac. Why?

A little searching and I came across Erich Styger's post (Getting Bluetooth Working With JY-MCU BT Board v1.06)trying to figure out the same thing. He is way more advanced than me but he found both a hardware solution and a software solution. Since I did not want to modify the board (and I was sitting at Starbucks hooking this up) I went with the software solution.

The sort answer was, to get the Bluetooth module to start broadcasting for pairing I had to add the following to my Sketch (code for the app):

#include "PORT_PDD.h"

PORT_PDD_SetPinPullSelect(PORTC_BASE_PTR, 3, PORT_PDD_PULL_UP);
PORT_PDD_SetPinPullEnable(PORTC_BASE_PTR, 3, PORT_PDD_PULL_ENABLE);

Erich also has a great article on Using The HC-06 Bluetooth Module which I am digging through to understand my Bluetooth module a little better.

Sunday, February 8, 2015

Just Made It

Flying out of St. John's, Newfoundland
I don't tend to be a pushy guy. I avoid lines, I don't wait in them and if I have too I don't push my way forward. Don't be an ass is my thinking.

Recently one morning, being an ass was in order. My flight from Vancouver to Toronto was delayed due to a late plane and then a late crew coming from another late plane.

We left 40 mins late. On landing, with deboarding and standing in the back with 65 rows in front of me, 7 seats wide, my 65 minute layover was evaporating fast. My bag was on the other side of the plane in an overhead compartment. I have to catch a flight in 25 minutes and time is counting down.

So I started butting line until someone decided their connection an hour later was more important than mine leaving in now 20 minutes. Finally, I get out after what feels like an eternity.

10 minutes to doors close and I am fighting my way up the ramp to find out where my connecting gate is. It's not far and I start running. You know you are in trouble when they say Mister Thistle as you run up to the gate. "Yes. Been waiting long?"

"Yes, sir." She radios down that I am here and I sprint to the plane. The doors close behind me.

If I had not pushed trough 20 rows until I got luggage blocked I might have missed the flight.

I was an ass that day as I pushed past people with their problems and worries that I know were as important to them as mine was to me. I pushed past because I was rushing home to my Nan's funeral to be by my Mom's side and pay my respects.

The next time someone asks to skip past, maybe they aren't being an ass.

Maybe they have something a little heavier they are rushing towards.


Lighten up.