Organizing your Xcode Swift Code with Local Packages

Luca Angeletti
The Startup
Published in
5 min readMay 2, 2020

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!
}
Photo by Jens Johnsson on Unsplash

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:

  1. ✅ The name of the Type can be the same used by another type outside of the package.
  2. ✅ 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).
  3. ✅ This is way easier to manage than creating a whole new Projectjust 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.

Photo by Kelli McClintock on Unsplash
  1. Open your Xcode project and select File > New > Swift Package.
  2. Assign a name to the Package (e.g. Model) in theSave as field.
  3. Select the main folder of your project in the dropdown on the top.
  4. Select your project in the Add to dropdown.
  5. Select again your project in the Group dropdown.
  6. Do NOT check the Create Git Repository on my Mac
  7. Click on Create.

Congratulations! The new Local Package has been added to your project.

  1. Next, select your Target > General.
  2. Press the + button.
  3. Select your Package Module.
  4. 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.

--

--