Organizing your Xcode Swift Code with Local Packages
For decades Xcode missed a very useful feature: namespace. This was true during the Objective-C years when the name of every class needed some special prefix to avoid naming collision. And it has remained an issue for the first years of the Swift domain (although in this case naming collision between types belonging to different projects was no longer a problem).
What Problem Does a Namespace Solve?
Let’s add some context to the problem. Every type we define in our Xcode project must have a unique name.
Example: we want to create a model struct to represent all the information of the Person
entity as shown below.
struct Person {
let id: String
let firstName: String
let lastName: String
}
Next, we want to create a View to show the information of a Person
class Person: UIView {
@IBOutlet weak var firstName: UILabel!
@IBOutlet weak var lastName: UILabel!
}
And boom! We get the following error!
Invalid redeclaration of 'Person'
Of course, we cannot assign the same name to 2 different types because in that case, it would no longer be clear which type we are referencing when we use the Person
word.
This is so obvious in the Xcode universe that many developers don’t even try it. In fact, for years the community adopted one of the following three workarounds.
Option 1: Adding a postfix (or prefix) to the type name
struct PersonModel {
let id: String
let firstName: String
let lastName: String
}class PersonView: UIView {
@IBOutlet weak var firstName: UILabel!
@IBOutlet weak var lastName: UILabel!
}
In this case, we just refer to the 2 types as PersonModel
and PersonView
. This is the simplest workaround. I don’t like it because completely ignores the idea of a namespace.
Optional 2: Simulating namespace nesting the type into an enum
enum Model { }
extension Model {
struct Person {
let id: String
let firstName: String
let lastName: String
}
}class Person: UIView {
@IBOutlet weak var firstName: UILabel!
@IBOutlet weak var lastName: UILabel!
}
In this case, we refer to the 2 types as Model.Person
and Person
. Things are getting better but we cannot benefit from the visibility modifiers. For example what if I want to add an init
to Model.Person
which is only visible to the Factory
class? I can’t.
Option 3: Creating a new project
The idea is to create a new Xcode Project only for the Model
and then adding it to the main workspace. Now I can finally use the visibility modifiers. This is a good solution but having a different project for each Module of your app may be overkill.
Swift Local Packages to the Rescue! 📦📦📦
With Xcode 11 Apple is suggesting its own official solution to this old problem. The idea is simply to leverage the power of Swift Packages to wrap our code into lightweight easy to use modules.
This solution fixes all the issues seen previously:
- ✅ The name of the
Type
can be the same used by another type outside of the package. - ✅ We get a namespace so now we can use the visibility modifiers to expose something to the whole package (
internal
) or to the whole project (public/open
). - ✅ This is way easier to manage than creating a whole new
Project
just for grouping similar types.
Finally, this solution is provided by Apple so I guess it deserves some attention.
Creating a Local Package
Finally, let’s see how to quickly create a Local Package.
- Open your Xcode project and select
File > New > Swift Package
. - Assign a name to the Package (e.g.
Model
) in theSave as
field. - Select the main folder of your project in the dropdown on the top.
- Select your project in the
Add to
dropdown. - Select again your project in the
Group
dropdown. - Do NOT check the
Create Git Repository on my Mac
- Click on
Create
.
Congratulations! The new Local Package has been added to your project.
- Next, select your
Target > General
. - Press the
+
button. - Select your Package
Module
. - Press
Add
.
Now navigate to this folder Model > Sources > Model
and make sure the Model
folder is highlighted as shown below.
Create a new file selecting File > New > File
. Name the new file Person.swift
.
Finally, add the following code to Person.swift
.
public struct Person {
public let name: String
public init(name: String) {
self.name = name
}
}
Rember that the default visibility modifier in internal. So if you want to access this struct from your project (outside of the package) you’ll need to make it public.
Using Your Package
Now go back to your project and open any swift file (e.g. ViewController.swift
).
To use the package, you’ll need to import it. So add this line on top of the file.
import Model
You can finally use in your project the type you defined inside your Local Package!
let person = Model.Person(name: "Jean Luc Picard")
Considerations
Swift developers finally have an official way of grouping the code of a project into logical units.
Using Local Packages will allow you to expose to the project only some parts of your code and to hide other features that are relevant only inside the package. Furthermore, they will help you fighting Tight Coupling, Spaghetti Code, and other bad habits.
If you need more info this is the official documentation provided by Apple.