-
Notifications
You must be signed in to change notification settings - Fork 80
Description
isReady
is frequently consulted from non-main actors/threads (for example, inside Swift-Concurrency code).
Because the AppLovin SDK updates its internal state on the main thread (delegate callbacks),
reading isReady
off the main thread creates a data race that can cause false negatives.
In production, we observe time-out warnings like
Timeout expired, ad not ready for adUnitId: XXXXXX
even though the ad finished loading seconds earlier.
Repro project
-
Create a small Swift app:
let ad = MARewardedAd.shared(withAdUnitIdentifier: "«TEST_AD_UNIT»") ad.delegate = self ad.loadAd() Task.detached { // background executor while true { print("isReady:", ad.isReady) try? await Task.sleep(nanoseconds: 50_000_000) // 50 ms } }
-
Observe console output right after
didLoad
fires on the main thread:
isReady
printsfalse
for several iterations before flipping totrue
(sometimes it never flips).
Expected behaviour
• Either
– Accessing isReady
from any thread/actor is documented as unsafe,
or
– The property is made thread-safe / @MainActor
-isolated so that mixed-thread
access is benign.
Actual behaviour
Reading from non-main threads sometimes returns stale false
, which makes
apps implement unnecessary retry/timeout logic and degrades UX.
Environment
• AppLovin MAX SDK version: 13.0.0
• Xcode version: 16.4
• iOS deployment target: 15
• Device / OS: occurs on both Simulator and Device (iOS 17–18)
Proposed solutions
-
Document clearly (in headers & guides) that all MAX API calls must occur on the main thread /
@MainActor
. -
Objective-C header enforcement
#define MA_REQUIRES_MAIN_THREAD __attribute__((objc_requires_main_queue)) @property (nonatomic, assign, readonly, getter=isReady) BOOL ready MA_REQUIRES_MAIN_THREAD;
-
Swift overlay
@MainActor public var isReady: Bool { get }
-
Swift-6–readiness (delegate protocols)
Swift 6 ships with Strict Concurrency enabled; compiling the current SDK
with-warn-concurrency
emits hundreds of warnings.
Please mark every delegate protocol as@MainActor
and, where applicable,
wrap Obj-C protocols with a Swift overlay:/// Always invoked on the main thread. @protocol MARewardedAdDelegate <MAAdDelegate> - (void)didLoadAd:(MAAd *)ad; … @end
@MainActor public protocol MARewardedAdDelegate: MAAdDelegate { func didLoad(_ ad: MAAd) … }
The same applies to
•MAInterstitialAdDelegate
•MABannerAdDelegate
• any other MAX delegate that is guaranteed to run on the main queue.
These changes will eliminate the current data race, silence Swift-6
concurrency warnings, and give developers compile-time guarantees about thread
safety.