Open sourcing Swift code
We all use open source code every day. Whether it’s by directly pulling in a framework in one of our apps, or indirectly in almost all of the apps, tools and utilities that we all use to get our work done as developers.
So open source is incredibly important for our industry, and it’s a big reason why I love the Swift community so much.
Since I’ve been quite active in the open source community for a while, many people often ask me for advice on how to get started and what to think about when open sourcing code. So this week I thought I’d try to sum up most of my tips & tricks on how to publish a new Swift open source project. Let’s get started!
Why open source?
There are many different reasons why you might want to open source parts of your code. Giving back to a community that we all benefit greatly from is an amazing feeling, and many companies really value open source experience when hiring new people. But there are also technical reasons why you might want to extract part of an app or system into an open source project, like the following:
Separation of concerns
When open sourcing code, you are clearly separating a system or some functionality from where it’s being used. This “forces” you to create better abstractions and more isolated objects, and usually leads to better dependency management.
Consider the following example, where a ShoppingCartManager
has an API to add a new product to the shopping cart. Currently that class also presents a confirmation UI whenever that API is called, like this:
class ShoppingCartManager {
func add(_ product: Product) {
model.products.append(product)
database.save(model)
let confirmationView = ConfirmationView()
confirmationView.productTitle = product.title
let window = UIApplication.shared.keyWindow!
window.addSubview(confirmationView)
}
}
The above code has a few problems, like mixing model code with UI code, and adding arbitrary views to the application’s key window (which is very common in code like this, that is not aware of the current view hierarchy). We could of course just fix these problems, but chances are most of these problems go by unnoticed (until the bugs start coming in), especially if it’s part of some legacy system that was written a while ago.
Now let’s say we want to open source our shopping cart management code, but not our UI code. This gives us an excellent opportunity to refactor ShoppingCartManager
to make it more properly abstracted, which will usually lead to fewer bugs in the future. For example, we might create a ShoppingCartConfirmationPresenter
protocol that any app using our project can implement, like this:
protocol ShoppingCartConfirmationPresenter {
func presentConfirmation(for product: Product)
}
class ShoppingCartManager {
private let confirmationPresenter: ShoppingCartConfirmationPresenter
init(confirmationPresenter: ShoppingCartConfirmationPresenter) {
self.confirmationPresenter = confirmationPresenter
}
func add(_ product: Product) {
model.products.append(product)
database.save(model)
confirmationPresenter.presentConfirmation(for: product)
}
}
Clearer APIs
In my talk “Everyone is an API designer” I spoke about how API design can be a great tool for improving the architecture of an app. Misunderstandings and accidental misuse of APIs is a very common source of bugs, and when you open source your code - and it starts being used in more than one project - you will often be able to identify APIs that could be improved more easily.
In one of the examples from my talk, I show an ActionSheetController
that has an API to add a button to it, like this:
class ActionSheetViewController: UIViewController {
func addButton(button: UIButton, dismiss: Bool) {
...
}
}
The second argument of the method above is called dismiss
, and the intention is to allow the API user to decide whether the button should dismiss the action sheet once tapped. However, by just looking at the call site, that intention is not very clear:
actionSheet.addButton(button: finishButton, dismiss: true)
When just reading this code in isolation, it’s very unclear what is being dismissed and when it will be dismissed. The API also has some room for improvement when it comes to duplicating the word "button" as well.
When open sourcing code, problems like the above become a lot more visible and gives us an opportunity to refactor and fix things. In this case, what we’ll do to make things super clear, is to divide our API up into two clearly separated methods - one for adding a "normal" button and one for adding a dismiss button, like this:
class ActionSheetViewController: UIViewController {
func add(button: UIButton) {
...
}
func add(dismissButton: UIButton) {
...
}
}
What to open source?
So what parts of an app are usually good candidates for being open sourced? For me, I usually aim to extract parts that are mostly focused on doing a single, more general purpose task, such as:
- Mapping JSON or other data to models.
- Loading or caching data.
- Accessing files, folders or other types of I/O operations.
- Performing animations, UI styling or other kinds of rendering.
- Convenience APIs that solve commonly faced problems.
One thing that I think is super important to point out is that your open source project does not have to be revolutionary or super amazing in order to be useful and interesting for the community. It also doesn’t have to be a unique solution that no one has ever seen before. Sometimes I feel that we focus a bit too much on finding "silver bullet" type solutions to problems, and only consider very ambitious projects candidates for open sourcing. If you want to get started with open source - just pick some part of your app that you think could be beneficial to people, and put it out there. Chances are someone else will find it interesting, and that is more than good enough!
How to open source a project?
OK, so we’ve taken a look at the why
and the what
- now lets more over to the how
. Here are some practical tips from things I’ve learned open sourcing several Swift projects:
Avoid nested dependencies
In order to make your project much easier to use & maintain, try to make projects that are self-contained and that don’t rely on concrete implementations of other projects. This will both enable others to much more easily use your code, as they only need to include your project. It will also make it much easier to maintain and keep up to date.
If your project needs functionality from other projects, I would suggest using the same technique as in the ShoppingCartManager
example above and create protocol-based APIs that the objects from the other projects can simply implement.
For example, rather than relying on Alamofire, Moya or some other networking framework, you might want to define a NetworkAdapter
protocol that any networking framework can be extended to implement, making your project a lot more flexible:
protocol NetworkAdapter {
func loadData(from url: URL, completionHandler: @escaping (NetworkResult) -> Void)
}
Support CocoaPods, Carthage & Swift Package Manager
In order for others to be able to easily use your project, make sure to support all 3 major package managers. It might seem like a lot of work, but is actually quite simple (and you can even automate the process using SwiftPlate). Here are the things you need to do:
- For CocoaPods, you need to create a
Podspec
. If you haven’t done this before, I recommend taking a look at any other open source project’sPodspec
and simply replace their information with yours. You can, for example, use Unbox’sPodspec
as a starting point. - For Carthage, all you need to do is to have an Xcode project that builds a framework in your repository. Make sure to share the schemes you want to build by checking the "Shared" checkbox under "Manage Schemes..." in Xcode.
- For the Swift Package Manager, you need to create a
Package.swift
file. The easiest way to get such a file - if you’re not using SwiftPlate - is to runswift package init
in a directory. Note that SPM currently only supports macOS & Linux.
Set clear expectations in the README
Every project should have a README, in order to give a quick introduction to the project - what it does and how to use it. But another really important role a README plays in a project is to set the right expectations. What can people expect from using your project? Is it considered a "finished product" or is it an ongoing project?
For example, I just recently open sourced my new Swift game engine - called Imagine Engine (A lot more about that in upcoming posts). While most of my open source projects are released in "1.0 shape" (meaning that I consider their API pretty much fully baked and ready to go), Imagine Engine is an ongoing project that will keep evolving together with the community. So rather than "pitching" the engine as something that is a finished product, I made sure to clearly state in the README that it is an ongoing project that I’d love to see people contribute to.
It’s totally fine to release something as open source even if you don’t consider it finished. In fact, it’s a great opportunity to get help from other people to actually do finish it, and to get feedback from the community at a much earlier stage of a project. Just make sure your intentions and what expectations others can have are clearly stated in the README.
Write tests to show what use cases the project supports
Testing is a great tool to maintain high quality in software in general, but for open source projects testing becomes especially important. Not only does testing provide a "safety net" when others want to contribute to your project, but it also clearly states what use cases it supports.
Use tests to implement various examples of how your project can be used, and to guarantee that those use cases are continuously supported over time. For my top tips about unit testing in Swift, check out my testing posts that you can find on the Category Page.
Conclusion
I ❤️ open source. It’s definitely one of my favorite parts about being a developer, and I think working with open source is a great way to quickly become a much better programmer and expand your communication and teamworking skills.
I’ll probably write a lot more about open source in the future, but hopefully this post has given you a bit of insight into how I think about open source and how I usually approach new projects. For more on this topic, I also recommend that you check out Felix Krause’s excellent talk about how to get started with open source.
If you have questions or comments, feel free to contact me on Twitter @johnsundell. I’d be happy to answer any questions that you might have about how to open source your code.
Thanks for reading! 🚀