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

Conversation

jonsimantov
Copy link
Contributor

Description

Provide details of the change, and generalize the change in the PR title above.


Testing

Describe how you've tested these changes. Link any manually triggered Integration tests or CPP binary SDK Packaging Github Action workflows, if applicable.


Type of Change

Place an x the applicable box:

  • Bug fix. Add the issue # below if applicable.
  • New feature. A non-breaking change which adds functionality.
  • Other, such as a build process or documentation change.

Notes

  • Bug fixes and feature changes require an update to the Release Notes section of release_build_files/readme.md.
  • Read the contribution guidelines CONTRIBUTING.md.
  • Changes to the public API require an internal API review. If you'd like to help us make Firebase APIs better, please propose your change in a feature request so that we can discuss it together.

…gateClasses

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.
…gateClasses (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.
…ture

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.
@jonsimantov jonsimantov requested a review from a-maurice June 23, 2025 22:37
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.
google-labs-jules bot and others added 2 commits June 24, 2025 16:54
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.
Also clean up NSLog messages elsewhere.
@jonsimantov jonsimantov merged commit 8071976 into refactor-forEachAppDelegateClass-ios Jun 24, 2025
23 of 24 checks passed
@github-actions github-actions bot added the tests: in-progress This PR's integration tests are in progress. label Jun 24, 2025
Copy link

github-actions bot commented Jun 24, 2025

❌  Integration test FAILED

Requested by @jonsimantov on commit 8071976
Last updated: Tue Jun 24 16:38 PDT 2025
View integration test log & download artifacts

Failures Configs
missing_log [BUILD] [ERROR] [iOS] [macos]

Add flaky tests to go/fpl-cpp-flake-tracker

@github-actions github-actions bot added the tests: failed This PR's integration tests failed. label Jun 24, 2025
@firebase-workflow-trigger firebase-workflow-trigger bot removed the tests: in-progress This PR's integration tests are in progress. label Jun 24, 2025
jonsimantov added a commit that referenced this pull request Jun 25, 2025
…zzles [UIApplication setDelegate:] to obtain App Delegate classes. (#1737)

* Refactor ForEachAppDelegateClass for iOS to use swizzling

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.

* I've tidied up `util_ios.mm` for you.

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.

* feat: Support multiple pending ForEachAppDelegateClass blocks

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.

* fixup: Cleanup comments and update RunOnAppDelegate call in messaging

- 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.

* I've addressed your review comments.

- 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.

* fix: Make ClassMethodImplementationCache robust against re-swizzling

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.

* fix: Check superclasses before processing new delegates

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.

* fix: Restore source code changes and integrate learnings

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.

* fix: Add idempotency to swizzling cache and update Jules.md

- 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.

* fix: Restore swizzling category and consolidate all recent changes

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.

* iOS: Allow specifying your AppDelegate class's name in the Info.plist 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>

* Fix build error.

* Finish renaming Jules.md to AGENTS.md.

---------

Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
tests: failed This PR's integration tests failed.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants