Even though we had dependencies managers for several years now, with Swift Package Manager (SPM) creating packages in your app is almost as easy as adding new folders. Convert parts in 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 these 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 a shared code between apps is a great way to maintain them over time. What I didn’t realize that even without putting those packages on Github and share 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. Organize your code to 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 setup. 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:
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 bundles 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 the logger modules to function.
Then we move to the sync (that obviously needs the network and the persistent store) and so on with the other modules.
Now, if you built one or two apps in your life, this diagram is not new. Most apps actually built like that, but with groups. With modules, it’s more clear and transparent.
Modules make your layers interface clearer
Because each part in your app is a module, the process of building them forces you to really plan and design your 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 are the dependencies. By looking at the diagram above, 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 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, writing tests, and make any modifications you want. It’s also clear on what part of your app you need to work, now that your project is built of SDKs.
Compiling is so fast now that it’s ridicules
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. Compile a particular module is easy — just select it from the scheme popup 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 to the previous point — the benefit of separation is not only compiled time, but it’s 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.
So, what about the actual app tests? Well, now that you have independent modules, which being tested internally, you can start doing something called “Integration Tests” — 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.
And 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 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 it.
– 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 it somehow using delegates or closures.
If those are big issues for you, there are other ways of creating swift packages.
Swift Package Manager has a way to go to be 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, structure your app with modules is always recommended