Bridging interfaces with the Adapter pattern in Swift
Learn how to use the Adapter pattern to integrate third-party APIs or legacy code.
28 Apr 2025 · 3 min read
Sometimes, we adopt libraries or frameworks that don't match our app's internal abstractions. Or, as our architecture evolves, existing components no longer fit with newer code cleanly. Instead of rewriting working code, we can adapt it with the Adapter pattern.
Let's look at how to do it with a practical example.

Let's say our app expects a SearchService protocol that defines how search functionality should work across different parts of the app:
protocol SearchService {func search(query: String) async throws -> [SearchResult]}
But we're integrating a third-party SDK with the following interface:
class ThirdPartySearch {func runSearch(term: String, completion: @escaping ([Any]) -> Void) {// ...}}
The SDK's API uses different method names, a completion handler instead of async/await, and returns untyped results.
Here's how we can bridge that gap:
class ThirdPartySearchAdapter: SearchService {private let thirdPartySearch = ThirdPartySearch()func search(query: String) async throws -> [SearchResult] {try await withCheckedThrowingContinuation { continuation inthirdPartySearch.runSearch(term: query) { rawResults inlet results = rawResults.compactMap { $0 as? SearchResult }continuation.resume(returning: results)}}}}
Now, we can use ThirdPartySearchAdapter anywhere a SearchService is expected.
The Adapter pattern helps us preserve encapsulation and maintain clean separation between domains, making our code more resilient to change.



Newsletter
Like to support my work?
Say hi
Related tags
Articles with related topics
Latest articles and tips



