How We Migrated Our Ad SDKs from CocoaPods to Swift Package Manager at Grindr

At Grindr, we’re always looking for ways to simplify our development process. One goal that kept coming up was: “Let’s use only one dependency manager across all our codebase.” And that meant saying goodbye to CocoaPods and fully embracing Swift Package Manager (SPM).
For the most part, it was a smooth transition until we got to our Ads stack, which includes AppLovin and a variety of mediation adapters. That’s where things got tricky.
This is the story of how we worked around those limitations, reused what CocoaPods gave us, and built a tool to generate a working Swift package from a bunch of .xcframeworks.
Why did we need to build our own Swift package?
If you’ve ever worked with AppLovin, you know the core SDK supports SPM. But what about the mediation adapters (Unity, GAM, HyBid…)? Not so much.
“Note, this Swift package only includes the main AppLovin MAX SDK. We currently do not support installing MAX mediation network adapters using Swift Package Manager.”
https://github.com/AppLovin/AppLovin-MAX-Swift-Package
At the time, most of AppLovin’s adapters were still only available via CocoaPods. That meant if we wanted to use SPM for everything (hint: which we did), we’d need a custom workaround.
After a bit of digging, we noticed something interesting: when you run pod install, CocoaPods downloads precompiled .xcframeworks for many of these dependencies.
That gave us an idea: What if we could take those .xcframeworks and use them to build our own Swift package?
This is how our idea came through...
Step One: Build a Fake Project
To test our idea, we created a minimal project with a Podfile that included AppLovin and all the mediation adapters we use. We ran pod install, and just like we hoped, many of the SDKs were downloaded as .xcframework bundles.
So far, so good.
But not everything was perfect. Some dependencies didn’t come with .xcframeworks at all; they were just pulled in as source code.
Step Two: Hunt for Binaries
For those source-based dependencies, we had to get creative, e.g HyBid.
We visited GitHub releases, vendor documentation, or artifact servers to manually find the correct .xcframework versions. It wasn’t elegant, but it worked, and once we had all the binaries in one place, we could move forward.
Step Three: Automate All the Things
We knew we didn’t want to repeat this process manually every time. So we built a set of scripts to automate everything:
- Generate the temporary CocoaPods project (XcodeGen)
- Run pod install
- Collect all the .xcframeworks
- Organize them into a clean directory (automated with a script)
- Generate a Package.swift file using binaryTargets
- Automatically compute the checksums for remote .zip packages (like AppLovin and HyBid)
In the end, we had a reproducible pipeline that turns a set of binary frameworks into a proper Swift package.


Validating Everything Works
Once we had the package integrated into the main app, we did a full test sweep:
Everything rendered correctly and loaded as expected, but as with any dependency migration, we hit a few bumps along the way.
Duplicate Symbols and Module Conflicts
Initially, we noticed that some builds failed due to duplicate symbol errors. This happened because a few .xcframeworks shared internal symbols across modules, or some were being linked more than once due to improper dependency declarations.
To resolve this, we had to carefully restructure the binary targets, ensuring that:
- Dependencies were declared only once per product.
- Wrapper targets were created for certain SDKs (e.g., AppLovin) to control linker behavior.
- Shared modules weren’t pulled in multiple times through transitive dependencies.
Controlling Linkage Behavior in SPM
Unlike CocoaPods, SPM handles linking automatically, which usually works well, but for performance-sensitive frameworks like ad SDKs, we needed more control.
We introduced custom .target entries with explicit linkerSettings, making sure the required system frameworks were statically linked. This helped us:
- Avoid increased launch times.
- Prevent SPM from defaulting to dynamic linking for certain modules.
- Match the behavior of our previous CocoaPods integration.
Handling Google SDKs with High Coupling
Some Google libraries (like GoogleMobileAds) have complex transitive dependencies, and managing them manually through .xcframework references proved brittle.
What We Learned
- CocoaPods remains a practical tool for resolving and extracting binary artifacts
While SPM is our long-term solution, CocoaPods can still serve a transitional purpose, particularly when dealing with SDKs that don’t yet expose precompiled binaries via .xcframework.zip. It reliably resolves versioned dependencies and materializes the .xcframeworks we can later repackage for SPM. - Swift Package Manager supports complex setups via binaryTarget and custom linkage
SPM’s binaryTarget support allows for clean integration of precompiled frameworks, even in ecosystems not originally designed for it. Combined with target configurations for linking system frameworks and setting linkerSettings, it provides the necessary tools to replace CocoaPods in binary-focused setups. - Automating the pipeline eliminates manual, error-prone work
By scripting the generation of Package.swift, dependency resolution, and checksum calculation, we created a reproducible and scalable process. This ensures consistency across environments (CI/local), reduces onboarding friction, and avoids configuration drift. - Explicit project structure improves maintainability and clarity
Separating concerns into dedicated directories — one for framework artifacts, one for script logic, and one for the generated package — brings transparency to the system. This makes it easier to maintain, debug, and evolve the tooling over time without unintended side effects.
https://developer.apple.com/documentation/xcode/distributing-binary-frameworks-as-swift-packages
https://www.emergetools.com/blog/posts/make-your-ios-app-smaller-with-dynamic-frameworks#building-our-xcframework





.jpg)
.jpg)
.jpg)




.jpg)