Closed quantamrhino closed 2 years ago
In this case, it looks like when RevenueCat calls
$0.set(customerInfo, forKey: CacheKeyBases.customerInfoAppUserDefaults + appUserID)
at DeviceCache.swift
line 123, that is causing the UserDefaults
object to trigger some KVO notification in Foundation
. By the look of it, Foundation
is the culprit here.
I see that you're using iOS 15.5
, can you retest what you're doing on a non-beta?
There are a couple other scenarios that might trigger this, like DeviceCache
being deallocated (we don't do that)- however, if you ~don't store the Purchases
object somewhere for the life of your app~ call configure
two times, it is possible that is happening. I doubt it, though, it's more likely a beta thing 😄
Another question we have-
If you're passing a UserDefaults
object to Purchases
, are you doing any KVO / UserNotifications on the UserDefaults
object you're passing?
Thanks for your quick response! I don't have a spare device with a release build yet :-( Also I don't know how to repro this .. it's happened once, seemingly randomly. No userdefaults/kvo being passed to purchases. The configure() is called in App's init() once. I'll keep a lookout for this crash again.
XCode 13.3.1 reports runtime concurrency error for DeviceCache.swift lines 123 and 390, specifically purchases-ios/Sources/Caching/DeviceCache.swift: runtime: SwiftUI: Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates.
.
Where is that coming from? The SDK doesn't use SwiftUI
🤔
@NachoSoto I think that is just context-based message, the project itself where RevenueCat
is being used is written with SwiftUI
and so the runtime is SwiftUI
, therefore the message is SwiftUI
flavored.
AFAIK this message:
Publishing changes from background threads is not allowed; make sure to publish values from the main thread (via operators like receive(on:)) on model updates
Can only be produced by SwiftUI
or maybe Combine
, neither of which are used by the SDK.
I could be wrong, but this sounds like the SDK calling a block outside the main thread and your app updating the UI (maybe an ObservableObject
?).
If you can show us more code where this is coming from in the app maybe we could also make sure that the SDK doesn't invoke callbacks outside the main thread to avoid this.
I am using SDK in ObservableObject
but without callbacks at all, i use provided async/await
variants. According to trace in both cases (123, 390) there is an HTTP request for customer info.
Can you show us the code for that? You'll need to make sure your ObservableObject
is updated in the MainActor
.
Code attached. But as far as i managed to investigate the problems is that DeviceCache.cache(customerInfo:appUserID:)
is executed on Backend callbackQueue
Thanks for the detailed info, I'm looking into this right now.
Another question: do you use @AppStorage
anywhere in your app?
Yes, i do use @AppStorage
.
Okay, so looks like your issue (@ZeeWanderer) and the original issue (@quantamrhino) are both due to similar things.
If you don't specify a custom UserDefaults
when initializing Purchases
, it uses UserDefaults.standard
. Internally, the SDK writes to it from backgrounds threads (which is fine, because it's thread-safe).
However, it seems like @AppStorage
isn't thread-safe, and possibly the original issue is affected by something similar (maybe by using KVO / UserNotifications
and not managing this thread safety correctly.
The solution long term for the SDK is likely to change the default UserDefaults
to a private one. In the mean time, I recommend working around this by specifying a custom UserDefaults(suiteName: "your_app_group")
.
Another solution is to specify a different UserDefaults
for @AppStorage
:
@AppStorage("your_key", store: UserDefaults(suiteName: "your_app_group"))
@ZeeWanderer what version of iOS are you seeing the issue with @AppStorage
though?
I did this quick test and I can't reproduce it, even doing this dubious change from a background thread works fine with no warnings:
struct ContentView: View {
@AppStorage("com.nachosoto.clicks")
private var clicks: Int = 0
var body: some View {
VStack {
Text("Number of clicks: \(self.clicks)")
Button("Click me") {
self.clicks = self.clicks + 1
}
Button("Do something in the background") {
DispatchQueue(label: "test").async {
UserDefaults.standard.setValue(0, forKey: "com.nachosoto.clicks")
UserDefaults.standard.synchronize()
}
}
}
}
}
iOS 15.4.1
Crashed: com.apple.main-thread
0 libswiftCore.dylib 0x37e84 _assertionFailure(_:_:file:line:flags:) + 308
1 Grow 0x9b8fb4 closure #1 in variable initialization expression of static FatalErrorUtil.defaultFatalErrorClosure + 20 (FatalErrorUtil.swift:20)
2 Grow 0x96ed1c DeviceCache.handleUserDefaultsChanged(notification:) + 32 (FatalErrorUtil.swift:32)
3 Grow 0x96ed8c @objc DeviceCache.handleUserDefaultsChanged(notification:) + 4386565516 (<compiler-generated>:4386565516)
4 CoreFoundation 0x2ab34 __CFNOTIFICATIONCENTER_IS_CALLING_OUT_TO_AN_OBSERVER__ + 148
5 CoreFoundation 0xc4e28 ___CFXRegistrationPost_block_invoke + 88
6 CoreFoundation 0x98284 _CFXRegistrationPost + 440
7 CoreFoundation 0x40844 _CFXNotificationPost + 704
8 Foundation 0x185a4 -[NSNotificationCenter postNotificationName:object:userInfo:] + 92
`crash_info_entry_0
RevenueCat/DeviceCache.swift:71: Fatal error: [Purchases] - Cached appUserID has been deleted from user defaults. This leaves the SDK in an undetermined state. Please make sure that RevenueCat entries in user defaults don't get deleted by anything other than the SDK. More info: https://rev.cat/userdefaults-crash`
Same issue? All crash is on iOS 16, And we use the last SDK version. We use custom UserDefault Group
Here is my init code
// https://dev.appsflyer.com/hc/docs/integrate-ios-sdk
AppsFlyerLib.shared().appsFlyerDevKey = Consts.appsFlyerDevKey
AppsFlyerLib.shared().appleAppID = Consts.appleAppID
#if DEBUG
Purchases.logLevel = .debug
#endif
/*
Initialize the RevenueCat Purchases SDK.
- `appUserID` is nil by default, so an anonymous ID will be generated automatically by the Purchases SDK.
Read more about Identifying Users here: https://docs.revenuecat.com/docs/user-ids
- `observerMode` is false by default, so Purchases will automatically handle finishing transactions.
Read more about Observer Mode here: https://dz gocs.revenuecat.com/docs/observer-mode
*/
Purchases.configure(
with: Configuration.Builder(withAPIKey: Consts.revenuecatKey)
.with(usesStoreKit2IfAvailable: true)
.with(userDefaults: UserDefaults.group)
.build()
)
// https://docs.revenuecat.com/docs/appsflyer
// Automatically collect the $idfa, $idfv, and $ip values
Purchases.shared.collectDeviceIdentifiers()
// You should make sure to set attributes after the Purchases SDK is configured, and before the first purchase occurs.
// Set the Appsflyer Id
Purchases.shared.setAppsflyerID(AppsFlyerLib.shared().getAppsFlyerUID())
It happened randomly, I know it's hard to fix this.🥲 But the crashing is still increasing. I hope someone can fix this later, Thank you.
Thanks for the report! The additional details are helpful.
Can you provide more info about what else you do with UserDefaults.group
? We are aware of this issue, and for now if you're using a custom UserDefaults
instance we recommend not using that outside of RevenueCat
.
Yet In UserDefaults.group
, we also use it to share some data to widget extension.
Thanks for the help we will try to use a single custom UserDefaults
for RevenueCat
later.
Yes, separating your data into 2 UserDefaults
should fix this 👍🏻
I think this is solved, so I'm going to close it out. If this is still a problem for you, definitely feel free to re-open and let us know what you tried and the results.
This issue has been automatically locked due to no recent activity after it was closed. Please open a new issue for related reports.
Describe the bug Crash in DeviceCache.cache(customerInfo:appUserID:) + 123 Crashed: Backend callbackQueue EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000b2005a2e0
see stack trace below
Additional context Add any other context about the problem here.
purchase state is unsubscribed. was on mobile data and not on wifi. app was in foreground, and buttons were pressed, and randomly the crash happened. hasn't happened again today