Adyen Tech

Insights from the team building the world’s payments infrastructure.

Preventing Accidental API Breaks: A Swift Developer’s Guide to API Diffing

Adyen
5 min readDec 16, 2024

--

Picture this: Your team just shipped a new SDK version, and suddenly, your Slack channels light up with messages from frustrated developers. The reason? An accidental breaking change in your public API that slipped through code review. Sound familiar?

The Challenge of API Stability

As SDK developers, we struggle to evolve our APIs and maintain stability. One wrong move — like removing a parameter or changing a method signature — can break our clients’ applications. While Swift’s robust type system helps catch many issues at compile-time, detecting API-breaking changes before they reach production requires specialized tooling.

Available Solutions: From Simple to Sophisticated

In this article, I will present the available solutions with their benefits and challenges:

  • Pure Swift Package Manager
  • xcodebuild & swift-api-digester
  • Swift Public API diff

Swift Package Manager’s Built-in Tool (and Why It’s Not Enough)

A first approach to detect breaking API changes might be to look at SPM. It ships with a tool that promises to detect API-breaking changes by comparing them against a previous version:

swift package diagnose-api-breaking-changes [commit/branch/tag]

However, there’s a catch: while this works great for pure Swift packages, it falls short for projects targeting iOS. Even with attempts to force iOS SDK usage:

swift package \
-Xswiftc "-sdk" \
-Xswiftc "$(xcrun - sdk iphonesimulator - show-sdk-path)" \
-Xswiftc "-target" \
-Xswiftc "x86_64-apple-ios18.1-simulator" \
diagnose-api-breaking-changes [commit/branch/tag]

For more details about the drawbacks, refer to discussions on the Swift Forums and this thread.

A More Robust Approach: xcodebuild + swift-api-digester

This combination offers a more suitable option for iOS projects. Here’s how to use it:

1) First, build your project

xcodebuild \
-scheme "YOUR_TARGET_NAME" \
-derivedDataPath .build \
-sdk "$(xcrun - sdk iphonesimulator - show-sdk-path)" \
-target "x86_64-apple-ios18.1-simulator" \
-destination "generic/platform=iOS Simulator"

2) Run the command to diagnose API changes

xcrun swift-api-digester -diagnose-sdk \
-abi \
-module "YOUR_TARGET_NAME" \
-use-interface-for-module "YOUR_TARGET_NAME" \
-BI …/baseline_version/.build/Build/Products/Debug-iphonesimulator \
-I …/updated_version/.build/Build/Products/Debug-iphonesimulator \
-bsdk "$(xcrun - sdk iphonesimulator - show-sdk-path)" \
-sdk "$(xcrun - sdk iphonesimulator - show-sdk-path)" \
-target "x86_64-apple-ios18.1-simulator" \
-o api_diff.txt

3) Verify the output with the differences

The Power Move: Swift Public API Diff

We created and made open-source the Swift Public API Diff tool as an alternative to swift-api-digester. Swift Public API Diff provides more detailed and contextual information, making it simpler to understand changes during code reviews.

It provides a detailed summary of changes and lets you specify the desired access level. As an open-source project, it can be customized to meet individual needs and easily integrate into your existing workflows.

Swift Public API Diff leverages .swiftinterface files that contain rich information about your APIs.

Here’s how to generate these files independently.

xcodebuild clean build \
-scheme "YOUR_TARGET_NAME" \
-derivedDataPath .build \
-sdk "$(xcrun - sdk iphonesimulator - show-sdk-path)" \
-destination "generic/platform=iOS" \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES

The .swiftinterface files are located inside the .swiftmodule directory of the scheme within your specified -derivedDataPath.

Depending on the usage of the @_spi and/or package access modifiers, there can be multiple .swiftinterface definitions with the following extensions:

  • .private.swiftinterface (exposes @_spi interfaces + package & public)
  • .package.swiftinterface (exposes package interfaces + public)
  • .swiftinterface (exposes only the public interface)

The Swift Public API Diff can generate the .swiftinterface files for you and uses swift-syntax to parse these files into a Swift model, which allows the elements to be compared individually.

Below, you can find an example of how you run the diff on two remote versions of a GitHub project:

swift run public-api-diff \
project \
- platform iOS \
- new "develop~https://github.com/Adyen/adyen-ios.git" \
- old "5.12.0~https://github.com/Adyen/adyen-ios.git"

For more examples and use-cases you can refer to the Github page.

The output is formatted in an easy-to-understand way, highlighting additions, removals, and changes.

Here is example of how the markdown output looks as a Github comment:

Best Practices for Maintaining API Stability

Delivering stable APIs is crucial to ensure that new versions do not affect existing applications while still incorporating new features and bug fixes.

  1. Add API diffing to your CI pipeline: This helps catch breaking changes before they make it to your main branch. Check out how Adyen iOS uses GitHub actions to detect API changes.
  2. Use semantic versioning: Adopt semantic versioning to communicate the nature of changes to your users.
  3. Document breaking changes: Keep a clear changelog and provide a migration guide for major SDK upgrades.
  4. Plan for API evolution: Always consider backward compatibility when designing new features.
  5. Deprecation strategy: Announce deprecations in advance to ensure developers are prepared when functionalities are removed from the SDK.

Taking Action

Start by integrating API diffing into your development workflow:

  1. Choose the tool that best fits your needs (At Adyen, we’ve made Swift Public API Diff an indispensable tool for our iOS projects)
  2. Set up automated checks in your CI pipeline.
  3. Establish a process for reviewing and communicating API changes.
  4. Create guidelines for API evolution in your team.

Remember: The best API break is the one that never happens. With these tools and practices in place, you can evolve your SDK with confidence while keeping your users happy.

Ready to try it out? Start with Swift Public API Diff in your next PR review and see the difference it makes in catching potential breaks before they reach your users.

--

--

Adyen Tech
Adyen Tech

Published in Adyen Tech

Insights from the team building the world’s payments infrastructure.

Adyen
Adyen

Written by Adyen

Development and design stories from the company building the world’s payments infrastructure. https://www.adyen.com/careers/