Skip to main content

Swift Module Organization

Introduction

Swift modules provide a powerful way to organize and structure your code. A module is a single unit of code distribution - a framework or application that is built and shipped as a single entity and that can be imported by another module. In this article, we'll explore how modules work in Swift, how they interact with access control, and how you can use them effectively to structure your code.

What is a Module in Swift?

In Swift, a module represents a code distribution unit. It can be:

  1. An application (your main app target)
  2. A framework (reusable code that can be imported by other projects)
  3. A library (compiled code that can be linked against)

Every Swift file you create belongs to a module. When you create an iOS app, all your Swift files belong to the app's module by default.

swift
// This code is part of your app's module
class MyAppClass {
func doSomething() {
print("Hello from my app module!")
}
}

Importing Modules

To use code from other modules, you need to import them with the import statement:

swift
// Importing built-in modules
import Foundation
import UIKit

// Using types from imported modules
let date = Date()
let view = UIView()

Creating Your Own Modules

Framework Creation

One of the most common ways to create a custom module is by creating a framework:

  1. In Xcode, go to File > New > Target
  2. Select "Framework" under iOS, macOS, or other platforms
  3. Name your framework and configure it

Once created, any code you add to this framework becomes part of that module:

swift
// Inside MyCustomFramework
public class NetworkManager {
public init() {}

public func fetchData(from url: URL, completion: @escaping (Data?, Error?) -> Void) {
// Implementation...
}
}

Using Your Custom Module

After creating your framework, you can import and use it in your application:

swift
// In your main application
import MyCustomFramework

let networkManager = NetworkManager()
networkManager.fetchData(from: someURL) { data, error in
// Process data or handle error
}

Module Boundaries and Access Control

Modules play a crucial role in Swift's access control. The module boundary is a fundamental line that defines the scope of access control modifiers:

  • private: Accessible only within the defining file
  • fileprivate: Accessible within the entire file
  • internal: Accessible within the entire module (default)
  • public: Accessible from other modules, but can't be subclassed or overridden
  • open: Accessible from other modules and can be subclassed or overridden

Example: Access Control in Modules

swift
// Inside NetworkModule framework
public class APIManager {
// Private: only accessible within this class
private let apiKey = "secret-api-key"

// Internal: accessible within NetworkModule but not outside
internal var baseURL = "https://api.example.com"

// Public: accessible from any module that imports NetworkModule
public func fetchUsers() -> [User] {
// Implementation...
return []
}

// Private function: only accessible within APIManager
private func authenticate() {
// Use apiKey for authentication
}

// Public initializer so other modules can create instances
public init() {}
}

// Since NetworkHelper is internal (default), it can only be used within NetworkModule
class NetworkHelper {
func prepareRequest() -> URLRequest {
// Implementation...
return URLRequest(url: URL(string: "https://example.com")!)
}
}

From another module (like your main app), only the public members are accessible:

swift
import NetworkModule

let apiManager = APIManager() // Works because init() is public
apiManager.fetchUsers() // Works because fetchUsers() is public
// apiManager.baseURL // Error: baseURL is internal to NetworkModule
// apiManager.apiKey // Error: apiKey is private to APIManager
// let helper = NetworkHelper() // Error: NetworkHelper is internal to NetworkModule

Practical Organization Strategies

1. Feature-Based Modules

Organize your code into modules based on features:

MyApp
├── AppCore (main app module)
├── Authentication (login, registration, etc.)
├── Networking (API calls, data fetching)
├── Analytics (tracking, reporting)
└── UIComponents (reusable UI elements)

2. Layer-Based Modules

Organize by architectural layers:

MyApp
├── AppCore (main app module)
├── DataLayer (models, repositories, data sources)
├── DomainLayer (business logic, use cases)
└── PresentationLayer (UI components, viewmodels)

Real-World Application: Modularizing an E-commerce App

Let's look at how you might modularize an e-commerce application:

1. Define Your Modules

EcommerceApp
├── Core (main app)
├── ProductCatalog (product browsing and search)
├── ShoppingCart (cart management)
├── Checkout (payment processing)
├── UserProfile (account management)
└── NetworkServices (API communication)

2. Set Up Module Communication

In the NetworkServices module:

swift
// Inside NetworkServices module
public class APIClient {
public static let shared = APIClient()

private init() {}

public func fetch<T: Decodable>(endpoint: String, completion: @escaping (Result<T, Error>) -> Void) {
// Implementation...
}
}

// Model that will be shared across modules
public struct Product: Codable {
public let id: String
public let name: String
public let price: Double
public let imageURL: URL

public init(id: String, name: String, price: Double, imageURL: URL) {
self.id = id
self.name = name
self.price = price
self.imageURL = imageURL
}
}

In the ProductCatalog module:

swift
// Inside ProductCatalog module
import NetworkServices

public class ProductService {
public static let shared = ProductService()

private init() {}

public func fetchProducts(completion: @escaping ([Product]) -> Void) {
APIClient.shared.fetch(endpoint: "/products") { (result: Result<[Product], Error>) in
switch result {
case .success(let products):
completion(products)
case .failure:
completion([])
}
}
}
}

In the Core app module:

swift
// Inside main app
import ProductCatalog
import ShoppingCart

class ProductListViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()

ProductService.shared.fetchProducts { [weak self] products in
// Update UI with products
}
}

func addToCart(product: Product) {
CartManager.shared.addItem(product)
}
}

3. Benefits of This Architecture

  • Independent development: Teams can work on different modules simultaneously
  • Clear boundaries: Each module has a well-defined responsibility
  • Reusability: Modules like NetworkServices can be reused in other projects
  • Faster compile times: Changes in one module don't force recompilation of everything
  • Improved testing: Modules can be tested in isolation

Best Practices for Module Organization

  1. Keep public APIs minimal: Only expose what other modules truly need
  2. Use internal access by default: Start with the default and increase visibility only when necessary
  3. Define clear module boundaries: Each module should have a single responsibility
  4. Consider compile time: Too many interdependent modules can slow down compile times
  5. Document public interfaces: Other developers need to understand how to use your modules
  6. Handle module dependencies carefully: Circular dependencies between modules should be avoided

Summary

Swift module organization is a powerful tool for structuring your code in a maintainable, scalable way. By thoughtfully organizing your code into modules with appropriate access control, you can create cleaner architectures, enable team collaboration, and improve compile times.

Understanding the relationship between modules and access control is essential for creating well-structured Swift code. Whether you're working on a small app or a large-scale project, effective module organization will help you manage complexity and build better software.

Additional Resources

Exercises

  1. Create a simple iOS app that uses at least two separate framework targets (modules).
  2. Refactor an existing project to extract functionality into a separate module.
  3. Create a module with appropriate access control that exposes a public API while keeping implementation details private.
  4. Design a modular architecture for a hypothetical social media application.
  5. Practice creating and using a Swift Package that can be shared across multiple projects.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)