firebase / firebase/firebase-ios-sdk

Swizzle breaks Application Delegates in Swift

Open
#13,566 2 comments 2 reactions 0 assignees View on GitHub
GoogleUtilities
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.