firebase / firebase/firebase-ios-sdk
Swizzle breaks Application Delegates in Swift
- Dominant language
- C++
- Stars
- 6.7k
- Forks
- 1.8k
- Avg merge
- 2d 18h
- Merged PRs (30d)
- 75
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](https://github.com/google/GoogleUtilities/blob/d6cf51c4617a34bbce94a25428051d1aa98c2e44/GoogleUtilities/AppDelegateSwizzler/GULAppDelegateSwizzler.m#L478-L488), 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.
```swift
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](https://github.com/user-attachments/files/16837107/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
```shell
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!
```
Contributor guide
Assessment
This issue has not been assessed yet.