Skip to content

[iOS] registerSessionReadyListener trips Main Thread Checker: UIApplication.applicationState called on a background thread #470

Description

@yanashenyang

Description

On iOS, registerSessionReadyListener() trips the Main Thread Checker: the native
-[AppsFlyerLib registerSessionReadyListener:] reads -[UIApplication applicationState]
while running on a Swift concurrency cooperative thread, not on the main thread.

The plugin's own AFRPCBridge already hops to the main thread for event delivery
(DispatchQueue.main.async { handler(jsonEvent) }, with a comment explaining why
Task { @MainActor in } is not used there). The request path does not do the same:
AFRPCCoreHandler.handle is async, so it executes on the cooperative pool and calls
straight into the native SDK from there.

Environment

appsflyer_sdk 7.0.2
AppsFlyerFramework 7.0.2
Flutter 3.47.2 (stable), Dart 3.13.2
Xcode 26.6 (17F113)
Platform iOS only app, UIScene lifecycle (FlutterSceneDelegate), SPM (no CocoaPods)

Steps to reproduce

final af = AppsFlyerSdk.instance;
await af.enableDebug(true);
await af.init(devKey: '<key>', appId: '<id>');
await af.setCustomerUserId('<id>');
await af.registerSessionReadyListener(() => af.start()); // <- warning fires here

Run a debug build on a device/simulator with the Main Thread Checker enabled (default).

Actual result

Main Thread Checker: UI API called on a background thread: -[UIApplication applicationState]
PID: 62283, TID: 3850498, Thread name: (none), Queue name: com.apple.root.user-initiated-qos.cooperative, QoS: 25
Backtrace:
4   Runner.debug.dylib  -[AppsFlyerLib registerSessionReadyListener:] + 164
5   Runner.debug.dylib  $s12AppsFlyerRPC16AFRPCCoreHandlerV34handleRegisterSessionReadyListener33_...
6   Runner.debug.dylib  $s12AppsFlyerRPC16AFRPCCoreHandlerV6handleys6ResultOyAA10SDKSuccessVyAA10AnyCodableVGAA10SDKFailureVGAA0D7RequestOYaFTY0_ + 780
7   libswift_Concurrency.dylib
9   libdispatch.dylib   _dispatch_root_queue_drain + 404
10  libdispatch.dylib   _dispatch_worker_thread2 + 188
11  libsystem_pthread.dylib _pthread_wqthread + 228

Expected result

No Main Thread Checker violation: RPC requests that reach UIKit-touching SDK APIs should
be dispatched on the main thread, the way AFRPCBridge already does for events.

Notes

Reading UIApplication.applicationState off the main thread is a data race by Apple's
own contract, and this one decides whether a session is sent, so the value it reads
matters. There is no way to work around it from the Dart side, since the queue is chosen
inside the plugin: the only alternative is to drop registerSessionReadyListener
entirely and call start() directly, which per the v7 docs would lose every session
after the first foreground cycle.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions