Skip to content

fix(ios): forward every application delegate assignment - #1979

Merged
fadi-george merged 2 commits into
OneSignal:mainfrom
OskarEichler:codex/onesignal-ios-delegate-assignment
Sep 1, 2026
Merged

fix(ios): forward every application delegate assignment#1979
fadi-george merged 2 commits into
OneSignal:mainfrom
OskarEichler:codex/onesignal-ios-delegate-assignment

Conversation

@OskarEichler

@OskarEichler OskarEichler commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Description

One Line Summary

Forward every application delegate assignment to UIApplication, and install the launch hook once per delegate class instead of once per process.

Compatibility and observable changes

Setting a replacement delegate or clearing it with nil now reaches UIApplication as expected. A leading nil delegate no longer consumes the installation token.

Because assignments are no longer swallowed, a replacement delegate genuinely becomes active. Installation is therefore keyed on the delegate class rather than a single process-wide token, so a later delegate of a different class still gets the launch hook. Each class is injected at most once, since a second method_exchangeImplementations would swap the original implementation back and drop OneSignal out of the launch chain.

Apps that today rely on the old behavior of silently discarding a nil or replacement delegate assignment will now see that assignment take effect, which matches the UIApplication contract.

Details

Motivation

The source audit reproduced the failure paths described below. This PR contains only the associated fix; unrelated audit changes are in separate PRs.

Two defects existed. Only the first assignment ever reached UIApplication, so replacements and nil clears were dropped. And a leading nil consumed the dispatch_once token, leaving the app with no delegate and OneSignal never initialized.

Scope

  • ios/RCTOneSignal/UIApplication+RCTOnesignal.m

Not changed: injectSelector and the oneSignalApplication:didFinishLaunchingWithOptions: chaining guard. A delegate class that inherits rather than implements application:didFinishLaunchingWithOptions: still has that inherited implementation shadowed and not chained. That behavior is identical before and after this PR and is tracked separately.

Testing

Compiled real category code with Foundation/Objective-C runtime and UIApplication/delegate doubles; baseline replacement and clearing fail, and all three assignment checks pass fixed.

Each code/tooling fix was also applied independently to upstream commit a70312207cf094ac361eaa9196c317acb175c2cd and passed its targeted external actual-source diagnostics. Documentation snippets were checked separately. Native diagnostic harnesses use bridge/SDK doubles and are not an end-to-end push test.

Per-class installation follow-up commit

The real category source was compiled unmodified against Foundation plus UIApplication, delegate, and RCTOneSignal doubles, with each scenario run in a fresh process so the dispatch_once token starts clean. Three versions were compared: base a703122, the first commit, and the per-class commit.

Scenario Base First commit Per-class commit
Delegate A only A active, init runs same same
A then B, different class delegate stays A B active, init never runs B active, init runs
A then B, same class A active, init runs same same
A, B, then A again A active, init runs once same same
nil then A delegate stays nil, init never runs A active, init runs same
A then nil nil ignored delegate cleared same
Inherited launch method inherited impl not chained same same

Also run: clang -Wall -Wextra with no warnings, clang-format LLVM style clean, existing SDK suite of 263 tests passing, and a Debug iOS Simulator build of the demo example reaching ** BUILD SUCCEEDED **. The build log was checked to confirm UIApplication+RCTOnesignal.m recompiled for arm64 and x86_64 in the react-native-onesignal pod target rather than being served from cache.

On the combined audit branch:

  • Existing SDK suite: 5 files, 262 tests pass; unchanged 95% coverage thresholds pass.
  • vp check: formatting, lint and type checks pass; native Spotless check passes.
  • Both example apps: Android Debug and unsigned iOS Simulator builds pass.
  • Both example apps: iOS and Android production Metro bundles pass.

No checked-in test files were added or modified; regression evidence comes from external diagnostic harnesses and the existing suite. No physical-device, live notification delivery, Appium/BrowserStack, or release-workflow execution is claimed. The no-location example's stale native lock was updated locally to resolve the current SDK for verification; generated locks are not part of this PR.

Affected code checklist

  • Notifications
    • Open
    • Push Processing
  • Sessions

Checklist

  • Required description sections completed.
  • Scope and observable/API behavior explained.
  • Diff reviewed and targeted regression checks run.
  • Automated checks and device-testing limitations documented.

@OskarEichler
OskarEichler requested a review from a team as a code owner August 27, 2026 23:47
OskarEichler and others added 2 commits August 31, 2026 16:23
Gating injection on a single dispatch_once token bound the launch hook to
the first non-null delegate class. Now that every assignment is forwarded,
a later delegate of a different class became active without the hook, so
initOneSignal: was skipped at launch. Track injected classes instead so
each new delegate class is injected exactly once.

Co-authored-by: Cursor <cursoragent@cursor.com>
@fadi-george
fadi-george force-pushed the codex/onesignal-ios-delegate-assignment branch from 8679d1f to cd6ef37 Compare August 31, 2026 23:24
@fadi-george
fadi-george merged commit 130a896 into OneSignal:main Sep 1, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants