When to Avoid Libraries
If I offer you $50 today or $100 one year from now, you'll take the $50. Yet if I offer $50 in five years or $100 in six years, you'll pick $100, which seems irrational given the difference is still one year. Economists call this Hyperbolic Discounting. Given two similar options, we strongly "discount" the later one.
Year ago, it was such as huge pain to load third-party libraries in an iPhone app, to the point where it forced us to ask if it's really worth it. Today, package managers take the process down to seconds, tempting us with those short term gains.
Let's say your app needs a wrapper for your server's API. Do you leverage a magic library and spend an hour, or write it yourself and spend a day?
If you're building a prototype, go with magic. I tell new developers to use libraries in their first projects because I don't want them bogged down in minutiae and lose momentum.
But if you're building an app to last years, this isn't "an hour vs a day" but "100 hours vs 101 hours." Magic works for simple, consistent API you control. Over time, APIs get bogged down by business logic, edge cases, and technical debt. When these systems have issues, even just slight inconsistencies, and magic hinders debugging.
This post isn't about magic or the shortcomings of particular packages, but due diligence. It's nuts that teams spend weeks looking for candidates, and days on interviews to test their code's worthiness, but these same teams don't think twice about using a 10,000 line library that was the first Google search result.
Libraries aren't a one-time cost. For a very long time "Core Data" framework was overly complicated, and more than a few developers used wrapper frameworks that made things appear friendlier. This also put developers on their heels, when Apple updated Core Data.
Years ago, Apple deprecated the lock
method. If you used a wrapper that supported that method, then your code would break once Apple removed it. Sure, you could just upgrade the library, but that upgrade also renamed methods, breaking your app in new ways. These headaches could be avoided by just running plain old Core Data, where you just have to tackle the one deprecated method.
Now consider transitive dependencies, or libraries that require libraries. Sure, the package manager might handle the graph, but it can't solve API churn or one of those hidden dependencies disappearing.
At scale, life is easier when you rely on as few third-party libraries as you can get away with. Fortunately, you don't need that many. That seems odd coming from a web background, but the iPhone is a "batteries included" platform. Out of the box, you've got frameworks for animation, networking, persistence, and everything else you need for most apps.
When I'm vetting libraries, I have a few guidelines. There are fine reasons to violate them, but come prepared with an argument.
Avoid abstractions over high-level APIs. iOS engineers made conscious decisions about what should be managed by frameworks and what's left up to the developer. The UIKit team could have abstracted UITableView
into something more magical, but they know developers should think about cell recycling if they want to hit 60FPS.
Generally, Apple provides low-level APIs in C, and a high-level API in Objective-C. If you need a simple photo picker, use UIImagePickerController
, and if you need to get your hands dirty, use AVFoundation.
Apple makes mistakes, like the iOS Keychain, or the overly complicated Address Book API. The former justifies a wrapper, and the latter was fixed in Apple's new Contacts.framework
.
Managing when the keyboard appears can be a drag, repositioning input views so they aren't obscured. If you leave it to a library, the abstraction leaks like a sieve as soon as you involve view controller containment. Consider how often automaticallyAdjustsScrollViewInsets
fails, and that's built right into UIKit.
Don't stress out over boilerplate. Setting up the Core Data stack takes a bizarre 20 line incantation just short of animal sacrifice. You could use a Core Data wrapper's one-line setup, but I just copy and paste the boilerplate. That's right, I copy and paste.
In your first year coding you learn "Don't Repeat Yourself." After you waste hundreds of hours debugging excessively DRY code, you realize there's a difference between repeating yourself and being explicit.
If the boilerplate gets mind numbing, refactor it into class methods. If that gets to be too much, build some model objects. The goal is to iterate toward your domain model. There's a reason Apple's frameworks only get you 80% of the way there. That remaining 20% is called "Your App."
Do you really need all that? In Code Complete, Steve McConnell explains that project costs scale non-linearly with the size of the codebase. It should come as no surprise that it's easier to do smaller projects well.
Take AFNetworking, what was once the most popular open source iOS library. It has a buffet of features, such as request serialization, reachability checking, and security. In April, researchers found it contained two critical security vulnerabilities. One of those affected anyone running 2.1 and above, which spans over a year. In my experience, most apps don't need AFNetworking, and most apps certainly don't use certificate pinning, the very feature that broke security.
Use libraries for problems outside your domain. Don't take things to an extreme with Not Invented Here Syndrome.
Debugging OAuth is reserved for the Ninth Circle of Hell, so please use a library. But that doesn't let you off the hook. Someone once told me, "You shouldn't need to understand how OAuth works if a library does the job."
In the iOS 5 beta, my team discovered a bug in Apple's system OAuth library. Multipart-uploads weren't signing correctly, so photo uploads broke. By this point, iOS 5 APIs were locked down, so it was unlikely Apple could fix things in time. Fortunately, we knew enough about OAuth to trick the framework into doing what we need.
You shouldn't need to understand OAuth, but if your app uses OAuth, that's life. Nobody cares if it's your code or the library that broke. It's your app, it's your problem.