Best Practices for Managing Third-Party Libraries in Multi-Module iOS Apps

When building a multi-module iOS app using Swift Package Manager, importing third-party libraries like Firebase directly into multiple feature modules quickly becomes messy. It leads to duplicated configurations, tight coupling, and hard-to-maintain code.

The best practice is to create a wrapper module (e.g. FirebaseModule) that imports and manages all Firebase logic in one place. Then, other modules like LoginModule, Data, etc., only depend on this wrapper — they never import the third-party library directly.


1. Create a Wrapper Module for the SDK (e.g. Firebase)

Modules/FirebaseModule/Package.swift:

// Only in the wrapper module
.package(url: "<https://github.com/firebase/firebase-ios-sdk.git>", branch: "main"),
.target(
  name: "FirebaseModule",
  dependencies: [
    .product(name: "FirebaseAuth", package: "firebase-ios-sdk"),
    .product(name: "FirebaseFirestore", package: "firebase-ios-sdk"),
    .product(name: "FirebaseStorage", package: "firebase-ios-sdk")
  ]
)

2. Wrap SDK Logic and Use Protocols

CommonModule/AuthService.swift:

public protocol AuthService {
    func login(email: String, password: String) -> AnyPublisher<User, Error>
}

FirebaseModule/FirebaseAuthService.swift:

import FirebaseAuth
import Combine

public class FirebaseAuthService: AuthService {
    public init() {}

    func login(email: String, password: String) -> AnyPublisher<Void, Error> {
          Future { promise in
              Auth.auth().signIn(withEmail: email, password: password) { result, error in
                  if let error = error {
                      promise(.failure(error))
                  } else {
                      promise(.success(()))
                  }
              }
          }.eraseToAnyPublisher()
      }
}

3. Other Modules Import Only the Wrapper

Modules/Data/Package.swift:

.package(path: "../FirebaseModule")
.target(
  name: "Data",
  dependencies: ["FirebaseModule", "CommonModule", ...]
)

In your Swift code:

import FirebaseModule

let authService: AuthService = FirebaseAuthService()

Benefits of This Pattern

  • Clean separation of concerns

  • No direct Firebase SDK imports in features

  • Easier unit testing via protocols

  • Centralized SDK configuration

  • Avoids duplication and compile issues


This practice applies to any third-party SDK (Firebase, Alamofire, Realm, GRDB, etc.). Keep them wrapped in a dedicated module, expose interfaces via protocols, and let your app scale cleanly.

Thanks for Reading! ✌️

If you have any questions or corrections, please leave a comment below or contact me via my LinkedIn account Pham Trung Huy.

Happy coding 🍻

2
Subscribe to my newsletter

Read articles from Phạm Trung Huy directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Phạm Trung Huy
Phạm Trung Huy

👋 I am a Mobile Developer based in Vietnam. My passion lies in continuously pushing the boundaries of my skills in this dynamic field.