Modular XCode Project

Selin Acar
Mac O’Clock
Published in
5 min readJun 27, 2020

--

Recently, I was given the task to figure out the best way to package and include a customized SDK written in Objective-C and associated frameworks, into a new Swift project. Was it better to just drop in all of the files into the project into a folder, or is there a better way? I had no idea the different ways to go about this, and not knowing the differences between a module and packages in Swift made things confusing. On my journey to figure out what to do, below are terms I needed to go about my way, and the different options I could do.

Understanding terms

A Swift Package contains files + manifest file (Package.swift that defines the package contents and name)+ has 1 or more targets.

Build Targets build items in a package, packaging its contents and any dependencies, into a Product.

A Product (*.app) is the result of packaging code, either into a library package or an executable package.

(Swift)Static Library(*.a) = object files included at compile time. This cannot include resource files i.e. images, assets, nibs, strings file, and other visual data, but these can be included in a separate bundle.

Dynamic Libraries(*.dylib) are included at run time and can be executed by an OS. Dynamic libraries alone are not supported in Swift but are actually part of Swift Dynamic Frameworks, which are supported.

(Dynamic)Frameworks(*.framework) package header files and resources (images, assets, documentations, strings files).

Bundle = a folder that keeps its file structure.

Modules in XCode are any of frameworks, static libraries, Swift packages, and build targets. These include code files (including other module dependencies) plus access controls on what can be used outside of the module. Modules can be a dynamic framework (included at run time, can be run by the operating system) or a static library.

Dependencies = modules required by the code in a package.

What are the options?

Creating a static library is not an option because it does not include resource files, also it would not be optimal since the files I’m trying to include are not required before run time. Doing nothing special, creating a cocoapod, using Swift Package Manager, and creating a dynamic framework module are all options:

1. Add the frameworks, drop in the SDK file, and make customizations directly to the SDK files in the project

Sounds exactly as it is, but this doesn’t look as clean, make things easier to update, and doesn’t separate the code that well…

2. Create a Cocoapod

Did you know there are also private cocoapods (read about it here)? Going this route involves creating a private source code repository, giving it to CocoaPods, then adding the podspecs to the repository.

3. Create a Package and use Swift Package Manager

Swift Package Manager manages Swift code distribution; automates downloading, compiling, and linking dependencies. Built into XCode 8.0+, this takes source code repos and makes it easy to add in the dependencies to a project.

If you choose to go this route and want to get started, check out Brian’s video. And directly from one of his comments of why using Swift Package Manager would be better over Cocoapods:

- Not having to use ruby for cocoapods is nice
- Managing everything from within Xcode is cool
- Dependency version is easy through the SPM menu
- One annoying downside is that if you have 10 dependencies you have to add them one by one. There’s probably a workaround for this though.

4. Create a framework module

Let’s start with cons:

  • Creating a framework seems pretty easy, but if I need to update the SDK and the five framework files from the vendor I basically need to redo the whole process of including everything all over again.
  • I would need to make changes separately from my project (in a different repo), then include them in my app to see if it’s what I want.

As for the benefits:

  • This is a good solution to easily include and remove whatever is in the package from the project. This is especially useful since it would allow me to test the app on simulator, which including the frameworks would not allow me to do (it is dependant on a physical device with a camera). This is important since simulator is always available whereas physical devices may not always be on hand. In my situation there is a large team working on different parts of the app who may not have devices, and since corona started my team has been working from home, with most devices being with testers and not developers.
  • This has the benefit of being dynamically loaded.
Two module types: framework and static library

What I ended up doing

I haven’t fully finished this part yet, but I’m going to go with creating a framework module.

This feels like the right choice because:

  1. It created some structure,
  2. The feature these files are used for is not used every time most users open the app, so being dynamically loaded is a good optimization,
  3. This allowed separation all of the 5 frameworks and SDK code, into a single framework file which simply needed to be included in Build Phases > Link Binary with Library. Further updates to assets would require me to just update this framework and drop it in.
  4. I have a ton of resources that need to be included that don’t seem to be something swift packages can handle.

I’ll be continuing to try out this tutorial. As the main project is Swift, and the code I wanted to include with the SDK and 5 frameworks is in is Objective-C, I created a Swift Dynamic Framework, imported the 5 frameworks and SDK code into it (Build Phases: headers into the headers, .m files into Compile Sources, frameworks into LinkBinary with Libraries, and assets into Copy Bundle Resources)…. and I guess when I get back to this part of the project I’ll be picking up from there.

Resources:

--

--