As we learn Swift, many of us are looking at existing Cocoa APIs with a different perspective. And, for some, the strict nature of Swift is bringing new emphasis to issues with how those APIs were designed.
It’s assumed by many that the Cocoa APIs will be updated in the coming months to better work with Swift, and given that assumption, it seems like a good time to make suggestions.
I needed to solve a relatively simple date computation problem last week, and in doing so, I noticed an issue with NSCalendar that I think serves as a good example of issues made more noticeable by Swift. Below, I’ll describe the problems with that API and suggest a solution that I’d like to see applied to several of the APIs as they are updated.
What Are the Problems?
The problems that I’d like to focus on in this article are all related to the designated initializer for NSCalendar.
init?(calendarIdentifier string: String)
This initializer can fail. In practice, if you use the provided string constants, this method will always succeed. So, it’s not really as variable as it seems. But, Swift’s interpretation is correct: it is possible for this method to fail to return a calendar, and regardless of which language you’re working in, you need to check to make sure that you got a calendar, which results in additional code you have to write.
Ambiguity leads to mistakes. The NSString argument allows me to provide arbitrary strings, and that is a problem in the design of this API, in my opinion. We could use an enumerated type instead, but I think I have an even better solution below.
Using the existing API requires 2 steps: Find the method, then find the constant identifier. Xcode’s autocompletion can help you, but you still need to complete 2 steps, which could easily be reduced to 1.
What Improvements Could Be Made?
If I were maintaining NSCalendar, I would add class methods that represent each type of calendar that the factory method currently supports.
So, instead of this, in ObjC
NSCalendar *gregorianCalendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
Or, this, in Swift
let calendar = NSCalendar(identifier:NSGregorianCalendar)
We would have this, in ObjC
NSCalendar *gregorianCalendar = [NSCalendar gregorianCalendar];
And, we would have this, in Swift
let calendar = NSCalendar.gregorianCalendar()
What Impact Do These Changes Have?
Reduces the work for those using this API: No more optional return values. Because these new convenience methods do not take an argument, they cannot fail. As long as the method is supported, you will be guaranteed to get a calendar. This means developers can write less code when using this API, because they don’t have to handle a failure case.
Reduces the opportunity for mistakes: Help developers get it right the first time by removing the ways to shoot ourselves in the foot. Even with autocomplete, you can accept the wrong suggestion and, in the existing API, because the argument is a NSString, Xcode can’t warn that the wrong value has been selected.
Helps Xcode save time and effort: Remove steps needed to find the right choices. This one is relatively minor compared to the others, but … when you’re working with this class a lot, you’ll really appreciate only having to type “gre” to get autocomplete to show you the static method for the Gregorian calendar, instead of having to choose the (correct) initializer, and then find the (correct) string constant each time.
Backwards compatible: We’re not changing existing methods. We’re only adding methods, so there is no compatibility issue created. Existing code can continue to use the existing initializer. New code, however, would be significantly simpler to write using the new convenience methods.
A friend pointed out that Cocoa APIs tend to favor keys over methods, and I’m not sure why that is, but in my opinion, NSCalendar is a good example where methods would make using this API easier and less error prone.
In the case of NSCalendar, I believe that my solution removes opportunities to make mistakes, and saves developers from writing extra code to handling error cases that don’t really exist.
I’ve submitted a bug report to Apple with the explanation above. Hopefully, the right people there will see it and consider it for a future release.
If you have examples of other Cocoa APIs that could be improved, I encourage you to submit your own suggestions to Apple via bugreport.apple.com.