Skip to content

iOS: Allow specifying your AppDelegate class's name in the Info.plist file. #1741

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
59 changes: 52 additions & 7 deletions app/src/util_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
#import <UIKit/UIKit.h>
#import <objc/runtime.h>

// Key used in Info.plist to specify a custom AppDelegate class name.
static NSString *const kFirebaseAppDelegateClassNameKey = @"FirebaseAppDelegateClassName";

#define MAX_PENDING_APP_DELEGATE_BLOCKS 8
#define MAX_SEEN_DELEGATE_CLASSES 32

Expand Down Expand Up @@ -163,6 +166,55 @@ @implementation UIApplication (FirebaseAppDelegateSwizzling)
+ (void)load {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSString *appDelegateClassName = [[NSBundle mainBundle] objectForInfoDictionaryKey:kFirebaseAppDelegateClassNameKey];

if (appDelegateClassName && [appDelegateClassName isKindOfClass:[NSString class]] && appDelegateClassName.length > 0) {
Class specificClass = NSClassFromString(appDelegateClassName);
if (specificClass) {
NSLog(@"Firebase: Info.plist key '%@' found. Targeting AppDelegate class: %@. Swizzling of [UIApplication setDelegate:] will be skipped.",
kFirebaseAppDelegateClassNameKey, appDelegateClassName);

// Set this class as the sole "seen" delegate for Firebase processing.
// g_seen_delegate_classes_count should be 0 here in +load, but clear just in case.
for (int i = 0; i < g_seen_delegate_classes_count; i++) {
g_seen_delegate_classes[i] = nil;
}
g_seen_delegate_classes[0] = specificClass;
g_seen_delegate_classes_count = 1;
NSLog(@"Firebase: %@ is now the only delegate class Firebase will initially process.", appDelegateClassName);

// If there are already blocks pending (e.g., from other Firebase components' +load methods),
// execute them now for the specified delegate. These blocks will remain in the pending
// queue, mirroring the behavior of the original swizzled setDelegate: method which also
// does not clear pending blocks after execution (as they might apply to future delegates).
// In this Info.plist mode, however, Firebase won't process further setDelegate: calls.
if (g_pending_block_count > 0) {
NSLog(@"Firebase: +load (Info.plist Mode) - Executing %d PENDING block(s) for specified delegate: %@. (Blocks are not removed from queue).",
g_pending_block_count, NSStringFromClass(specificClass));
for (int i = 0; i < g_pending_block_count; i++) {
if (g_pending_app_delegate_blocks[i]) {
g_pending_app_delegate_blocks[i](specificClass);
}
}
NSLog(@"Firebase: +load (Info.plist Mode) - Pending blocks executed for specific delegate.");
}
// Skip swizzling. g_original_setDelegate_imp remains NULL.
return;
} else {
NSLog(@"Firebase Error: Info.plist key '%@' specifies class '%@', which was not found. Proceeding with default [UIApplication setDelegate:] swizzling.",
kFirebaseAppDelegateClassNameKey, appDelegateClassName);
}
} else {
if (appDelegateClassName) { // Key is present but value is invalid (e.g., empty string or wrong type).
NSLog(@"Firebase Warning: Info.plist key '%@' has an invalid value ('%@'). Proceeding with default [UIApplication setDelegate:] swizzling.",
kFirebaseAppDelegateClassNameKey, appDelegateClassName);
} else { // Key is not present.
// This is the default case, no special logging needed here beyond the swizzling log itself.
}
}

// Standard behavior: Swizzle [UIApplication setDelegate:]
NSLog(@"Firebase: Proceeding with swizzling of [UIApplication setDelegate:].");
Class uiApplicationClass = [UIApplication class];
SEL originalSelector = @selector(setDelegate:);
Method originalMethod = class_getInstanceMethod(uiApplicationClass, originalSelector);
Expand All @@ -172,18 +224,11 @@ + (void)load {
return;
}

// Replace the original method's implementation with Firebase_setDelegate
// and store the original IMP.
IMP previousImp = method_setImplementation(originalMethod, (IMP)Firebase_setDelegate);
if (previousImp) {
g_original_setDelegate_imp = previousImp;
NSLog(@"Firebase: Successfully swizzled [UIApplication setDelegate:] and stored original IMP.");
} else {
// This would be unusual - method_setImplementation replacing a NULL IMP,
// or method_setImplementation itself failed (though it doesn't typically return NULL on failure,
// it might return the new IMP or the old one depending on versions/runtime).
// More robustly, g_original_setDelegate_imp should be checked before use.
// For now, this logging indicates if previousImp was unexpectedly nil.
NSLog(@"Firebase Error: Swizzled [UIApplication setDelegate:], but original IMP was NULL (or method_setImplementation failed to return the previous IMP).");
}
});
Expand Down
35 changes: 32 additions & 3 deletions release_build_files/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -537,9 +537,30 @@ addition to any you may have implemented.

The Firebase Cloud Messaging library needs to attach
handlers to the application delegate using method swizzling. If you are using
these libraries, at load time, Firebase will identify your `AppDelegate` class
and swizzle the required methods onto it, chaining a call back to your existing
method implementation.
these libraries, at load time, Firebase will typically identify your `AppDelegate`
class and swizzle the required methods onto it.

#### Specifying Your AppDelegate Class Directly (iOS)

For a more direct approach, or if you encounter issues with the default
method swizzling, you can explicitly tell Firebase which class is your
application's `AppDelegate`. To do this, add the `FirebaseAppDelegateClassName`
key to your app's `Info.plist` file:

* **Key:** `FirebaseAppDelegateClassName`
* **Type:** `String`
* **Value:** Your AppDelegate's class name (e.g., `MyCustomAppDelegate`)

**Example `Info.plist` entry:**
```xml
<key>FirebaseAppDelegateClassName</key>
<string>MyCustomAppDelegate</string>
```

If this key is provided with a valid class name, Firebase will use that class
directly for its AppDelegate-related interactions. If the key is not present,
is invalid, or the class is not found, Firebase will use its standard method
swizzling approach.

### Custom Android Build Systems

Expand Down Expand Up @@ -654,6 +675,14 @@ workflow use only during the development of your app, not for publicly shipping
code.

## Release Notes
### 12.9.0 (Upcoming)
- Changes
- iOS: Added an option to explicitly specify your app's `AppDelegate` class
name via the `FirebaseAppDelegateClassName` key in `Info.plist`. This
provides a more direct way for Firebase to interact with your specified
AppDelegate. See "Platform Notes > iOS Method Swizzling >
Specifying Your AppDelegate Class Directly (iOS)" for details.

### 12.8.0
- Changes
- General (iOS): Update to Firebase Cocoapods version 11.14.0.
Expand Down
Loading