feat(appsflyer): add startAppsFlyer to safely resume a withheld start() - #1064
Conversation
🐦 Swift Migration ProgressProduction implementation code at
Objective-C retained by design: Core SDK 6,405 · SDK kit infrastructure 2,451 · Standalone kits 0. This PR's code movement
How this is measured
Generated with |
📦 SDK Size Impact ReportMeasures how much the SDK adds to an app's size (with-SDK minus without-SDK).
➡️ SDK size impact change is minimal. Where the bytes are
Debug symbols (not shipped to users)The SDK is embedded as a dynamic framework, so the app's own executable barely
Raw measurementsTarget branch (main): {"baseline_app_size_kb":84,"baseline_executable_size_bytes":75464,"with_sdk_app_size_kb":2852,"with_sdk_executable_size_bytes":76312,"sdk_impact_kb":2768,"sdk_executable_impact_bytes":848,"xcframework_size_kb":6880,"framework_size_kb":3056,"dsym_size_kb":3820}This PR: {"baseline_app_size_kb":84,"baseline_executable_size_bytes":75464,"with_sdk_app_size_kb":2856,"with_sdk_executable_size_bytes":76312,"sdk_impact_kb":2772,"sdk_executable_impact_bytes":848,"xcframework_size_kb":6884,"framework_size_kb":3060,"dsym_size_kb":3820} |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Repository UI Review profile: QUIET Plan: Enterprise Run ID: 📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 10 remain after this review. 📝 WalkthroughWalkthrough
Priority: ⬇️ Low Merge Risk: 🟡 Moderate · up to A concurrent host call during initialization can start AppsFlyer before the kit finishes configuring it. Guard startup until configuration is complete before merging.
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: QUIET
Plan: Enterprise
Run ID: be18610e-3c0e-46e7-88c2-b2447b345f91
📒 Files selected for processing (3)
Kits/appsflyer/appsflyer-6/Sources/mParticle-AppsFlyer/MPKitAppsFlyer.mKits/appsflyer/appsflyer-6/Sources/mParticle-AppsFlyer/include/MPKitAppsFlyer.hKits/appsflyer/appsflyer-6/Tests/mParticle-AppsFlyerTests/MPKitAppsFlyerTests.swift
Included review availability: This review used your included allowance. Your plan provides up to 12 included reviews per hour; 11 remain after this review.
PR SummaryLow Risk Overview Internal activation logic in Reviewed by Cursor Bugbot for commit ffa5526. Bugbot is set up for automated code reviews on this repo. Configure here. |
The manualStart setting already withholds AppsFlyerLib's automatic start() call correctly, but gave host apps no safe way to resume it afterward other than calling AppsFlyerLib.shared().start() directly, which can resolve a different, unconfigured instance if the app also links the AppsFlyer SDK elsewhere. Adds a public +[MPKitAppsFlyer startAppsFlyer] that always operates on the kit's own configured instance instead, and routes the kit's own automatic-start call through it too so there is one code path. Guards against starting a tracker that's been assigned but not yet configured (dev key not set), and documents the method in the kit's README. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
4480524 to
ffa5526
Compare
Why
Apps that enable the AppsFlyer kit's "manual start" setting rely on it to delay AppsFlyer's attribution until the right moment — for example, until the app's own consent screen is accepted. The kit already withholds the automatic start correctly, but it has no safe way to tell it to go afterward: a host app has to reach into AppsFlyer's own shared instance directly, and if that app also happens to link the AppsFlyer SDK anywhere else, that call can land on a second, unconfigured copy and fail outright. After this change, there is a method on the kit itself that always resumes the one instance it configured, so that failure mode cannot happen through this call, and it's documented so developers actually find it.
Programme
Standalone change, no programme.
What changes
Before: once
manualStartwithheld the automatic start, the only way to resume it was calling AppsFlyer's own shared-instance accessor directly from host app code, and nothing documented this setting at all.After:
MPKitAppsFlyerexposes a new class method,startAppsFlyer, that starts the kit's own already-configured instance. It returnsNO(and logs a warning) if called before mParticle has configured the kit, or before that configuration has reached the point of setting a dev key (see Risks).didBecomeActive, now callsstartAppsFlyertoo instead of starting the tracker directly, so there is one code path for "start AppsFlyer" rather than two. Verified this doesn't change behavior:didBecomeActiveonly ever runs after the dev key is already set (traced both the self-triggered path indidFinishLaunchingWithConfiguration:and the externally-triggered path inMPKitContainerExecutionAdapter, which gates on the kit'sstartedflag — set only after the dev key is assigned).A reviewer should start at
startAppsFlyerinMPKitAppsFlyer.m(right after the existingsetDelegate:) and its header doc inMPKitAppsFlyer.h; theNS_SWIFT_NAMEannotation there is load-bearing, not decorative — see Risks.Linked work
None.
Rollout
Path: ships as part of the next published version of the
mParticle-AppsFlyer-6kit (CocoaPods) /mparticle-apple-integration-appsflyer-6(SPM); a consuming app only gets the new method, and the (behaviorally unchanged) internal rewiring, once it upgrades that dependency. No server-side or account-level component.Feature flags: none.
Turning it off: revert this pull request and cut a new kit release. A consuming app that has started calling the new method would need to switch back to calling AppsFlyer directly, same as before this shipped.
What we watch: nothing in production changes for an app that doesn't adopt the new method, since the internal rewiring was verified behavior-preserving; if that verification is wrong, the symptom would be AppsFlyer not auto-starting for apps that never touched
manualStart, which would surface immediately in this kit's own attribution.Risks
+startAppsFlyeron a class named...AppsFlyerdown to a barestart(), confirmed by an actual compile failure while building this change, which would have collided with an unrelated existing method and produced a confusing public name. Contained by theNS_SWIFT_NAME(startAppsFlyer())annotation added alongside it, verified by a passing Swift build against both Objective-C and Swift call sites. How we'd see it: Swift consumers of this kit would otherwise seeMPKitAppsFlyer.start()instead of the intendedMPKitAppsFlyer.startAppsFlyer()in autocomplete — a reviewer comparing this PR's header diff against a local Swift build would catch it, same as happened here.startAppsFlyer's guard originally only checked for aniltracker. CodeRabbit flagged thatappsFlyerTrackeris assigned before the rest of its configuration (dev key, delegates) runs, so a caller landing in that window would pass a nil check alone. Narrowed by also requiring a non-empty dev key, with a dedicated test (test_startAppsFlyer_withTrackerAssignedButNotYetConfigured_returnsFalse) using an unconfigured mock. This narrows the window rather than closing it outright — a caller landing between the dev key assignment and the delegate assignments a few lines later would still pass. Not fully addressed because closing it completely would mean adding kit-owned state solely to guard against a caller racing the kit's own synchronous initialization, which every production call path (traced for bothdidBecomeActivepaths) cannot actually do. How we'd see it: a consuming app would see attribution or deep-link callbacks silently not fire for a session that hit the window; given the narrow trigger (another thread calling this during the single synchronous method that configures the tracker), this hasn't been reported before and isn't expected now.didBecomeActiverewiring is additive in effect, not just in code — confirmed no production call path reaches it before the dev key is set. Contained by two independent traces: the self-triggered path (didFinishLaunchingWithConfiguration:sets the dev key, then flips the kit'sstartedflag, then dispatches the call todidBecomeActive) and the externally-triggered path (MPKitContainerExecutionAdapteronly callsdidBecomeActiveon kits whosestartedflag is already set). How we'd see it: same as above — a regression here would show as AppsFlyer failing to auto-start for apps not usingmanualStartat all, which would be immediately visible.Who
Written by: an automated coding agent (Claude Code), prompted by an internal defect report; no public link available.
Code reviewed before opening: an automated adversarial reviewer, across two passes — once before this PR was first opened (found one documentation typo, fixed), and again after extending it with the dev-key guard, the
didBecomeActiverewiring, and the README section (found no blocking issues; confirmed the "no behavior change" claim against the actual call graph rather than accepting it as asserted).Design reviewed before opening: no one.
Decision this implements: give the current AppsFlyer kit the same safe manual-start resume path an older, no-longer-maintained version of this kit once had, and address CodeRabbit's review comment on this PR; no separate design record.
Checked:
xcodebuild test -scheme mParticle-AppsFlyer -destination "platform=iOS Simulator,id=<booted sim>"against the kit's SwiftPM package (46 tests, all passing) andtrunk checkon all four changed files, today.Not checked: no instrumented run against a real AppsFlyer dev key / physical device; no verification against the CocoaPods-static-library linking scenario
startAppsFlyerexists to prevent (reproducing a duplicate-instance link error reliably needs a throwaway host app and isn't practical to do here).Size
Hand-written: 76 lines across 4 files.
Generated: none.
Notes for reviewers
Files changed:
Kits/appsflyer/appsflyer-6/Sources/mParticle-AppsFlyer/MPKitAppsFlyer.m— the newstartAppsFlyermethod (with the dev-key guard), anddidBecomeActivenow calling itKits/appsflyer/appsflyer-6/Sources/mParticle-AppsFlyer/include/MPKitAppsFlyer.h— its public declaration, header doc, and theNS_SWIFT_NAMEannotationKits/appsflyer/appsflyer-6/Tests/mParticle-AppsFlyerTests/MPKitAppsFlyerTests.swift— three new tests;setUp()now gives the shared mock a dev key so existing tests still reflect a "configured" trackerKits/appsflyer/appsflyer-6/README.md— new "Manual Start" section with Swift and Objective-C samples