Using Swift Modules
In this blog post, I refactor an existing repository by incorporating Swift modules. While certain aspects of this process were smooth sailing, I encountered challenges along the way. Join me as I share my experiences and insights.
The source code is hosted on GitHub in this repository: https://github.com/field-theory-org/match-game-architecture.git.
The Project Background
My post series on app architecture starts in this post. Throughout the series, I showcase an app implemented using different architectural patterns, emphasizing the advantages and disadvantages of each approach. The distinct variants of the app share code, particularly reusing the data model and view. Since the project’s inception in 2016, the Swift programming language and its toolchain have made significant strides. In this installment, I demonstrate how to enhance the project by transitioning from linking source code files to employing Swift modules.
The Original Approach
The basic structure of the repository is as follows:
│ ├── CustomViews
│ ├── Model
│ └── Resources
Initially, I had just used symbolic links from a common source file in
01_Common folder to the specific usage in each of the
projects. This approach was problematic because Xcode was not good at
understanding how to handle symbolic links – once you edit a file,
it’ll get confused.
Moreover, the shared code lacked organization within a dedicated project, rendering it impossible to build and test in isolation. Ultimately, this approach was not only suboptimal but also inelegant.
The traditional approach to addressing shared code issues is to put the shared code into a framework. With its own project, a framework can include tests. While this remains a viable solution today, Swift modules present an alternative. Here, I outline the advantages and disadvantages of both options:
- Both Swift modules and frameworks enable code modularization, and thus share and reuse the code across different projects. For the current code-base either choice would be suitable.
- Both can be distributed independently as separate units.
- Both support versioning.
- A framework is pre-compiled, and as such, cannot be reduced in size. Consequently, they are fully included in a project, particularly increasing app load times.
- Swift modules are more lightweight compared to frameworks, with less overhead related to dynamic linking. Ultimately, a large Swift module loads faster than a similarly sized framework.
- Swift Package Manager has its own dependency management out of the box. For frameworks, you need third-party solutions like CocoaPods.
- Swift modules can also be used in other projects, such as Swift on Linux, whereas frameworks are restricted to the Apple platform.
- Frameworks can be used both in Swift and Objective-C code bases. Swift modules only work with the former.
Additionally, I encountered another limitation:
- Frameworks have their own dedicated Xcode project. Thus, they support tests for each target platform. However, I discovered that testing code reliant on UIKit in Swift modules is not feasible without creating a separate, dedicated Xcode project.
Such a project is usually not needed when running platform-independent tests.
In the specific case of the match game app, there are two separate groups of functionality. Thus, I have divided the functionality into two separate modules:
- The data model, and
- the custom view.
I have reorganized the code in
│ ├── MatchModel
│ │ ├──Sources
│ │ ├──Tests
│ ├── MatchView
│ │ ├──Sources
│ └── Resources
- The data model is in the
- The view is in the
- The resources remain untouched.
The data model has a suite of tests that can be run on the macOS command line directly using
Note that this is not possible to run any actions on the command line
swift for the
MatchView module. The view can only be built
and tested using
xcodebuild, which necessitates a separate Xcode
The first project in
10_Commandline-App now requires only the
MatchModel module, whereas the second project in
needs both the
MatchView modules. The third project
30_MVVM-App must be adapted in the same way as the basic iOS app.
In addition, I have carried out the following steps:
- Introduced workspaces for all projects that use modules, which
- Utilizing the workspace instead of the project file for all command line and CI build actions.
- Opening the workspace in Xcode rather than the project file.
- Explicitly included both modules in the list of library dependencies in the target build settings.
- Fixed the dependency of the
MatchPileViewin the iOS storyboards, as it is now part of the external module rather than the project itself.
import MatchViewexplicitly in the source code, since they now belong to modules instead of the main source code base.
Ultimately, the project compiles and runs smoothly, utilizing modern tools and workflows.