-
Notifications
You must be signed in to change notification settings - Fork 124
Refactor ForEachAppDelegateClass for iOS into a new function that swizzles [UIApplication setDelegate:] to obtain App Delegate classes. #1737
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
Open
jonsimantov
wants to merge
15
commits into
main
Choose a base branch
from
refactor-forEachAppDelegateClass-ios
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
+383
−96
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This commit refactors the `ForEachAppDelegateClass` function in `app/src/util_ios.mm`. Instead of scanning all runtime classes to find UIApplicationDelegate implementers, it now relies on method swizzling. The `[UIApplication setDelegate:]` method is swizzled at startup. When `setDelegate:` is called, the class of the actual application delegate is captured and stored globally (static to the .mm file). `ForEachAppDelegateClass` now uses this stored class. If it's called before `setDelegate:` has been invoked, the block passed to `ForEachAppDelegateClass` is queued. This queued block is then executed once the delegate is set via the swizzled `setDelegate:` method. This approach is more efficient and directly targets the actual App Delegate class used by the application. Key changes: - Added `Firebase_setDelegate` C function as the swizzled implementation. - Introduced `UIApplication(FirebaseAppDelegateSwizzling)` category with a `+load` method to perform the swizzling. - Uses `method_setImplementation` for swizzling and stores the original IMP. - Global static variables `g_app_delegate_class`, `g_original_setDelegate_imp`, and `g_pending_app_delegate_block` manage the state within `util_ios.mm`. - Modified `ForEachAppDelegateClass` to use the new mechanism and queue blocks if the delegate is not yet known.
Integration test with FLAKINESS (succeeded after retry)Requested by @jonsimantov on commit 99bac53
Add flaky tests to go/fpl-cpp-flake-tracker |
I replaced some logging calls with NSLog for better stability during early app startup. I also removed some unnecessary comments and unused include statements to keep things clean.
Modified `util_ios.mm` to support queueing multiple blocks (up to 8, defined by `MAX_PENDING_APP_DELEGATE_BLOCKS`) if `ForEachAppDelegateClass` is called before `[UIApplication setDelegate:]` is invoked. Changes include: - Replaced single pending block storage with a C array of block pointers and a counter (`g_pending_app_delegate_blocks` and `g_pending_block_count`). - `ForEachAppDelegateClass` now adds blocks to this array if the app delegate is not yet known. If the array is full, an error is logged and the block is discarded. - `Firebase_setDelegate` (the swizzled method) now iterates through all pending blocks in the array. If a valid delegate is being set, it executes each pending block. If the delegate is being set to nil, it clears all pending blocks. The array count is reset in both cases. - Added `#define MAX_PENDING_APP_DELEGATE_BLOCKS 8` for configurability.
- I removed extraneous developmental comments from app/src/util_ios.mm for better code clarity. - I updated a call site of firebase::util::RunOnAppDelegate (formerly ForEachAppDelegateClass) in messaging/src/ios/messaging.mm to use the new function name.
jonsimantov
commented
Jun 18, 2025
jonsimantov
commented
Jun 18, 2025
jonsimantov
commented
Jun 18, 2025
jonsimantov
commented
Jun 18, 2025
- I updated the documentation for RunOnAppDelegateClasses (formerly RunOnAppDelegate) in app/src/util_ios.h to accurately reflect its new behavior. - I renamed RunOnAppDelegate to RunOnAppDelegateClasses in all relevant locations (declaration, definition, internal logs, and call sites in invites and messaging modules) for clarity. - I removed the specified extraneous code comments from app/src/util_ios.mm and app/src/invites/ios/invites_ios_startup.mm.
I modified `ClassMethodImplementationCache::ReplaceOrAddMethod` in app/src/util_ios.mm to prevent re-swizzling a method if it's already swizzled with the target implementation. This is done by checking if the current method IMP is identical to the incoming IMP; if so, the function returns early. This resolves a recursive call issue observed when App Delegate hooks were applied multiple times to the same effective class via different GUL-proxied delegate instances. I also included a final cleanup of specified iterative code comments.
Modified `Firebase_setDelegate` in `app/src/util_ios.mm` to prevent redundant processing for delegate classes that are subclasses of already seen delegates. - When `setDelegate:` is called with a `newClass`: - It now first iterates through the superclasses of `newClass`. If any superclass is found in the `g_seen_delegate_classes` list, `newClass` is considered handled, and no further processing (adding to seen list or running pending blocks for it) occurs. - If no superclass is seen, it checks if `newClass` itself is already seen. If so, it's skipped. - If `newClass` is genuinely new (neither itself nor any superclass already seen), it's added to `g_seen_delegate_classes`, and all blocks from `g_pending_app_delegate_blocks` are executed for it. - This addresses potential issues with third-party libraries (like GUL) that might set their own delegate subclasses, ensuring our hooks and blocks run appropriately. - Includes cleanup of minor iterative comments.
This commit addresses several items after an accidental reset: 1. **Restores Source Code Logic:** * Re-implements the correct logic for `RunOnAppDelegateClasses` (formerly ForEachAppDelegateClass) and the swizzled `Firebase_setDelegate` in `app/src/util_ios.mm`. * `Firebase_setDelegate` now correctly tracks multiple unique delegate classes seen, includes a superclass check to prevent redundant processing for subclasses of already-seen delegates, and executes persistent pending blocks for genuinely new delegate classes. * `RunOnAppDelegateClasses` executes blocks for all currently known unique delegates and queues the block for future new delegate classes. * Ensures `ClassMethodImplementationCache` is in its state prior to the reverted idempotency fix attempt. * All associated constants, global variables, function declarations (in `util_ios.h`), and call sites (in `invites` and `messaging` modules) are correctly restored/updated. * Logging uses `NSLog` and iterative comments have been cleaned. 2. **Integrates Learnings into `Jules.md`:** * Reverts the previous commit that added a task-specific learnings section. * Integrates key insights from this refactoring task directly into the most appropriate existing sections of `Jules.md`, covering robust swizzling, callback lifecycle, naming, logging safety, and agent interaction patterns. This commit aims to bring the branch to the desired functional state with updated documentation.
jonsimantov
commented
Jun 19, 2025
jonsimantov
commented
Jun 19, 2025
- Re-implemented the idempotency check in ClassMethodImplementationCache::ReplaceOrAddMethod in app/src/util_ios.mm. This prevents re-swizzling if a method already has the target IMP, addressing potential recursion issues. - Updated Jules.md: - Integrated learnings from the recent iOS App Delegate refactoring task into relevant existing sections (covering robust swizzling, callback lifecycles, naming, logging safety, and agent interaction). - Added a document convention note to maintain 80-character line wrapping. - Word-wrapped the entire document to 80 characters for readability. This commit consolidates the fix for the swizzling cache and the comprehensive updates and formatting for Jules.md.
This commit addresses several critical fixes and consolidates all recent updates for the iOS App Delegate handling mechanism: 1. **Restored Swizzling Mechanism:** - Re-added the `UIApplication(FirebaseAppDelegateSwizzling)` category and its `+load` method to `app/src/util_ios.mm`. This was inadvertently lost during a previous operation and is essential for swizzling `[UIApplication setDelegate:]` with `Firebase_setDelegate`. 2. **Core Logic (already in working tree, confirmed):** - `Firebase_setDelegate` correctly tracks multiple unique delegate classes, includes a superclass check, and executes persistent pending blocks for genuinely new delegate classes. - `RunOnAppDelegateClasses` executes blocks for all currently known delegates and queues blocks for future new delegates. - `ClassMethodImplementationCache::ReplaceOrAddMethod` includes an idempotency check to prevent re-swizzling if a method already has the target IMP. 3. **Documentation (`Jules.md`):** - Learnings from this refactoring are integrated into relevant existing sections. - The document is formatted with 80-character line wrapping, and a note regarding this convention is included. This commit aims to bring the `refactor-forEachAppDelegateClass-ios` branch to its fully intended functional state, including all logic fixes, restorations, and documentation updates.
a-maurice
reviewed
Jun 24, 2025
a-maurice
approved these changes
Jun 24, 2025
… file. (#1741) * Feature: Allow specifying AppDelegate via Info.plist for RunOnAppDelegateClasses Currently, `firebase::util::RunOnAppDelegateClasses` on iOS automatically swizzles `[UIApplication setDelegate:]` to capture and act on any class set as the application delegate. This change introduces an optional feature where developers can specify their app's main AppDelegate class name directly in the `Info.plist` file using the key `FirebaseAppDelegateClassName`. If this key is present and provides a valid class name: - `RunOnAppDelegateClasses` will only execute blocks for this specified class. - `[UIApplication setDelegate:]` will NOT be swizzled by Firebase. If the key is not present, is invalid, or the specified class is not found, Firebase will fall back to the original behavior of swizzling `[UIApplication setDelegate:]`. This provides developers more control over Firebase's interaction with the AppDelegate, especially in scenarios where swizzling might be undesirable or needs to be more targeted. Detailed logging has been added to trace the behavior in both modes. A manual testing plan has been outlined to cover various scenarios. * Feature: Allow specifying AppDelegate via Info.plist for RunOnAppDelegateClasses (Refined) Currently, `firebase::util::RunOnAppDelegateClasses` on iOS automatically swizzles `[UIApplication setDelegate:]` to capture and act on any class set as the application delegate. This change introduces an optional feature where developers can specify their app's main AppDelegate class name directly in the `Info.plist` file using the key `FirebaseAppDelegateClassName`. If this key is present and provides a valid class name: - `RunOnAppDelegateClasses` will only execute blocks for this specified class. - Pending blocks are processed once for this target. - New blocks execute immediately on this target and are not queued for others. - `[UIApplication setDelegate:]` will NOT be swizzled by Firebase. If the key is not present, is invalid, or the specified class is not found, Firebase will fall back to the original behavior of swizzling `[UIApplication setDelegate:]`. This provides developers more control over Firebase's interaction with the AppDelegate. The implementation of `RunOnAppDelegateClasses` has been refined to support this new mode more simply while ensuring correct block execution and pending queue management. Detailed logging has been added. A manual testing plan is provided. * Refactor: Improve comments and logging for AppDelegate Info.plist feature This commit cleans up comments and refines logging messages within the `+load` method in `FirebaseAppDelegateSwizzling` category for clarity and accuracy related to the recently added feature for specifying the AppDelegate via Info.plist. - Clarified comments explaining the Info.plist handling path, including the setup of the specified delegate and the execution of pending blocks. - Ensured comments accurately reflect that pending blocks are not cleared from the queue after execution in `+load` when in Info.plist mode. - Minor wording improvements to log messages for better diagnostics. - Removed redundant or outdated comments from previous iterations. No functional code changes are included in this commit. * Docs: Simplify AppDelegate Info.plist option in README Further refines the documentation for the `FirebaseAppDelegateClassName` Info.plist key feature on iOS. - The explanation in `release_build_files/readme.md` under "Specifying Your AppDelegate Class Directly (iOS)" has been made more concise and user-focused, removing internal implementation details. - The corresponding release note for version 12.9.0 has also been simplified to match this approach. This change aims to make the documentation easier for developers to understand by focusing on the action and benefit rather than Firebase internal mechanisms. * Docs: Use generic 'Upcoming Release' title in README Changes the heading for the newest release notes entry from '### 12.9.0 (Upcoming)' to '### Upcoming Release' as the specific version number is not yet known. * Update logging to not be verbose unless debug logs are on. Also clean up NSLog messages elsewhere. * Update log message. * Fix build error. * Format code. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
….com/firebase/firebase-cpp-sdk into refactor-forEachAppDelegateClass-ios
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
This commit refactors the
ForEachAppDelegateClass
function inapp/src/util_ios.mm
. Instead of scanning all runtime classes to find UIApplicationDelegate implementers, it now relies on method swizzling.The
[UIApplication setDelegate:]
method is swizzled at startup. WhensetDelegate:
is called, the class of the actual application delegate is captured and stored globally (static to the .mm file).ForEachAppDelegateClass
now uses this stored class. If it's called beforesetDelegate:
has been invoked, the block passed toForEachAppDelegateClass
is queued. This queued block is then executed once the delegate is set via the swizzledsetDelegate:
method.This approach is more efficient and directly targets the actual App Delegate class used by the application.
Key changes:
Firebase_setDelegate
C function as the swizzled implementation.UIApplication(FirebaseAppDelegateSwizzling)
category with a+load
method to perform the swizzling.method_setImplementation
for swizzling and stores the original IMP.g_app_delegate_class
,g_original_setDelegate_imp
, andg_pending_app_delegate_block
manage the state withinutil_ios.mm
.ForEachAppDelegateClass
to use the new mechanism and queue blocks if the delegate is not yet known.This change also allows you to set a special key in your
Info.plist
file to specify your App Delegate class name. If you set theFirebaseAppDelegateClassName
to the name of your App Delegate class, it will bypass this whole setDelegate swizzling logic and simply use the App Delegate class you provided.The purpose of this fix is to remove the dependency on
objc_copyClassList
, which was causing a crash on iOS 15. This crash was occurring because this function was forcing all Objective-C classes to be realized during the +load of the Firebase classes, which caused an issue with a common unrelated iOS framework. With this fix,objc_copyClassList
is no longer used, which should also improve startup times on iOS.Testing
Manually tested in C++ and Unity testapps.
Type of Change
Place an
x
the applicable box:Notes
Release Notes
section ofrelease_build_files/readme.md
.