Modular iOS: Splitting A Workspace into Modules
If you haven’t read the previous part in this series, it’s available here: Modular iOS: Strangling the iOS Monolith. In the previous article I discuss the benefits of a modular architecture, which is hopefully evident from the visuals!
In this post, I’ll share how to go about splitting up your workspace.
Add your project to a workspace
If you don’t have a workspace already, it’s really simple to create a workspace from an existing project. (If you’re a CocoaPods user, you may be familiar with the workspace created automatically when you first install dependencies.)
To create a workspace from an existing project, open the project in Xcode, and select File → Save as Workspace…
(If the option is disabled, it means your project is already part of a workspace!)
Select a sensible location to save your
.xcworkspace package. This is usually in the same directory as the
.xcodeproj package that represents your project.
You’re now all set to add feature modules to your codebase!
Add additional projects
It may be that you’re starting a project from scratch and want to follow good practices. Or it might be that you wish to modularise an already mature codebase. Either way, you’ll want to create an additional project within your workspace to get started.
- Select File → New → Project…
- Select Cocoa Touch Framework from the template options and hit Next
- Give your feature module a name and hit Next
- When selecting a directory to save your new module, you may wish to create a new directory, such as Features. It’s best to create the new directory in the same location as the
- Don’t hit Create yet!
- From the Add to: drop-down, select your workspace
- From the Group: drop-down, select your workspace again. Make sure you’ve selected the workspace and not the project. You can tell because the workspace has a white background to the icon instead of blue
- Now hit Create!
Now you have your feature module, you can add new code to it, or migrate code from your existing project.
If you’re migrating code, be sure to move the files on disk as well as the file references in the Xcode Project Navigator. If you use Xcode to move the files around, be sure to select Copy items if needed. Then you can safely delete the original files and Move to Trash.
Organise your workspace
By default, Xcode adds each new project (module) to the top of the Project Navigator. So the main project — the project that contains the App Delegate — appears below your feature modules.
It’s good practice to move the added projects below the main project, so that the main project is first and foremost when developers open your workspace.
As of Xcode 9.3, you can drag and drop the framework projects within the workspace. You can also add groups, by secondary-clicking in the Project Navigator, and selecting New Group.
Prior to Xcode 9.3, Xcode would crash when trying to move the framework projects within the workspace.
If you create a group and add a few modules, your Project Navigator will look something like this:
Accessing code from your modules
Any code that you wish to access from the main app, you’ll need to declare
public. (Swift interfaces are
internal by default.)
To access the module’s public interfaces from your main project, you’ll need to link the frameworks and embed the framework binaries.
To do this, select the main app target, and the General tab. Scroll down to the Embedded Binaries section. Click the
You’ll see a dialogue like the one below, from which you can select the modules you wish to access. You can select multiple at once, by command-selecting, and then hit Add.
By adding them to the list of Embedded Binaries, they are also added to the list of Linked Frameworks and Libraries. They need to be in both:
You can now import your code as you would any system or third party module:
Clean up the Project Navigator
You will notice that the frameworks have been added to the Project Navigator, under the main project, like so:
It feels a bit messy having them exposed like this, especially as you add more and more. We can clean this up by creating a “Frameworks” group and moving them there, like so:
Then you can collapse that group and never look at it again! 🙈
Coming up next
In the next part in this series, I’ll show how to setup unit tests from modules to run when you run tests for the main target; how to setup CocoaPods to be used across a modular codebase like this; and how you can share test logic between modules.
If you’re interested in solving problems like this, and want to join the most passionate, collaborative iOS team in London, head to our careers page! 😎