Files
purchases-ios/Sources/FoundationExtensions/UserDefaults+Extensions.swift
T
NachoSoto ecce3ff982 Changed default UserDefaults from .standard to our own Suite (#2046)
Fixes [CSDK-16].
See
https://docs.google.com/document/d/1DDyed98RFpIvUwUPJcetc7X4fBhDzf-r1trC4g3zf4c/edit#
for reasoning and documentation.

### Motivation

The RevenueCat SDK has always used `UserDefaults.standard` as the
default value. The advantage of this was simplicity, however, this
brings a few challenges.

### Issues

- While `UserDefaults` is itself thread-safe, our users might be
observing `UserDefaults.defaults` changes via `KVO`,
`NotificationCenter`, or `@AppStorage` (see
https://github.com/RevenueCat/purchases-ios/issues/1527). We update
`UserDefaults` from background threads, and users might be getting
updates on those threads without handling thread-safety correctly.
- Users can accidentally delete data from the shared `UserDefaults` (see
https://rev.cat/userdefaults-crash).

Using a custom `UserDefaults` solves all these issues.

### Change

The new `UserDefaults.revenueCatSuite` is used when:
- Only if users aren’t overriding the `UserDefaults` parameter
- Only if `UserDefaults.standard` has no value for
`com.revenuecat.userdefaults.appUserID.new` (the user hasn't used the
SDK in that app before).

#### Logged during app start:

> PurchaseTester[35110:6323140] DEBUG: ℹ️ Configuring SDK using
RevenueCat's UserDefaults suite.

### Alternatives Considered
See
https://docs.google.com/document/d/1c2B4MNAFKs_DcFJ3OE_KnmryHCArBkjlZbAD9_kSXu0/edit#

We considered also migrating data for everyone into the new
`UserDefaults`.
This new approach is much simpler, less error prone, easier to
implement, and maintain.
The only downside is that existing users won’t benefit from this change,
but @aboedo correctly pointed out: if the app is working for them, no
reason to change anything.

So in this way, this becomes a purely forward-looking improvement.

[CSDK-16]:
https://revenuecats.atlassian.net/browse/CSDK-16?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
2022-11-22 11:32:09 -08:00

50 lines
1.8 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// Copyright RevenueCat Inc. All Rights Reserved.
//
// Licensed under the MIT License (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://opensource.org/licenses/MIT
//
// UserDefaults+Extensions.swift
//
// Created by Nacho Soto on 11/9/22.
import Foundation
extension UserDefaults {
// These are the only 2 documented reasons why `.init(suiteName:)` might return `nil`:
// - "Because a suite manages the defaults of a specified app group, a suite name
// must be distinct from your app’s main bundle identifier.
// - The globalDomain is also an invalid suite name, because it isn't writeable by apps.
//
// Because we know at compile time that it's neither of those, this is a safe force-unwrap.
static let revenueCatSuite: UserDefaults = .init(suiteName: UserDefaults.revenueCatSuiteName)!
private static let revenueCatSuiteName = "com.revenuecat.user_defaults"
}
extension UserDefaults {
/// Determines the "default" `UserDefaults` to use for the SDK.
///
/// Moving foward, this default is `UserDefaults.revenueCatSuite`,
/// but existing users will continue using `UserDefaults.standard` for compatibility.
/// This is determined by the presence of an app user ID in `UserDefaults.standard`.
static func computeDefault() -> UserDefaults {
let standard: UserDefaults = .standard
if standard.value(forKey: DeviceCache.CacheKeys.appUserDefaults.rawValue) != nil {
Logger.debug(Strings.configure.using_user_defaults_standard)
return standard
} else {
Logger.debug(Strings.configure.using_user_defaults_suite_name)
return .revenueCatSuite
}
}
}