Skip to content
Tolinku
Tolinku
Sign In Start Free
Deep Linking · · 5 min read

Method Swizzling and Universal Links: What to Watch For

By Tolinku Staff
|
Tolinku industry trends dashboard screenshot for deep linking blog posts

If you have ever integrated a third-party analytics or crash reporting SDK and suddenly found that your Universal Links stopped working, method swizzling is a likely culprit. This is one of the more subtle iOS integration problems because it does not produce a clear error. Your app builds and runs fine, but deep links silently fail to route users to the right content.

This article explains what method swizzling is, how third-party SDKs use it, why it interferes with Universal Link handling, and how to detect and fix these conflicts.

For Universal Links fundamentals, see universal links: everything you need to know. For common deep linking challenges, see deep linking challenges.

What Is Method Swizzling?

Method swizzling is a technique made possible by the Objective-C runtime. It allows you to swap the implementation of one method with another at runtime. Because Swift classes that inherit from Objective-C classes (like UIResponder and UIApplication) participate in the same runtime, swizzling can affect Swift code too.

The core mechanism relies on two runtime functions:

// Conceptual Objective-C equivalent
class_getInstanceMethod(MyClass.self, #selector(originalMethod))
class_getInstanceMethod(MyClass.self, #selector(replacementMethod))
method_exchangeImplementations(originalMethod, replacementMethod)

When a library calls method_exchangeImplementations, it replaces the original method pointer with its own. If it is well-behaved, it will call the original implementation inside its replacement (so the chain continues). If it is not, or if multiple libraries swizzle the same method, the chain can break.

The Objective-C runtime documentation from Apple gives a full explanation of the underlying objc_msgSend dispatch mechanism: Objective-C Runtime Reference.

When a user taps a Universal Link, iOS calls application(_:continue:restorationHandler:) on your UIApplicationDelegate. This is the entry point for your routing logic. A typical implementation looks like this:

func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL else {
        return false
    }

    // Route the URL to the correct screen
    return handleIncomingURL(incomingURL)
}

This method must return true if your app handles the URL. If it returns false, iOS falls back to opening the URL in Safari. See the full delegate reference at UIApplicationDelegate.

How SDKs Break This With Swizzling

Many popular third-party SDKs, including analytics platforms, crash reporters, and attribution tools, swizzle UIApplicationDelegate methods during initialization. Their goal is to automatically capture lifecycle events without requiring you to add code to each delegate method manually.

The problem arises in several ways:

Incomplete chaining. The SDK swizzles your delegate method but does not correctly call through to the original implementation. Your handler never runs.

Order-dependent behavior. Two SDKs both swizzle the same method. Depending on initialization order, one SDK's swizzle may replace the other's, breaking the chain entirely.

Return value hijacking. The SDK's swizzled implementation returns false before your code gets a chance to return true, causing iOS to think the URL was not handled.

Scene-based architecture issues. In apps using UIWindowSceneDelegate, Universal Links arrive via scene(_:continue:) instead of the app delegate method. SDKs that only swizzle the app delegate method will miss the call entirely in these apps.

Recognizing a Swizzling Conflict

The symptoms are easy to confuse with other Universal Link problems (misconfigured AASA files, entitlement issues, etc.). A swizzling conflict typically presents as:

  • Universal Links work in a clean project but stop working after adding an SDK.
  • Links open Safari instead of the app, even though the AASA file and entitlements are correct.
  • Links work on the simulator but not on a device (or vice versa), depending on SDK behavior differences.
  • The behavior is inconsistent. Some links work and some do not, depending on timing or SDK initialization state.

For iOS version-specific behavior changes, see universal links changes in iOS 17.

Debugging Swizzling Conflicts

Set a breakpoint on the delegate method. Open your AppDelegate.swift, add a breakpoint inside application(_:continue:restorationHandler:), and tap a Universal Link. If the breakpoint is never hit, the method is either not being called or has been swizzled away. If the breakpoint is hit, you can step through and confirm your handler returns true.

Use the call stack to trace swizzling. When the breakpoint hits, check the call stack in Xcode's debug navigator. If you see SDK class names between UIKit and your delegate method, that SDK has swizzled the method. The call stack will reveal the full chain.

Disable SDKs one at a time. Comment out SDK initialization calls in application(_:didFinishLaunchingWithOptions:) and test Universal Links after each removal. When links start working, you have identified the offending SDK.

Check SDK documentation for swizzling opt-out. Many SDKs provide a flag or plist key to disable swizzling. For example, analytics and attribution SDKs often support an AutomaticScreenReportingEnabled or similar key. Check the SDK's configuration documentation for a swizzling opt-out option.

Enable AASA validation first. Before debugging swizzling, confirm the AASA file and entitlements are correct. Use Apple's AASA Validator and check the Apple CDN cache for your domain.

Fixing Swizzling Conflicts

Once you have identified a swizzling conflict, you have a few options.

Opt out of swizzling and call the SDK manually. Most SDKs that swizzle delegate methods provide a way to disable the automatic swizzling and instead call their tracking methods directly. This is the cleanest fix:

func application(
    _ application: UIApplication,
    continue userActivity: NSUserActivity,
    restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void
) -> Bool {
    // Manually inform the SDK about the user activity
    AnalyticsSDK.shared.trackUserActivity(userActivity)

    // Then run your own routing logic
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL else {
        return false
    }

    return handleIncomingURL(incomingURL)
}

Control initialization order. If you cannot opt out of swizzling, initialize your own delegate hooks before the third-party SDKs. Place your setup code at the very beginning of application(_:didFinishLaunchingWithOptions:), before any SDK initialization. The last swizzle applied in the chain typically wins, so order matters.

File an issue with the SDK vendor. If an SDK unconditionally swizzles methods without providing an opt-out, that is a bug. Report it. Most mature SDK teams will add an opt-out flag when the issue is raised.

Apps targeting iOS 13 and later that use UIWindowSceneDelegate receive Universal Links in the scene delegate, not the app delegate. The relevant method is:

func scene(
    _ scene: UIScene,
    continue userActivity: NSUserActivity
) {
    guard userActivity.activityType == NSUserActivityTypeBrowsingWeb,
          let incomingURL = userActivity.webpageURL else {
        return
    }

    handleIncomingURL(incomingURL)
}

SDKs that only swizzle the app delegate will not intercept this call. However, if an SDK is aggressive about swizzling and patches both the app delegate and scene delegate, you may encounter conflicts in both places.

Apple's documentation on scene-based deep linking is at Scenes – UIKit.

Tolinku manages your AASA file and deep link route configuration, so when debugging swizzling conflicts, you can rule out infrastructure issues. If the AASA file and routing are managed by Tolinku, you know the problem is app-side (swizzling, scene delegate setup), not server-side. The iOS SDK provides a lightweight integration for handling incoming Universal Links in your delegate method. See the Universal Links developer guide for setup details.

For the complete Universal Links guide, see universal links: everything you need to know. For iOS troubleshooting, see the iOS troubleshooting guide.

Get deep linking tips in your inbox

One email per week. No spam.

Ready to add deep linking to your app?

Set up Universal Links, App Links, deferred deep linking, and analytics in minutes. Free to start.