How Swift Packages Changed the Way I Build Apps

Creating packages in your app is almost as easy as adding new folders

Avi Tsadok
Better Programming

--

Photo by Martin on Unsplash

Introduction

Even though we’ve had dependency managers for several years now, with Swift Package Manager (SPM), creating packages in your app is almost as easy as adding new folders. Converting parts of your project to packages is not just a way to organize your code nicely; it actually changes the way you work and architect your app.

It all started when I had to take an existing big project and port one feature to a dedicated app. Since this feature is based on a low-level code, such as networking, persistent store, analytics, and logging, I thought it would be best to check how to convert those parts to modules.

I planned to:

  • Copy all this code to the new app
  • Organize the code in Swift Packages
  • Take the packages and implement them back into the “mother” app
  • Put the packages on their own repositories in GitHub and have shared code

I knew that having code shared between apps is a great way to maintain them over time. What I didn’t realize was that even without putting those packages on GitHub and sharing them, using modules (packages/Pods, whatever you want to call it) brings additional value to your app.

Playing With Lego

Most standard iOS apps are built from one big codebase and some third-party libraries. Organizing your code into packages changes all that. It makes your app development process feels like playing with Lego bricks. Do you want an app with the same foundation but a different UI? No problem, easy to set up. You want the today widget to take advantage of the excellent logging mechanism you created? Piece of cake. Everything is modular and easy to maintain.

Take a look at the following diagram:

Image source: Author

Except for the UI, all the other components are, in fact, packages. The diagram also reflects the dependencies between them. The logger and some help methods bundled to a common library are the low layer of the project. They do not depend on anything, but the other packages need them. So we have a network and persistent store packages that require the common and logger modules to function.

Then we move to the sync, which obviously needs the network and the persistent store, and so on with the other modules.

If you built one or two apps in your life, this diagram is not new. Most apps are actually built like that but with groups. With modules, it’s more clear and transparent.

Modules Make Your Layers Interface Clearer

Because each part of your app is a module, the process of building modules forces you to really plan and design the interfaces between them. You need to make important design decisions, and you need to decide what functions are public — oh, finally, it has meaning inside your app! — and what the dependencies are. By looking at the diagram above, you can see that you cannot import the analytics package into the persistent store library. I’m not sure it’s even possible, but surely it’s not a best practice.

Actually, without drawing the diagram, it’s hard to define the dependencies, so we’ve also earned … a diagram?

You Now Have Several Small Problems Instead of a Giant One

This is part of something called dynamic programming. Everybody knows that solving a small problem is easier than solving a big one. Splitting your app to small modules makes it easier for you to solve issues in your code.

In fact, your app can be in a state that it’s not even compiled (!), and you can still work on a specific module, compile it, write tests, and make any modifications you want. It’s also clear what part of your app you need to work on, now that your project is built of SDKs.

Compiling Is So Fast Now That It’s Ridiculous

You know when you do a small change, and then you wait 30–40 seconds for your project to compile? Well, those days are (almost) over.

As I just mentioned, you can compile a specific module, and oh man, it is so fast, you do it again just to make sure something actually happened. Compiling a particular module is easy: Just select it from the scheme pop-up menu:

I don’t think I need to explain why compile-time has a significant impact on your daily life as a developer. There’s no way to go back.

It’s Straightforward to Test Everything Now, Even Your Apple Watch Extension

And this is a continuation of the previous point: The benefit of separation is not only compiled time but also testing.

Every module has its own tests:

The fact that the tests are located just below the execution code, plus the fast compilation, encourages you to add a lot of tests to your app (module). Heck, sometimes it’s the only way to check that your module works since, in many cases, you do it when your app doesn’t even compile.

Also, it’s the only way to test an Apple Watch, through its modules.

What about the actual app tests? Now that you have independent modules, which are being tested internally, you can start doing something called integration tests, i.e., test how your modules work together.

Remember, when you share your package with another app, its tests go with it. So it’s another something you save, some of the tests for the new app.

Yes, It’s Effortless to Share Code

After all, the main reason for a Swift package is to share code between projects. Look at the dependencies from the manifest file:

You see? You can define a dependency as a local package, or download it from Git. You can just upload your package to GitHub, change the manifest, and you’re done!

SPM (Swift Package Manager) Has Its Drawbacks

SPM still has its drawbacks:

  • You cannot add any resource files to your package, not even a Xib file. It means that building UI has to be done from code and building a persistent store library based on core data might be an issue since core data needs to make use of the model file (although you can bypass it somehow). Swift 5.3 is planned to solve this.
  • A Swift Package can be depended on another Swift Package, not Pods or your app code. The problem is that not all the essential frameworks have support for SPM, so you need to solve this somehow using delegates or closures.

If those are big issues for you, there are other ways of creating Swift packages.

Summary

Swift Package Manager has a way to go to be as usable and functional as CocoaPods or Carthage. But Xcode 11 makes it so easy that it makes no sense not to use it. Some of the things I wrote are also true for CocoaPods and Carthage; after all, the principles stay the same. No matter what you use, structuring your app with modules is always recommended

Thanks for reading!

--

--

Head of Mobile at Melio, Author of “Pro iOS Testing”, “Mastering Swift Package Manager” and “Unleash Core Data”