Swift Modules

By

Learn how modules work in Swift to group files so you can reuse and encapsulate code, and how importing frameworks like SwiftUI and UIKit brings them in.

~~~

A module in Swift is a group of files that forms a single unit of code you can import and reuse. You write software in files. A simple program might be stored in a single file, but complex programs are written across multiple files, and modules are how Swift organizes them.

Modules help us do 2 things: reuse code, and encapsulate code.

You just have to write a particular functionality once, and after putting it into a module, you can import that into different places and projects.

Encapsulation means that the library can do lots of complicated things internally, but you only expose a tiny bit of it to the outside.

What counts as a module?

In practice, every build target is a module. Your app is a module. A framework is a module. Each target in a Swift package is a module.

This is why you can use a struct defined in one file of your app from another file, without importing anything. Both files belong to the same module, so they see each other automatically.

Importing a module

You start using modules by importing them.

If you’ve ever written

import SwiftUI

or

import UIKit

you’ve already used modules. Frameworks like UIKit and SwiftUI, and many others, are modules.

After you import a module, everything that that module declares public will be visible inside your application code.

Modules can import other modules, and when this happens you have access to those modules automatically.

For example SwiftUI imports Foundation, so you don’t have to write

import SwiftUI
import Foundation

because you just need the first line.

Swift itself is a module, as well. And you never have to import Swift because it’s done automatically for you.

What does a module expose?

Inside a module, access levels decide what the outside world can see.

By default, everything you declare is internal: usable anywhere inside the module, invisible outside of it.

struct Invoice {
  let amount: Double
}

Any file in the same app can use Invoice. But move this struct into a Swift package, and code that imports the package can’t see it.

This is the classic surprise when you extract code into a package for the first time. You import the package, and the compiler says it cannot find Invoice in scope. Nothing is wrong with the import. The type is just internal.

The fix is marking what you want to expose as public:

public struct Invoice {
  public let amount: Double

  public init(amount: Double) {
    self.amount = amount
  }
}

Notice the explicit public init. The memberwise initializer Swift generates for structs is internal, so without it, importing code can see the type but can’t create an instance of it. Each property you want readable from outside needs public too.

My advice is to expose as little as possible. Everything you mark public becomes part of the module’s contract, and you’ll have to keep supporting it.

Tagged: Swift · All topics
~~~

Related posts about swift: