Description
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:
- Tapping the "Call Delegate" button yields a
application(_:didRegisterForRemoteNotificationsWithDeviceToken:) was called with 11 bytes
log message. - Tap "Configure Firebase App"
- 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
{
"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
Replace this line with the contents of your Podfile.lock!