Best Practices for Sharing Internal Code Between Swift Modules in an SDK

Scenario

I am building a Swift SDK with the following structure:
1. Module A: Contains public APIs for customers to use.
2. Module B: Contains additional public APIs for customers.
3. Module Core: Contains shared code (e.g., data models, utilities) used internally by both Module A and Module B.

Requirements

1.	Shared Internal Code:
•	Module A and Module B need access to some internal classes, methods, or properties in Module Core.
•	These internals should not be exposed to the customers using the SDK.
2.	Customer-Facing Public APIs:
•	Module A and Module B provide clean, public APIs to the customers.

Challenges

To achieve this, I explored a few options:
1. Make Core Classes Public:
• If I make the classes/methods public in Module Core, I can access them in Modules A and B.
• However, this also makes them visible to customers, which I want to avoid.
2. Use Internal Access Control:
• Keeping the Core classes internal ensures they aren’t exposed to customers.
• But this restricts access to Module A and Module B since they are separate modules.
3. Alternative Approaches:
• I’ve considered using @_spi (Swift’s special access control for internal APIs across modules), but I’m not sure if this is the best long-term solution.

Questions

1.	What is the best way to share internal functionality between modules without exposing it to SDK customers?
2.	Are there specific Swift access control techniques or project configurations that align with this use case?
3.	Does exposing internal code as public but not including Core as a product in the Swift Package manifest create any hidden risks?

Example Structure

Here is a high-level view of the modules:
• Module Core:
• Contains internal data models, utilities, and shared functionality.
• Example:

// Module Core
internal struct CoreModel {
let id: String
}

•	Module A:
•	Uses CoreModel but provides its own public-facing API.
•	Example:

// Module A
public struct PublicModel {
let coreModel: CoreModel
}

•	Module B:
•	Also uses CoreModel internally.

Expected Outcome

•	Modules A and B can access and use internal classes/methods from Module Core.
•	Customers cannot access or see the internal details of Module Core.
1 Like

The package access modifier keyword was introduced to solve this exact problem. It requires Swift 5.9. Here is the proposal.

4 Likes

@RandomHashTags thank you very much! This is indeed what I was looking for!

1 Like