0

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.


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí