Skip to content

Changed default UserDefaults from .standard to our own Suite#2046

Merged
NachoSoto merged 9 commits into
mainfrom
user-defaults-default
Nov 22, 2022
Merged

Changed default UserDefaults from .standard to our own Suite#2046
NachoSoto merged 9 commits into
mainfrom
user-defaults-default

Conversation

@NachoSoto

@NachoSoto NachoSoto commented Nov 9, 2022

Copy link
Copy Markdown
Contributor

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

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.

@NachoSoto NachoSoto added the pr:fix A bug fix label Nov 9, 2022
@NachoSoto NachoSoto requested a review from a team November 9, 2022 19:29

@vegaro vegaro left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@NachoSoto NachoSoto requested a review from a team November 14, 2022 17:45
@NachoSoto

NachoSoto commented Nov 14, 2022

Copy link
Copy Markdown
Contributor Author

Thanks!
I'll leave this up for a few more days since it's an important change, also in case @aboedo wants to take a look at it.

@NachoSoto NachoSoto requested a review from aboedo November 16, 2022 21:26
@NachoSoto NachoSoto force-pushed the user-defaults-default branch from 283de18 to 18f14d6 Compare November 16, 2022 21:31
@NachoSoto

Copy link
Copy Markdown
Contributor Author

I just did some extra testing with PurchaseTester and this is working as expected.

@aboedo aboedo left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks awesome! Left a question re: DI

Comment on lines 18 to 30

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels like an odd workaround to solve a problem that the rest of the project solves with DI - we even pass in the userDefaults when initializing purchases.

Not saying that doing all the DI in one place is necessarily better, but it does have the advantage of looking exactly the same in all our SDKs, whereas this would be probably impossible to achieve in most languages.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, we don't need to cache this because we only call it once!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed!

Comment thread Sources/FoundationExtensions/UserDefaults+Extensions.swift Outdated
@NachoSoto NachoSoto force-pushed the user-defaults-default branch from 7a2f700 to 7dffafc Compare November 22, 2022 19:06
@NachoSoto NachoSoto enabled auto-merge (squash) November 22, 2022 19:24
@NachoSoto NachoSoto merged commit ecce3ff into main Nov 22, 2022
@NachoSoto NachoSoto deleted the user-defaults-default branch November 22, 2022 19:32
NachoSoto added a commit that referenced this pull request Nov 22, 2022
…aults`

We can do this now thanks to #2046. Solves #1906.
We no longer need to do this check if we're using our own `UserDefaults`, which simplifies the code, avoids a dangerous `fatalError`, and makes [CSDK-519] less likely.
NachoSoto added a commit that referenced this pull request Nov 23, 2022
**This is an automatic release.**

### Bugfixes
* Changed default `UserDefaults` from `.standard` to our own Suite
(#2046) via NachoSoto (@NachoSoto)
### Other Changes
* `Logging`: added log when configuring SDK in observer mode (#2065) via
NachoSoto (@NachoSoto)
* `PurchaseTesterSwiftUI`: added observer mode setting (#2052) via
NachoSoto (@NachoSoto)
* Exposed `SystemInfo.observerMode` to simplify code (#2064) via
NachoSoto (@NachoSoto)
* `Result.init(catching:)` with `async` method (#2060) via NachoSoto
(@NachoSoto)
* Updated schemes and project for Xcode 14.1 (#2081) via NachoSoto
(@NachoSoto)
* `PurchasesSubscriberAttributesTests`: simplified tests (#2056) via
NachoSoto (@NachoSoto)
* `DeviceCache`: removed `fatalError` for users not overriding
`UserDefaults` (#2079) via NachoSoto (@NachoSoto)
* `DeviceCache`: changed `NotificationCenter` observation to be received
on posting thread (#2078) via NachoSoto (@NachoSoto)
* `StoreKit1Wrapper`: added instance address when detecting transactions
(#2055) via NachoSoto (@NachoSoto)
* Fixed lint issues with `SwiftLint 0.5.0` (#2076) via NachoSoto
(@NachoSoto)
* `NSData+RCExtensionsTests`: improved errors (#2043) via NachoSoto
(@NachoSoto)
* `APITester`: fixed warning in `SubscriptionPeriodAPI` (#2054) via
NachoSoto (@NachoSoto)
* `Integration Tests`: always run them in random order locally (#2068)
via NachoSoto (@NachoSoto)

Co-authored-by: NachoSoto <ignaciosoto90@gmail.com>
NachoSoto added a commit that referenced this pull request Mar 24, 2023
Fixes #2252, #2364, RevenueCat/react-native-purchases#579, [SDKONCALL-214], [SDKONCALL-238], [SDKONCALL-241].
See also #2078 and #2079.

### Background

#2078 solved a potential deadlock in `DeviceCache`. This could happen when a background thread was in the middle of writing to `UserDefaults` through `DeviceCache`, and at the same time, the main thread wanted to read from it.
This is normally fine, except that if there are any `NotificationCenter` observations on the `UserDefaults` used, the background thread would then post that notification and wait for the main thread, leading to a deadlock. What #2078 did was to use a different method to make sure that the observation was received in the calling thread.
Additionally, #2079 avoided that observation whenever we used a different `UserDefaults`.

### Problem

When the SDK changed to a custom `UserDefaults` (#2046), we made the choice to only do this if there was no prior data.
However, if an app has prior RevenueCat data saved in `UserDefaults.standard`, that instance is used.

This potential deadlock is possible for any app with prior data, that also contains observations to `UserDefaults`, either manually through `NotificationCenter`, or using something like `@AppStorage` with `SwiftUI`.

### Solution

`DeviceCache.cachedAppUserID` is a very commonly used method. This PR changes it to an in-memory cache, initialized to the `UserDefaults` value.
By doing that, most code paths that rely on this value won't risk deadlocking with `UserDefaults` writes that jump threads.

### Long term

I've filed SDK-3034 to fully migrate data from `UserDefaults.standard` to avoid this problem altogether in the future.
NachoSoto added a commit that referenced this pull request Mar 24, 2023
Fixes #2252, #2364, RevenueCat/react-native-purchases#579, [SDKONCALL-214], [SDKONCALL-238], [SDKONCALL-241].
See also #2078 and #2079.

### Background

#2078 solved a potential deadlock in `DeviceCache`. This could happen when a background thread was in the middle of writing to `UserDefaults` through `DeviceCache`, and at the same time, the main thread wanted to read from it.
This is normally fine, except that if there are any `NotificationCenter` observations on the `UserDefaults` used, the background thread would then post that notification and wait for the main thread, leading to a deadlock. What #2078 did was to use a different method to make sure that the observation was received in the calling thread.
Additionally, #2079 avoided that observation whenever we used a different `UserDefaults`.

### Problem

When the SDK changed to a custom `UserDefaults` (#2046), we made the choice to only do this if there was no prior data.
However, if an app has prior RevenueCat data saved in `UserDefaults.standard`, that instance is used.

This potential deadlock is possible for any app with prior data, that also contains observations to `UserDefaults`, either manually through `NotificationCenter`, or using something like `@AppStorage` with `SwiftUI`.

### Solution

`DeviceCache.cachedAppUserID` is a very commonly used method. This PR changes it to an in-memory cache, initialized to the `UserDefaults` value.
By doing that, most code paths that rely on this value won't risk deadlocking with `UserDefaults` writes that jump threads.

### Long term

I've filed SDK-3034 to fully migrate data from `UserDefaults.standard` to avoid this problem altogether in the future.
NachoSoto added a commit that referenced this pull request Mar 24, 2023
Fixes #2252, #2364, [SDKONCALL-214], [SDKONCALL-241].
See also #2078 and #2079.

### Background

#2078 solved a potential deadlock in `DeviceCache`. This could happen
when a background thread was in the middle of writing to `UserDefaults`
through `DeviceCache`, and at the same time, the main thread wanted to
read from it.
This is normally fine, except that if there are any `NotificationCenter`
observations on the `UserDefaults` used, the background thread would
then post that notification and wait for the main thread, leading to a
deadlock. What #2078 did was to use a different method to make sure that
the observation was received in the calling thread.
Additionally, #2079 avoided that observation whenever we used a
different `UserDefaults`.

### Problem

When the SDK changed to a custom `UserDefaults` (#2046), we made the
choice to only do this if there was no prior data.
However, if an app has prior RevenueCat data saved in
`UserDefaults.standard`, that instance is used.

This potential deadlock is possible for any app with prior data, that
also contains observations to `UserDefaults`, either manually through
`NotificationCenter`, or using something like `@AppStorage` with
`SwiftUI`.

### Solution

`DeviceCache.cachedAppUserID` is a very commonly used method. This PR
changes it to an in-memory cache, initialized to the `UserDefaults`
value.
By doing that, most code paths that rely on this value won't risk
deadlocking with `UserDefaults` writes that jump threads.

### Long term

I've filed SDK-3034 to fully migrate data from `UserDefaults.standard`
to avoid this problem altogether in the future.


[SDKONCALL-214]:
https://revenuecats.atlassian.net/browse/SDKONCALL-214?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SDKONCALL-238]:
https://revenuecats.atlassian.net/browse/SDKONCALL-238?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SDKONCALL-241]:
https://revenuecats.atlassian.net/browse/SDKONCALL-241?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
NachoSoto added a commit that referenced this pull request Apr 10, 2023
Fixes #2252, #2364, [SDKONCALL-214], [SDKONCALL-241].
See also #2078 and #2079.

when a background thread was in the middle of writing to `UserDefaults`
through `DeviceCache`, and at the same time, the main thread wanted to
read from it.
This is normally fine, except that if there are any `NotificationCenter`
observations on the `UserDefaults` used, the background thread would
then post that notification and wait for the main thread, leading to a
deadlock. What #2078 did was to use a different method to make sure that
the observation was received in the calling thread.
Additionally, #2079 avoided that observation whenever we used a
different `UserDefaults`.

When the SDK changed to a custom `UserDefaults` (#2046), we made the
choice to only do this if there was no prior data.
However, if an app has prior RevenueCat data saved in
`UserDefaults.standard`, that instance is used.

This potential deadlock is possible for any app with prior data, that
also contains observations to `UserDefaults`, either manually through
`NotificationCenter`, or using something like `@AppStorage` with
`SwiftUI`.

`DeviceCache.cachedAppUserID` is a very commonly used method. This PR
changes it to an in-memory cache, initialized to the `UserDefaults`
value.
By doing that, most code paths that rely on this value won't risk
deadlocking with `UserDefaults` writes that jump threads.

I've filed SDK-3034 to fully migrate data from `UserDefaults.standard`
to avoid this problem altogether in the future.

[SDKONCALL-214]:
https://revenuecats.atlassian.net/browse/SDKONCALL-214?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SDKONCALL-238]:
https://revenuecats.atlassian.net/browse/SDKONCALL-238?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
[SDKONCALL-241]:
https://revenuecats.atlassian.net/browse/SDKONCALL-241?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:fix A bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants