firebase / firebase-ios-sdk

Firebase SDK for Apple App Development
https://firebase.google.com
Apache License 2.0
5.66k stars 1.48k forks source link

Swizzle breaks Application Delegates in Swift #13566

Open Supereg opened 2 months ago

Supereg commented 2 months ago

Description

The Firebase SDK uses swizzeling to transparently access the APNS device token by overriding methods like application(_:didRegisterForRemoteNotificationsWithDeviceToken:) of the currently associated app delegate. Looking at the relevant source code, it seems the intention is to have the existing app delegates continue working. However, currently it seems like this doesn't work at all. At least in Swift, it completely prevents the original app delegate method from being called.

Reproducing the issue

Here is a minimal example. This example assumes a GoogleService-Info.plist configured in the Xcode project.

class AppDelegateA: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        print("\(Self.self) \(#function) was called with \(deviceToken)")
    }
}

class AppDelegateB: AppDelegateA {}

@main
struct FirebaseSwizzleReproductionApp: App {
    @UIApplicationDelegateAdaptor(AppDelegateA.self)
    private var delegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        NavigationStack {
            List {
                Button("Configure Firebase App") {
                    FirebaseApp.configure()
                }
                Button("Call Delegate") {
                    guard let delegate = UIApplication.shared.delegate else {
                        print("Didn't find an associated delegate!")
                        return
                    }

                    let data = "Hello World".data(using: .utf8)!
                    delegate.application?(UIApplication.shared, didRegisterForRemoteNotificationsWithDeviceToken: data)
                }
            }
                .navigationTitle("Swizzle Bug")
        }
    }
}

You can observe the following:

  1. Tapping the "Call Delegate" button yields a application(_:didRegisterForRemoteNotificationsWithDeviceToken:) was called with 11 bytes log message.
  2. Tap "Configure Firebase App"
  3. Tap "Call Delegate" button again and there will not be another log statement printed indicating that the original delegate method was not called.

Our original suspicion was that this only happens in inheritance scenarios (the reason for the definition of AppDelegateB). However, while building a minimal reproducible example, it showed that the issue is present with any (Swift) app delegate implementation.

I attached the full Xcode project of the example as a zip archive. FirebaseSwizzleReproduction.zip

Firebase SDK Version

11.1

Xcode Version

16, 15.4

Installation Method

Swift Package Manager

Firebase Product(s)

Infrastructure

Targeted Platforms

N/A

Relevant Log Output

AppDelegateA application(_:didRegisterForRemoteNotificationsWithDeviceToken:) was called with 11 bytes
11.1.0 - [FirebaseCore][I-COR000001] Configuring the default app.
11.1.0 - [GoogleUtilities/AppDelegateSwizzler][I-SWZ001008] Successfully created App Delegate Proxy automatically. To disable the proxy, set the flag GoogleUtilitiesAppDelegateProxyEnabled to NO (Boolean) in the Info.plist
11.1.0 - [FirebaseAuth][I-AUT000006] Assuming prod APNs token type on simulator.

If using Swift Package Manager, the project's Package.resolved

Expand Package.resolved snippet
```json { "originHash" : "a1569f9895aa2be8e24832f98525d5da4eb90b5d158a82691c15b47eb72a13d7", "pins" : [ { "identity" : "abseil-cpp-binary", "kind" : "remoteSourceControl", "location" : "https://github.com/google/abseil-cpp-binary.git", "state" : { "revision" : "194a6706acbd25e4ef639bcaddea16e8758a3e27", "version" : "1.2024011602.0" } }, { "identity" : "app-check", "kind" : "remoteSourceControl", "location" : "https://github.com/google/app-check.git", "state" : { "revision" : "21fe1af9be463a359aaf8d96789ef73fc3760d09", "version" : "11.0.1" } }, { "identity" : "firebase-ios-sdk", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/firebase-ios-sdk.git", "state" : { "revision" : "9118aca998dbe2ceac45d64b21a91c6376928df7", "version" : "11.1.0" } }, { "identity" : "googleappmeasurement", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleAppMeasurement.git", "state" : { "revision" : "07a2f57d147d2bf368a0d2dcb5579ff082d9e44f", "version" : "11.1.0" } }, { "identity" : "googledatatransport", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleDataTransport.git", "state" : { "revision" : "617af071af9aa1d6a091d59a202910ac482128f9", "version" : "10.1.0" } }, { "identity" : "googleutilities", "kind" : "remoteSourceControl", "location" : "https://github.com/google/GoogleUtilities.git", "state" : { "revision" : "53156c7ec267db846e6b64c9f4c4e31ba4cf75eb", "version" : "8.0.2" } }, { "identity" : "grpc-binary", "kind" : "remoteSourceControl", "location" : "https://github.com/google/grpc-binary.git", "state" : { "revision" : "f56d8fc3162de9a498377c7b6cea43431f4f5083", "version" : "1.65.1" } }, { "identity" : "gtm-session-fetcher", "kind" : "remoteSourceControl", "location" : "https://github.com/google/gtm-session-fetcher.git", "state" : { "revision" : "a2ab612cb980066ee56d90d60d8462992c07f24b", "version" : "3.5.0" } }, { "identity" : "interop-ios-for-google-sdks", "kind" : "remoteSourceControl", "location" : "https://github.com/google/interop-ios-for-google-sdks.git", "state" : { "revision" : "2d12673670417654f08f5f90fdd62926dc3a2648", "version" : "100.0.0" } }, { "identity" : "leveldb", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/leveldb.git", "state" : { "revision" : "a0bc79961d7be727d258d33d5a6b2f1023270ba1", "version" : "1.22.5" } }, { "identity" : "nanopb", "kind" : "remoteSourceControl", "location" : "https://github.com/firebase/nanopb.git", "state" : { "revision" : "b7e1104502eca3a213b46303391ca4d3bc8ddec1", "version" : "2.30910.0" } }, { "identity" : "promises", "kind" : "remoteSourceControl", "location" : "https://github.com/google/promises.git", "state" : { "revision" : "540318ecedd63d883069ae7f1ed811a2df00b6ac", "version" : "2.4.0" } }, { "identity" : "swift-protobuf", "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-protobuf.git", "state" : { "revision" : "edb6ed4919f7756157fe02f2552b7e3850a538e5", "version" : "1.28.1" } } ], "version" : 3 } ```

If using CocoaPods, the project's Podfile.lock

Expand Podfile.lock snippet
```yml Replace this line with the contents of your Podfile.lock! ```
google-oss-bot commented 2 months ago

I couldn't figure out how to label this issue, so I've labeled it for a human to triage. Hang tight.

Supereg commented 2 months ago

It seems that this has been reported before (2 years ago) https://github.com/firebase/firebase-ios-sdk/issues/10417