Introduce Tracking namespace and extract delegate stores#6681
Conversation
|
@RCGitBot please test |
Mirror the caseless-enum namespace pattern that admob-tracking-namespace-enum (#6681) introduced for the `Tracking` subsystem. - New `RewardVerification.swift` declares `internal enum RewardVerification {}` as the umbrella for the post-reward SSV poll subsystem. - All six leaf types from the previous commit are now nested under `RewardVerification` and lose the redundant prefix: `RewardVerificationDispatcher` -> `RewardVerification.Dispatcher` `RewardVerificationPoller` -> `RewardVerification.Poller` `RewardVerificationOutcome` -> `RewardVerification.Outcome` `RewardVerificationState` -> `RewardVerification.State` `RewardVerificationStateStore` -> `RewardVerification.StateStore` `RewardVerificationSetup` -> `RewardVerification.Setup` Plus the supporting types: `RewardVerificationPollResult` -> `RewardVerification.PollResult` `RewardVerificationStatusPolling` -> `RewardVerification.StatusPolling` `RewardVerificationCapableAd` -> `RewardVerification.CapableAd` `RewardVerificationJitter` -> `RewardVerification.Jitter` `PurchasesRewardVerificationStatusPoller` -> `RewardVerification.PurchasesStatusPoller` `AsyncSleeper` / `TaskSleeper` -> `RewardVerification.AsyncSleeper` / `.TaskSleeper` - Source files drop the prefix to mirror the namespace: `RewardVerificationDispatcher.swift` -> `Dispatcher.swift` `RewardVerificationPoller.swift` -> `Poller.swift` `RewardVerificationOutcome.swift` -> `Outcome.swift` `RewardVerificationState.swift` -> `State.swift` `RewardVerificationStateStore.swift` -> `StateStore.swift` `RewardVerificationSetup.swift` -> `Setup.swift` - `RewardVerification.StateStore` collapses the previous nested `AssociatedKeys` enum into a `private static var key: UInt8`, matching the pattern used by `Tracking.FullScreenDelegateStore` / `Tracking.NativeDelegateStore` / `Tracking.NativeAdLoaderProxyStore`. This also satisfies SwiftLint's nesting rule (max 1 level deep). - Test classes mirror production: `RewardVerificationPollerTests` -> `PollerTests`, `RewardVerificationOutcomeTests` -> `OutcomeTests`, etc. The polling-loop test factory `makeRewardVerificationPoller` is now just `makePoller`. No behavior changes. The GMA conformances of `GoogleMobileAds.RewardedAd` and `GoogleMobileAds.RewardedInterstitialAd` to `RewardVerification.CapableAd` still live next to the protocol they conform to (in `Setup.swift`), but hang off file scope rather than inside the namespace extension because Swift forbids declaring conformances inside an `extension` of a different type.
ajpallares
left a comment
There was a problem hiding this comment.
Nice refactor! I need to look deeper into this, but I have a couple of initial suggestions
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit a73c8e2. Configure here.
38c43d0 to
86bb476
Compare
910f4cc to
6cfa59a
Compare
|
@RCGitBot please test |
Plumbs the verified-reward payload from the backend response through the
internal `pollRewardVerificationStatus` SPI so RC ad adapters can dispatch
meaningful "verified" events with the granted reward.
* Adds `VirtualCurrencyReward` and `VerifiedReward` (`@_spi(Internal) public`)
under `Sources/Ads/RewardVerification/`.
* Extends `RewardVerificationStatusResponse` to optionally decode a typed nested
reward payload (`{ type, code, amount }`) on `verified` responses.
- `reward` absent or null → `.noReward`
- `type == "virtual_currency"` with valid `code`+`amount` → `.virtualCurrency`
- any other `type` (or malformed `virtual_currency`) → `.unsupportedReward` + warn
* `RewardVerificationPollStatus.verified` now carries the `VerifiedReward`.
* `Purchases.pollRewardVerificationStatus` returns `.verified(reward)`.
* Test mocks injected via a new `BasePurchasesTests.MockBackend` convenience
init so `backend.adsAPI` resolves to `MockAdsAPI`.
Previously, `decodeVerifiedReward` silently returned `.noReward` for any of: missing reward key, null reward, or non-object reward value (e.g. a string/number/array). The first two are expected; the last indicates a backend mismatch and should be surfaced. Now distinguish: - absent or null reward → `.noReward` (silent, expected) - present but not a JSON object → `.unsupportedReward` + `Logger.warn` This matches the existing convention in this file of logging warnings when decoding unrecognized/malformed values into fallback cases.
Align reward payload modeling with backend semantics by decoding `amount` as Int and updating tests to reject fractional values as malformed rewards.
…apter
First step in regrouping the AdMob adapter under a single namespace.
Internal-only refactor — no public API changes:
* New `Tracking` umbrella enum (caseless) holds `Adapter`, `Tracker`,
`PurchasesTracker`.
* `RCAdMob` class → `Tracking.Adapter`; its file is split into
`Tracking/{Tracking,Adapter,Tracker,PurchasesTracker}.swift`.
* `AdTracking` protocol → `Tracking.Tracker`.
* `PurchasesAdTracker` → `Tracking.PurchasesTracker`.
* `rcAdMob:` parameter / `rcAdMob` property → `adapter:` / `adapter`
across the four delegate wrappers, the GMA `loadAndTrack` extensions,
and tests.
The four `RCAdMob*` delegate wrapper class names, `RCFullScreenAdTracking`,
and the `Tracking/` file moves of the wrappers themselves are deferred to
commit 2 for a smaller diff per step.
Move every adapter file into the Tracking/ subfolder and drop the RC*/
RCAdMob* prefixes by nesting the types inside the Tracking namespace.
File moves (git mv preserves history):
- RCAdMobBannerViewDelegate.swift -> Tracking/BannerViewDelegate.swift
- RCAdMobFullScreenContentDelegate.swift -> Tracking/FullScreenContentDelegate.swift
- RCAdMobNativeAdDelegate.swift -> Tracking/NativeAdDelegate.swift
- RCAdMobNativeAdLoader.swift -> Tracking/AdLoader+Tracking.swift
- BannerView+RCAdMob.swift -> Tracking/BannerView+Tracking.swift
- FullScreenAd+RCAdMob.swift -> Tracking/FullScreenAd+Tracking.swift
- FullScreenAd+RCAdMob+Completion.swift -> Tracking/FullScreenAd+Tracking+Completion.swift
Type renames (all internal):
- RCAdMobBannerViewDelegate -> Tracking.BannerViewDelegate
- RCAdMobFullScreenContentDelegate -> Tracking.FullScreenContentDelegate
- RCAdMobNativeAdDelegate -> Tracking.NativeAdDelegate
- RCNativeAdLoaderDelegateProxy -> Tracking.NativeAdLoaderDelegateProxy
(kept "Proxy" suffix to avoid clashing with GoogleMobileAds.NativeAdLoaderDelegate)
- RCFullScreenAdTracking (protocol) -> Tracking.FullScreenAd
- FullScreenLoadContext (struct) -> Tracking.FullScreenLoadContext
- rcSetTrackingFullScreenContentDelegate (helper on the protocol)
-> setTrackingDelegate on Tracking.FullScreenAd
- private file-scoped key enums lose RC prefix
(RCBannerAssociatedKeys -> BannerAssociatedKeys,
RCNativeAdLoaderAssociatedKeys -> NativeAdLoaderAssociatedKeys)
Public API surface is unchanged: setTrackingFullScreenContentDelegate(_:),
loadAndTrack(...), and present(from:placement:...) keep their signatures and
behavior; only the internal helpers they forward to were renamed.
Test files still use their RCAdMob* filenames (deferred to commit 6); only
the type references inside them were updated.
Verified: swift build, xcodebuild test on iOS Simulator (87 tests passing),
swiftlint clean.
Move the Obj-C associated-object retention logic for the full-screen
content tracking delegate out of Tracking.Adapter into a dedicated
Tracking.FullScreenDelegateStore type that owns the associated-object key
and exposes retain/retrieve operations.
Tracking.Adapter holds an instance and exposes it directly so callers
reach for adapter.fullScreenDelegateStore.{retain,retrieve}(...) instead
of the previous retainFullScreenDelegate / retrieveFullScreenDelegate
facades on Adapter.
Behavior is identical (still uses OBJC_ASSOCIATION_RETAIN_NONATOMIC).
Public API unchanged. Verified with iOS Simulator test run (87/87 passing).
Move the Obj-C associated-object retention logic for the native ad tracking delegate out of Tracking.Adapter into a dedicated Tracking.NativeDelegateStore type that owns the associated-object key and exposes the retain operation. Tracking.Adapter holds an instance and exposes it directly so callers reach for adapter.nativeDelegateStore.retain(...) instead of the previous retainNativeDelegate facade on Adapter. Behavior is identical (still uses OBJC_ASSOCIATION_RETAIN_NONATOMIC). Public API unchanged. Verified with iOS Simulator test run (87/87 passing).
Move the Obj-C associated-object retention logic for the native ad-loader delegate proxy out of GoogleMobileAds.AdLoader.loadAndTrack into a dedicated Tracking.NativeAdLoaderProxyStore type that owns the associated-object key and exposes the retain operation. Tracking.Adapter holds an instance and exposes it directly, so the AdLoader extension reaches for adapter.nativeAdLoaderProxyStore.retain(...) instead of using a private file-scoped associated-object key with objc_setAssociatedObject inline. Behavior is identical (still uses OBJC_ASSOCIATION_RETAIN_NONATOMIC). Public API unchanged. Verified with iOS Simulator test run (87/87 passing). This completes the symmetry across the three delegate stores (FullScreenDelegateStore, NativeDelegateStore, NativeAdLoaderProxyStore), each owning a single associated-object key and a single responsibility.
Drop the RCAdMob* prefix from all adapter test files and class names so
the test suite matches the namespacing used by the production sources
(Tracking.* in Sources/RevenueCatAdMob/Tracking/).
File and class renames:
- RCAdMobTestCase.swift -> AdapterTestCase.swift
(typealias RCAdMobTestCase -> AdapterTestCase)
- RCAdMobAPISurfaceTests.swift -> APISurfaceTests.swift
- RCAdMobBannerDelegateForwardingTests.swift -> BannerDelegateForwardingTests.swift
- RCAdMobBannerPaidHandlerBehaviorTests.swift -> BannerPaidHandlerBehaviorTests.swift
- RCAdMobBannerWrapperBehaviorTests.swift -> BannerWrapperBehaviorTests.swift
- RCAdMobDelegateContractTests.swift -> DelegateContractTests.swift
- RCAdMobFullScreenDelegateForwardingTests.swift
-> FullScreenDelegateForwardingTests.swift
- RCAdMobNativeAdLoaderProxyBehaviorTests.swift
-> NativeAdLoaderProxyBehaviorTests.swift
- RCAdMobNativeDelegateForwardingTests.swift -> NativeDelegateForwardingTests.swift
- RCAdMobPrecisionMappingTests.swift -> PrecisionMappingTests.swift
- RCAdMobRevenueConversionTests.swift -> RevenueConversionTests.swift
- RCAdMobTrackingTests.swift -> AdapterTrackingTests.swift
(also renames the secondary RCAdMobDelegateTrackingTests class within
the file to AdapterDelegateTrackingTests)
Split RCAdMobDelegateRetentionTests.swift into per-store test files,
mirroring the store extractions:
- FullScreenDelegateStoreTests.swift — exercises Tracking.Adapter
.fullScreenDelegateStore.retain(_:for:)
- NativeDelegateStoreTests.swift — exercises Tracking.Adapter
.nativeDelegateStore.retain(_:for:)
No production code changes. Verified with iOS Simulator test run
(87/87 passing).
e015c4d to
ca17fc5
Compare
|
@ajpallares fyi I will manage this and the related PRs this week while Pol is away. Let me know if there is anything else here to do - it looks good to me. Thank you! |
|
@RCGitBot please test |
ajpallares
left a comment
There was a problem hiding this comment.
Looking good! I have some more suggestions, mostly to simplify things a bit. Also, we should update the PR, as it seems to be a bit stale
|
@RCGitBot please test |
|
@RCGitBot please test |
|
@RCGitBot please test |
|
@RCGitBot please test |
|
@RCGitBot please test |
|
@ajpallares simplified the description, removed the unnecessary comment and the branch is up to date with main |
|
@RCGitBot please test |

Checklist
purchases-androidand hybridsMotivation
The adapter had a flat
Sources/RevenueCatAdMob/directory withRC*/RCAdMob*-prefixed types and aRCAdMobgod-class mixing the singleton, event forwarding, associated-object retention, and orchestration. TheRC*prefixes are Obj-C naming conventions that add noise in an internal-only Swift adapter.Description
Trackingnamespace (enum Tracking {}) and move all adapter sources intoSources/RevenueCatAdMob/Tracking/RCAdMob→Tracking.Adapter,RCAdMob*delegates/helpers →Tracking.*Tracking.AssociatedObjectStore<Value: AnyObject>; per-format stores are typealiasesBannerTrackingStateobject per banner, eliminating repeated lookups and the explicit "already wrapped" flagAssociatedObjectStoreTestsPublic API (
loadAndTrack(...),present(from:placement:...),setTrackingFullScreenContentDelegate(_:)) and all behavior are unchanged.Testing
swift buildcleanxcodebuild test -scheme RevenueCatAdMob)swiftlintclean