diff --git a/app/src/util_ios.mm b/app/src/util_ios.mm index df30e71af6..144a91feb5 100644 --- a/app/src/util_ios.mm +++ b/app/src/util_ios.mm @@ -27,6 +27,11 @@ #import #import +using firebase::GetLogLevel; + +// 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 @@ -40,10 +45,12 @@ static void Firebase_setDelegate(id self, SEL _cmd, id de Class new_class = nil; if (delegate) { new_class = [delegate class]; - NSLog(@"Firebase: UIApplication setDelegate: called with class %s (Swizzled)", - class_getName(new_class)); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: UIApplication setDelegate: called with class %s (Swizzled)", + class_getName(new_class)); } else { - NSLog(@"Firebase: UIApplication setDelegate: called with nil delegate (Swizzled)"); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: UIApplication setDelegate: called with nil delegate (Swizzled)"); } if (new_class) { @@ -54,8 +61,10 @@ static void Firebase_setDelegate(id self, SEL _cmd, id de for (int i = 0; i < g_seen_delegate_classes_count; i++) { if (g_seen_delegate_classes[i] == current_super) { superclass_already_seen = true; - NSLog(@"Firebase: Delegate class %s has superclass %s which was already seen. Skipping processing for %s.", - class_getName(new_class), class_getName(current_super), class_getName(new_class)); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Delegate class %s has superclass %s which was already seen. Skipping " + @"processing for %s.", + class_getName(new_class), class_getName(current_super), class_getName(new_class)); break; } } @@ -69,8 +78,9 @@ static void Firebase_setDelegate(id self, SEL _cmd, id de for (int i = 0; i < g_seen_delegate_classes_count; i++) { if (g_seen_delegate_classes[i] == new_class) { direct_class_already_seen = true; - NSLog(@"Firebase: Delegate class %s already seen directly. Skipping processing.", - class_getName(new_class)); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Delegate class %s already seen directly. Skipping processing.", + class_getName(new_class)); break; } } @@ -80,12 +90,14 @@ static void Firebase_setDelegate(id self, SEL _cmd, id de if (g_seen_delegate_classes_count < MAX_SEEN_DELEGATE_CLASSES) { g_seen_delegate_classes[g_seen_delegate_classes_count] = new_class; g_seen_delegate_classes_count++; - NSLog(@"Firebase: Added new delegate class %s to seen list (total seen: %d).", - class_getName(new_class), g_seen_delegate_classes_count); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Added new delegate class %s to seen list (total seen: %d).", + class_getName(new_class), g_seen_delegate_classes_count); if (g_pending_block_count > 0) { - NSLog(@"Firebase: Executing %d pending block(s) for new delegate class: %s.", - g_pending_block_count, class_getName(new_class)); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Executing %d pending block(s) for new delegate class: %s.", + g_pending_block_count, class_getName(new_class)); for (int i = 0; i < g_pending_block_count; i++) { if (g_pending_app_delegate_blocks[i]) { g_pending_app_delegate_blocks[i](new_class); @@ -94,7 +106,8 @@ static void Firebase_setDelegate(id self, SEL _cmd, id de } } } else { - NSLog(@"Firebase Error: Exceeded MAX_SEEN_DELEGATE_CLASSES (%d). Cannot add new delegate class %s or run pending blocks for it.", + NSLog(@"Firebase Error: Exceeded MAX_SEEN_DELEGATE_CLASSES (%d). Cannot add new delegate " + @"class %s or run pending blocks for it.", MAX_SEEN_DELEGATE_CLASSES, class_getName(new_class)); } } @@ -103,7 +116,8 @@ static void Firebase_setDelegate(id self, SEL _cmd, id de // Call the original setDelegate: implementation if (g_original_setDelegate_imp) { - ((void (*)(id, SEL, id))g_original_setDelegate_imp)(self, _cmd, delegate); + ((void (*)(id, SEL, id))g_original_setDelegate_imp)(self, _cmd, + delegate); } else { NSLog(@"Firebase Error: Original setDelegate: IMP not found, cannot call original method."); } @@ -163,28 +177,89 @@ @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) { + if (GetLogLevel() <= kLogLevelDebug) + 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; + if (GetLogLevel() <= kLogLevelDebug) + 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) { + if (GetLogLevel() <= kLogLevelDebug) + 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); + } + } + if (GetLogLevel() <= kLogLevelDebug) + 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 swizzling.", + kFirebaseAppDelegateClassNameKey, appDelegateClassName); + } + } else { + if (appDelegateClassName) { // Key is present but value is invalid (e.g., empty string or + // wrong type). + NSLog(@"Firebase Error: Info.plist key '%@' has an invalid value ('%@'). Proceeding " + @"with default 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:] + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Proceeding with swizzling of [UIApplication setDelegate:]."); Class uiApplicationClass = [UIApplication class]; SEL originalSelector = @selector(setDelegate:); Method originalMethod = class_getInstanceMethod(uiApplicationClass, originalSelector); if (!originalMethod) { - NSLog(@"Firebase Error: Original [UIApplication setDelegate:] method not found for swizzling."); + NSLog( + @"Firebase Error: Original [UIApplication setDelegate:] method not found for swizzling."); 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."); + g_original_setDelegate_imp = previousImp; + if (GetLogLevel() <= kLogLevelDebug) + 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)."); + NSLog(@"Firebase Error: Swizzled [UIApplication setDelegate:], but original IMP was NULL (or " + @"method_setImplementation failed to return the previous IMP)."); } }); } @@ -196,24 +271,33 @@ + (void)load { void RunOnAppDelegateClasses(void (^block)(Class)) { if (g_seen_delegate_classes_count > 0) { - NSLog(@"Firebase: RunOnAppDelegateClasses executing block for %d already seen delegate class(es).", - g_seen_delegate_classes_count); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: RunOnAppDelegateClasses executing block for %d already seen delegate " + @"class(es).", + g_seen_delegate_classes_count); for (int i = 0; i < g_seen_delegate_classes_count; i++) { - if (g_seen_delegate_classes[i]) { // Safety check + if (g_seen_delegate_classes[i]) { // Safety check block(g_seen_delegate_classes[i]); } } } else { - NSLog(@"Firebase: RunOnAppDelegateClasses - no delegate classes seen yet. Block will be queued for future delegates."); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: RunOnAppDelegateClasses - no delegate classes seen yet. Block will be " + @"queued for future delegates."); } // Always try to queue the block for any future new delegate classes. if (g_pending_block_count < MAX_PENDING_APP_DELEGATE_BLOCKS) { g_pending_app_delegate_blocks[g_pending_block_count] = [block copy]; g_pending_block_count++; - NSLog(@"Firebase: RunOnAppDelegateClasses - added block to pending list (total pending: %d). This block will run on future new delegate classes.", g_pending_block_count); + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: RunOnAppDelegateClasses - added block to pending list (total pending: %d). " + @"This block will run on future new delegate classes.", + g_pending_block_count); } else { - NSLog(@"Firebase Error: RunOnAppDelegateClasses - pending block queue is full (max %d). Cannot add new block for future execution. Discarding block.", MAX_PENDING_APP_DELEGATE_BLOCKS); + NSLog(@"Firebase Error: RunOnAppDelegateClasses - pending block queue is full (max %d). Cannot " + @"add new block for future execution. Discarding block.", + MAX_PENDING_APP_DELEGATE_BLOCKS); } } @@ -458,7 +542,7 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func const char *class_name = class_getName(clazz); Method method = class_getInstanceMethod(clazz, name); NSString *selector_name_nsstring = NSStringFromSelector(name); - const char *selector_name = selector_name_nsstring.UTF8String; // Used for logging later + const char *selector_name = selector_name_nsstring.UTF8String; // Used for logging later IMP current_actual_imp = method ? method_getImplementation(method) : nil; @@ -466,16 +550,18 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func if (current_actual_imp == imp) { // Assuming GetLogLevel() and kLogLevelDebug are available here. // Based on previous file content, GetLogLevel is available in this file from util_ios.h. - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Firebase Cache: Method %s on class %s is already swizzled with the target IMP. Skipping re-swizzle.", + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Method %s on class %s is already swizzled with the target IMP. Skipping " + @"re-swizzle.", selector_name, class_name); - } - return; // Already swizzled to the desired implementation + + return; // Already swizzled to the desired implementation } // === End idempotency check === // If we reach here, current_actual_imp is different from imp, or the method didn't exist. - // We now assign original_method_implementation to be current_actual_imp for the rest of the function. + // We now assign original_method_implementation to be current_actual_imp for the rest of the + // function. IMP original_method_implementation = current_actual_imp; // Get the type encoding of the selector from a type_encoding_class (which is a class which @@ -485,9 +571,9 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func assert(type_encoding); NSString *new_method_name_nsstring = nil; - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Firebase Cache: Attempting to register method for %s selector %s", class_name, selector_name); - } + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Attempting to register method for %s selector %s", class_name, selector_name); + if (original_method_implementation) { // Try adding a method with randomized prefix on the name. int retry = kRandomNameGenerationRetries; @@ -502,32 +588,32 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func } const char *new_method_name = new_method_name_nsstring.UTF8String; if (retry == 0) { - NSLog(@"Failed to add method %s on class %s as the %s method already exists on the class. To " - @"resolve this issue, change the name of the method %s on the class %s.", - new_method_name, class_name, new_method_name, new_method_name, class_name); + NSLog( + @"Firebase Error: Failed to add method %s on class %s as the %s method already exists on " + @"the class. To resolve this issue, change the name of the method %s on the class %s.", + new_method_name, class_name, new_method_name, new_method_name, class_name); return; } method_setImplementation(method, imp); // Save the selector name that points at the original method implementation. SetMethod(name, new_method_name_nsstring); - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Registered method for %s selector %s (original method %s 0x%08x)", class_name, - selector_name, new_method_name, + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Registered method for %s selector %s (original method %s 0x%08x)", + class_name, selector_name, new_method_name, static_cast(reinterpret_cast(original_method_implementation))); - } + } else if (add_method) { - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Adding method for %s selector %s", class_name, selector_name); - } + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Adding method for %s selector %s", class_name, selector_name); + // The class doesn't implement the selector so simply install our method implementation. if (!class_addMethod(clazz, name, imp, type_encoding)) { - NSLog(@"Failed to add new method %s on class %s.", selector_name, class_name); + NSLog(@"Firebase Error: Failed to add new method %s on class %s.", selector_name, class_name); } } else { - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Method implementation for %s selector %s not found, ignoring.", class_name, + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Method implementation for %s selector %s not found, ignoring.", class_name, selector_name); - } } } @@ -541,9 +627,9 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func selector_implementation_names_per_selector_[selector_name_nsstring]; const char *class_name = class_getName(clazz); if (!selector_implementation_names) { - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Method not cached for class %s selector %s.", class_name, selector_name); - } + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Method not cached for class %s selector %s.", class_name, selector_name); + return nil; } @@ -561,10 +647,10 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func search_class = clazz; for (; search_class; search_class = class_getSuperclass(search_class)) { const char *search_class_name = class_getName(search_class); - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Searching for selector %s (%s) on class %s", selector_name, + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Searching for selector %s (%s) on class %s", selector_name, selector_implementation_name, search_class_name); - } + Method method = class_getInstanceMethod(search_class, selector_implementation); method_implementation = method ? method_getImplementation(method) : nil; if (method_implementation) break; @@ -572,18 +658,18 @@ void RunOnBackgroundThread(void (*function_ptr)(void *function_data), void *func if (method_implementation) break; } if (!method_implementation) { - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Class %s does not respond to selector %s (%s)", class_name, selector_name, + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Class %s does not respond to selector %s (%s)", class_name, selector_name, selector_implementation_name_nsstring.UTF8String); - } + return nil; } - if (GetLogLevel() <= kLogLevelDebug) { - NSLog(@"Found %s (%s, 0x%08x) on class %s (%s)", selector_name, + if (GetLogLevel() <= kLogLevelDebug) + NSLog(@"Firebase: Found %s (%s, 0x%08x) on class %s (%s)", selector_name, selector_implementation_name_nsstring.UTF8String, static_cast(reinterpret_cast(method_implementation)), class_name, class_getName(search_class)); - } + return method_implementation; } diff --git a/release_build_files/readme.md b/release_build_files/readme.md index 09b9644733..0ecbafb0d9 100644 --- a/release_build_files/readme.md +++ b/release_build_files/readme.md @@ -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 +FirebaseAppDelegateClassName +MyCustomAppDelegate +``` + +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 @@ -654,6 +675,14 @@ workflow use only during the development of your app, not for publicly shipping code. ## Release Notes +### Upcoming Release +- 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.