Borrow, Buy, or Build?
One of the great benefits of modern app development is the
plethora of libraries that are easily available to your
app. The popular CocoaPods
dependency manager makes it drop dead easy to incorporate any
of its 44 thousand libraries. Two little words - pod
install
- and the world is at your fingertips.
Graphing and charting, networking, keychain management, custom UI, analytics - there's certainly something you can use in your next app. In fact, CocoaPods indicates there are currently over 3 million apps that have pulled in at least one of the libraries cataloged in the CocoaPods repository. Is there any reason not to jump right in and borrow the functionality someone else has developed and made available?
Support
One alternative would be to buy commercial components for your app. Spending money on software components may be like chewing on aluminum foil given all the free options that can be available. Why pay almost $1,500 (USD) for Highcharts if I can use CorePlot for free? One possible advantage is that buying helps guarantee some level of support if you run into problems with the library you're using. Popular libraries can have avid followers who are glad to offer help to other adopters on sites like Stack Overflow, but there are no guarantees that you'll find the answer to whatever problem you may be facing or that your answer will come in the timeframe you need.
One of the other potential benefits of buying is that commercial components may tend to have an easier upgrade path between versions. Whereas free, open-source library developers may choose to take their library in a new direction (requiring a lot of rework on your part), commercial vendors may be more willing to figure out how to provide a clear path forward with new software versions, lest they alienate customers and take a revenue hit.
Service Availability
Of course, there are some free libraries that are provided by big, commercial entities, such as Google. Their popular set of Firebase components provide valuable features with plenty of documentation and support. Analytics, crash reporting, user authentication, remote configuration - the list goes on and on. Why buy when I can borrow in a case like that?
One possible concern is their service availability. A quick Google search reveals that your app won't be able to access some Firebase services in certain parts of the world, so if those markets are significant for your app and Firebase is providing crucial functionality for your app, you have a bit of a dilemma. If you are shipping an app and depending on Firebase Remote Config to enable features after you ship, those features will never be enabled in countries that are blocking access to Firebase servers.
In such cases, you may find your best option is to build. Of course, building your own solution requires time and talent. You're not going to replicate all the features of Firebase in a short timeframe, or benefit from the fact that Google has probably already solved many of the problems you will run into along the way. But if their service doesn't work in the markets you want to reach, you may not have much choice.
So, support and service availability are two considerations in the decision to borrow, buy, or build. Are there any other issues worth considering?
Synchronization
Adding a library dependency means you are also adding all of that library's dependencies as well - including its dependencies on Xcode and Swift. Apple may be resolving some of these issues as we move closer to ABI stability and receive compilers that are compatible with old language syntax, but there's still some chance that your dependencies will prevent you from upgrading to new versions of Xcode and the Swift compiler. You will need to synchronize your migration to a new toolchain with the migration of all your dependecies.
Of course, this isn't generally a problem if your dependencies are written in Objective C instead of Swift.
Size
Have you ever checked to see how much a new library adds to the size of your app? If you're quick to add libraries, you're also going to be quickly increase the size of your app - perhaps much more than you expected. In particular, you will want to pay close attention to your dependecies' dependencies. If you are using a library to access the iOS keychain or perform JSON encoding and decoding, and another library relies on different libraries for the same functionality, you're pulling in a lot of unnecessary code.
Here's a quick investigation I performed to see how adding some common dependencies might impact your app size. The sizes are the .xcarchive file size, rather than actual install sizes, but the number should give some idea for how much some common libraries increase the size of an app (and someone should be paying attention to this every time you add or update a dependency).
Project | .xcarchive Size | Increase |
---|---|---|
Single page Swift app, no additional code | 208 MB | - |
Add Firebase Core | 215.8 MB | 7.8 MB |
Add Firebase Messaging, Database, Dynamic Links, Remote Config, Auth, and Performance | 247.3 MB | 31.5 MB |
Add Alamofire | 255.1 MB | 7.8 MB |
Add SwiftyJSON | 257.9 MB | 2.8 MB |
Add SnapKit | 260.1 MB | 2.2 MB |
Add RxSwift | 288.9 MB | 28.8 MB |
Firebase and other libraries are careful to split their components into separate modules that you can choose to include or not, but not all libraries are so conscientious. You may sometimes find that you're consuming a lot of library bloat because of features you never even use.
If you're including Alamofire and SwiftyJSON in your project
because you pull data from a handful of endpoints, you may want
to shave some megabytes from your app and stick with vanilla
URLSession
and use the Codable
protocol on your data objects.
Simplicity
Building on the idea of consuming features you never use, you may want to take a look at the source code from your dependencies to see exactly how much code is there. If you're only borrowing a few hundred lines of code, you may just want to implement the functionality yourself and jettison the dependency.
Likewise, if you're only using a small portion of the library's functionality, you may be able to quickly re-write just the parts you need. For instance, you can pull in a dependency on Firebase Remote Config to enable features in your app after launch, but you might just as easily stand up an endpoint that provides the same bit-flipping capability if you don't require all the configurability of Firebase Remote Config.
Sharing (Data Privacy)
When a user grants your app permission to access a particular service or system resource, that permission is being granted to your entire app - including all of your library dependencies. Before adopting an analytics library, you may want to investigate whether it will take advantage of location permissions if they've been granted and how that location information may be shared with third parties.
Or what if you've been given access to the user's photos or contacts? Is there any chance that one of your dependencies could access those photos or contacts and use the information in a way you didn't expect? With the upcoming enforcement of General Data Protection Regulation you want to absolutely certain you're not leaking user information through third-party dependencies.
Security
Related to data privacy, when you include third party code in your app, how do you ensure it doesn't contain any malicious code? You might assume the most popular libraries are safe or someone would have raised red flags on the internet. What about smaller libraries? Do you review their code before pulling them in? It's entirely possible they could access the user's keychain to siphon off credentials. And libraries that are distributed as binary-only frameworks (e.g. Firebase) don't allow for inspection - you just need to trust Google isn't doing something you wouldn't like (but at least you read all the license agreements first).
Speaking of license agreements, you will want to make sure the various licenses for third party libraries don't have any implications on your own source code.
Summary
CocoaPods makes it extremely simple to bring new features into an app, but, as software engineers, we are responsible to weigh all of our options before making engineering decisions. Support, service availability, synchronization, size, simplicity, sharing, security - these are all factors we need to consider before making decisions about the materials we're going to use in our next building project.