NachoSoto

NachoSoto

Software Engineer and Airline Pilot

Member Since 10 years ago

San Francisco, California

Experience Points
426
follower
Lessons Completed
98
follow
Lessons Completed
773
stars
Best Reply Awards
94
repos

1367 contributions in the last year

Pinned
⚡ Streams of values over time
⚡ UIImageView for rendering data asynchronously, with composable renderers and caches
Activity
Nov
30
15 hours ago
pull request

NachoSoto merge to RevenueCat/purchases-ios

NachoSoto
NachoSoto

Check for productsWithIntroOffers first, before backend

If we can return productsWithIntroOffers for all productIds without asking the backend, we should. Otherwise, we should filter out the ids that we know about and only ask the backend for ones we don't. Fixes #982

close pull request

NachoSoto wants to merge RevenueCat/purchases-ios

NachoSoto
NachoSoto

DeviceCache: reduced cache duration on sandbox

Fixes #989 / [sc-11154].

pull request

NachoSoto merge to RevenueCat/purchases-ios

NachoSoto
NachoSoto

DeviceCache: reduced cache duration on sandbox

Fixes #989 / [sc-11154].

Activity icon
issue

NachoSoto issue comment RevenueCat/purchases-ios

NachoSoto
NachoSoto
NachoSoto
NachoSoto

@taquitos manually checked and Carthage integration is working. We'll assume the failure is a flaky test in the 3.x branch. We can look at it more closely if it happens again in 4.x

Activity icon
delete

NachoSoto in NachoSoto/purchases-ios delete branch empty-app-user-id

deleted time in 12 hours ago
push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

Don't allow empty string for appUserID during initialization (#995)

Fixes [sc-10159].

Based on top of #974 because it contains a significant simplification of IdentityManager that makes this a lot easier.

Note that Purchases.logIn will still fail with an error when trying to use an empty string. This PR does not change that behavior.

commit sha: 641e8b5cae19eddae48417b8665b49a81792f5c1

push time in 12 hours ago
pull request

NachoSoto pull request RevenueCat/purchases-ios

NachoSoto
NachoSoto

Don't allow empty string for `appUserID` during initialization

Fixes [sc-10159].

Based on top of #974 because it contains a significant simplification of IdentityManager that makes this a lot easier.

Note that Purchases.logIn will still fail with an error when trying to use an empty string. This PR does not change that behavior.

push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

DeviceCache: reduced cache duration on sandbox (#1000)

Fixes #989 / [sc-11154].

commit sha: 1c44d23b7c36847999f4963009a0dcc884e340a4

push time in 12 hours ago
Activity icon
delete

NachoSoto in RevenueCat/purchases-ios delete branch cache-duration-sandbox

deleted time in 12 hours ago
pull request

NachoSoto pull request RevenueCat/purchases-ios

NachoSoto
NachoSoto

DeviceCache: reduced cache duration on sandbox

Fixes #989 / [sc-11154].

Activity icon
issue

NachoSoto issue RevenueCat/purchases-ios

NachoSoto
NachoSoto

Reduce cache duration in sandbox

Purchases in sandbox have a reduced duration, however, we’re still using the same cache duration as we do for production.

This leads to developer confusion, since they expect that the data will always be up-to-date.

We should reduce the cache duration for the sandbox environment, which will make testing a lot easier and less confusing.

Related issue: https://github.com/RevenueCat/purchases-ios/issues/853

The proposed cache duration for Sandbox is 5 minutes. We can use the isSandbox variable in SystemInfo to determine if we're running in the sandbox environment.

https://app.shortcut.com/revenuecat/story/11154/reduce-cache-duration-in-sandbox

Activity icon
issue

NachoSoto issue comment RevenueCat/purchases-ios

NachoSoto
NachoSoto

Replaced custom `DateFormatter` with new `ISO8601DateFormatter`

Fixes #988.

Unfortunately ISO8601DateFormatter inherits from Formatter, and not DateFormatter, so this introduces a new protocol DateFormatterType that both types can implement.

Another unfortunate difference with the new type compared to the old implementation is that milliseconds are either optional or mandatory. For that reason, I added ISO8601DateFormatter.default which replicates the same behavior by deferring date(from: String) -> Date to one formatter or the other. I added tests to cover this behavior directly, but it was only thanks to the existing ArraySlice tests that I became aware of this requirement.

Additionally, I made Transaction.init(with:productId:dateFormatter) not public and not @objc so it can take a new DateFormatterType. The method didn't need to be public, it was just a pre-Swift-migration leftover.

As a bonus I changed some of the tests in DateFormatter+ExtensionsTests to use XCTUnwrap.

NachoSoto
NachoSoto

This should be ready for review now :)

push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

ProductDetails: log warning when product title is empty (#1002)

Fixes #624.

commit sha: a8de7e335439d1a5103eaee5eb148eccc261afd4

push time in 14 hours ago
Activity icon
delete

NachoSoto in RevenueCat/purchases-ios delete branch product-details-empty-title

deleted time in 14 hours ago
pull request

NachoSoto pull request RevenueCat/purchases-ios

NachoSoto
NachoSoto

ProductDetails: log warning when product title is empty

Fixes #624.

Activity icon
issue

NachoSoto issue RevenueCat/purchases-ios

NachoSoto
NachoSoto

Add log warning when product.title is empty string, nil, or just filled with spaces.

I think RevenueCat/purchases-flutter#212 could be debugged easier if we added some more logging around product fields that we expect to not be empty.

push

NachoSoto push NachoSoto/purchases-ios

NachoSoto
NachoSoto

Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation (#974)

  • Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation

Fixes #732. Instead of worrying about whether IdentityManager.configure was idempotent or if the implementation was correct, I realized that Purchases never called that method after its initialization Therefore, to simplify the whole implementation, the configure part is now in the constructor. Subsequent logIn/logOut calls are the only way to change the identity.

Some of this code was tangled with some of the deprecations in IdentityManager, so to aid in this refactor, I obsoleted 3 methods that were already deprecated in version 3.13.0.

This allowed me to remove the associated tests instead of changing them to use the new API. Following #950, these methods are now separate in Obsoletions.swift, and they point to the new methods.

The remaining tests in PurchasesTests all pass, and the ones in IdentityManagerTests have been converted to initializing new IdentityManager instances. Those pass as well, so I feel confident in this refactor :muscle:

  • SwiftAPITester: removed calls to deprecated methods

  • ObjCAPITester: removed calls to deprecated methods

NachoSoto
NachoSoto
NachoSoto
NachoSoto

IdentityManager: convert empty appUserIDs to anonymous users

Fixes [sc-10159]

NachoSoto
NachoSoto
NachoSoto
NachoSoto

IdentityManager: add test to check spaces aren't trimmed

commit sha: 8f419667be32acc42abb69fe77de709e59ea6cfb

push time in 14 hours ago
push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

Fixed invalid project reference (#993)

NachoSoto
NachoSoto

APITester: combined targets into one cross-platform target (#980)

Follow up to #979. This simplifies APITesters like so:

  • 1 Workspace including ObjC/Swift
  • Each project only has 1 cross-platform target

These can be built easily locally from Xcode, and therefore they're easier to maintain.

NachoSoto
NachoSoto

Refactored DeviceCache to improve thread safety (#983)

Fixes #739.

Changes:

  • Created Atomic, based on Lock, to abstract atomic access and modifications to values.
  • Simplified InMemoryCachedObject using Atomic (tests still pass!).
  • Created SynchronizedUserDefaults using Atomic, to enforce atomic access to UserDefaults.
  • Refactored DeviceCache using Atomic to enforce all access to UserDefaults are synchronized:
    • DeviceCache no longer stores a UserDefaults instance (only SynchronizedUserDefaults), so we guarantee we don't do unsynchronized modifications.
    • The old threadUnsafe_ methods are now static, which ensures they don't access any local state. Instead, UserDefaults is injected, and DeviceCache calls these methods using SynchronizedUserDefaults.read/SynchronizedUserDefaults.write.
    • The combination of both of these things eliminates potential for programmer error within the class.
    • There's still potential for what's described in #739, that a user of DeviceCache reads data, and THEN writes it again, without synchronizing both operations. This PR doesn't address that, but I haven't found any examples of that being an issue, and if we do, we should move both operations into a single method in DeviceCache that can enforce the semantics.
NachoSoto
NachoSoto

Using XCTSkip instead of silently passing tests (#999)

Fixes #990 and [sc-5246].

NachoSoto
NachoSoto

Add async alternative APIs (#987)

  • added async alternative to logIn

  • added availability check, fixed linting

  • added async / await alternative to logOut

  • added async / await alternative for getCustomerInfo, fixed api availability for presentCodeRedemptionSheet

  • updated a couple of references to purchaserInfo -> customer info

  • fixed a few more references to purchaserInfo

  • added async alternative to getProducts

  • added async alternative to getOfferings

  • added async alternative to purchase(product:)

  • added async alternative to purchase(package:)

  • removed the get prefix for async alternatives

  • added async alternative to purchase(product:discount:)

  • added async alternative to purchase(package:discount:)

  • added async alternative to syncPurchases()

  • added async alternative to restoreTransactions

  • added async alternative to checkTrialOrIntroductoryPriceEligibility

  • disabled swiftlint check

  • added async alternative to paymentDiscount(forProductDiscount:product:)

  • added async alternative to showManageSubscriptionModal

  • added async alternative to beginRefundRequest, fixed availability for macOS

  • added Purchases+async file, and moved implementation of a few async methods there

  • moved products to async file

  • reverted macOS API availability to prevent failing tests

  • added missing availability for macOS

  • moved a few more methods to async file

  • moved remaining async methods to async extension

  • fixed indentation

  • named values in return tuple from logIn async alternative

  • fixed issues with API availability by adding OS checks

  • fixed linting and API call on unavailable method for macOS

  • cleaned up api availability in a couple more places

NachoSoto
NachoSoto

Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation (#974)

  • Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation

Fixes #732. Instead of worrying about whether IdentityManager.configure was idempotent or if the implementation was correct, I realized that Purchases never called that method after its initialization Therefore, to simplify the whole implementation, the configure part is now in the constructor. Subsequent logIn/logOut calls are the only way to change the identity.

Some of this code was tangled with some of the deprecations in IdentityManager, so to aid in this refactor, I obsoleted 3 methods that were already deprecated in version 3.13.0.

This allowed me to remove the associated tests instead of changing them to use the new API. Following #950, these methods are now separate in Obsoletions.swift, and they point to the new methods.

The remaining tests in PurchasesTests all pass, and the ones in IdentityManagerTests have been converted to initializing new IdentityManager instances. Those pass as well, so I feel confident in this refactor :muscle:

  • SwiftAPITester: removed calls to deprecated methods

  • ObjCAPITester: removed calls to deprecated methods

NachoSoto
NachoSoto
NachoSoto
NachoSoto

IdentityManager: convert empty appUserIDs to anonymous users

Fixes [sc-10159]

NachoSoto
NachoSoto
NachoSoto
NachoSoto

IdentityManager: add test to check spaces aren't trimmed

commit sha: 8f419667be32acc42abb69fe77de709e59ea6cfb

push time in 14 hours ago
push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

Using XCTSkip instead of silently passing tests (#999)

Fixes #990 and [sc-5246].

NachoSoto
NachoSoto

Add async alternative APIs (#987)

  • added async alternative to logIn

  • added availability check, fixed linting

  • added async / await alternative to logOut

  • added async / await alternative for getCustomerInfo, fixed api availability for presentCodeRedemptionSheet

  • updated a couple of references to purchaserInfo -> customer info

  • fixed a few more references to purchaserInfo

  • added async alternative to getProducts

  • added async alternative to getOfferings

  • added async alternative to purchase(product:)

  • added async alternative to purchase(package:)

  • removed the get prefix for async alternatives

  • added async alternative to purchase(product:discount:)

  • added async alternative to purchase(package:discount:)

  • added async alternative to syncPurchases()

  • added async alternative to restoreTransactions

  • added async alternative to checkTrialOrIntroductoryPriceEligibility

  • disabled swiftlint check

  • added async alternative to paymentDiscount(forProductDiscount:product:)

  • added async alternative to showManageSubscriptionModal

  • added async alternative to beginRefundRequest, fixed availability for macOS

  • added Purchases+async file, and moved implementation of a few async methods there

  • moved products to async file

  • reverted macOS API availability to prevent failing tests

  • added missing availability for macOS

  • moved a few more methods to async file

  • moved remaining async methods to async extension

  • fixed indentation

  • named values in return tuple from logIn async alternative

  • fixed issues with API availability by adding OS checks

  • fixed linting and API call on unavailable method for macOS

  • cleaned up api availability in a couple more places

NachoSoto
NachoSoto

Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation (#974)

  • Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation

Fixes #732. Instead of worrying about whether IdentityManager.configure was idempotent or if the implementation was correct, I realized that Purchases never called that method after its initialization Therefore, to simplify the whole implementation, the configure part is now in the constructor. Subsequent logIn/logOut calls are the only way to change the identity.

Some of this code was tangled with some of the deprecations in IdentityManager, so to aid in this refactor, I obsoleted 3 methods that were already deprecated in version 3.13.0.

This allowed me to remove the associated tests instead of changing them to use the new API. Following #950, these methods are now separate in Obsoletions.swift, and they point to the new methods.

The remaining tests in PurchasesTests all pass, and the ones in IdentityManagerTests have been converted to initializing new IdentityManager instances. Those pass as well, so I feel confident in this refactor :muscle:

  • SwiftAPITester: removed calls to deprecated methods

  • ObjCAPITester: removed calls to deprecated methods

NachoSoto
NachoSoto

DeviceCache: reduced cache duration on sandbox

Fixes #989 / [sc-11154].

commit sha: 3ae238e9015d22c54754d3078923ad696d3b43e6

push time in 14 hours ago
push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

Using XCTSkip instead of silently passing tests (#999)

Fixes #990 and [sc-5246].

NachoSoto
NachoSoto

Add async alternative APIs (#987)

  • added async alternative to logIn

  • added availability check, fixed linting

  • added async / await alternative to logOut

  • added async / await alternative for getCustomerInfo, fixed api availability for presentCodeRedemptionSheet

  • updated a couple of references to purchaserInfo -> customer info

  • fixed a few more references to purchaserInfo

  • added async alternative to getProducts

  • added async alternative to getOfferings

  • added async alternative to purchase(product:)

  • added async alternative to purchase(package:)

  • removed the get prefix for async alternatives

  • added async alternative to purchase(product:discount:)

  • added async alternative to purchase(package:discount:)

  • added async alternative to syncPurchases()

  • added async alternative to restoreTransactions

  • added async alternative to checkTrialOrIntroductoryPriceEligibility

  • disabled swiftlint check

  • added async alternative to paymentDiscount(forProductDiscount:product:)

  • added async alternative to showManageSubscriptionModal

  • added async alternative to beginRefundRequest, fixed availability for macOS

  • added Purchases+async file, and moved implementation of a few async methods there

  • moved products to async file

  • reverted macOS API availability to prevent failing tests

  • added missing availability for macOS

  • moved a few more methods to async file

  • moved remaining async methods to async extension

  • fixed indentation

  • named values in return tuple from logIn async alternative

  • fixed issues with API availability by adding OS checks

  • fixed linting and API call on unavailable method for macOS

  • cleaned up api availability in a couple more places

NachoSoto
NachoSoto

Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation (#974)

  • Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation

Fixes #732. Instead of worrying about whether IdentityManager.configure was idempotent or if the implementation was correct, I realized that Purchases never called that method after its initialization Therefore, to simplify the whole implementation, the configure part is now in the constructor. Subsequent logIn/logOut calls are the only way to change the identity.

Some of this code was tangled with some of the deprecations in IdentityManager, so to aid in this refactor, I obsoleted 3 methods that were already deprecated in version 3.13.0.

This allowed me to remove the associated tests instead of changing them to use the new API. Following #950, these methods are now separate in Obsoletions.swift, and they point to the new methods.

The remaining tests in PurchasesTests all pass, and the ones in IdentityManagerTests have been converted to initializing new IdentityManager instances. Those pass as well, so I feel confident in this refactor :muscle:

  • SwiftAPITester: removed calls to deprecated methods

  • ObjCAPITester: removed calls to deprecated methods

NachoSoto
NachoSoto

ProductDetails: log warning when product title is empty

Fixes #624.

NachoSoto
NachoSoto

Extracted String to OfferingStrings

commit sha: d0619a75224228d10241b5cdda65c7e4661c3120

push time in 14 hours ago
Activity icon
delete

NachoSoto in RevenueCat/purchases-ios delete branch bump/3.14.0-SNAPSHOT

deleted time in 14 hours ago
pull request

NachoSoto pull request RevenueCat/purchases-ios

NachoSoto
NachoSoto

Prepare next version: 3.14.0-SNAPSHOT

push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

Updated Package.resolved (#997)

I keep getting Xcode automatically updating these 2. Looks like for 2 reasons:

  • #946 got rid of SPM on the MagicWeatherProject
  • The dependency got updated

commit sha: d9d8c58858989ac3f140e48ffbb61bd98056bcd8

push time in 14 hours ago
Activity icon
delete

NachoSoto in RevenueCat/purchases-ios delete branch SPM-resolved-file

deleted time in 14 hours ago
pull request

NachoSoto pull request RevenueCat/purchases-ios

NachoSoto
NachoSoto

Updated Package.resolved

I keep getting Xcode automatically updating these 2. Looks like for 2 reasons:

  • #946 got rid of SPM on the MagicWeatherProject
  • The dependency got updated
push

NachoSoto push RevenueCat/purchases-ios

NachoSoto
NachoSoto

Refactored DeviceCache to improve thread safety (#983)

Fixes #739.

Changes:

  • Created Atomic, based on Lock, to abstract atomic access and modifications to values.
  • Simplified InMemoryCachedObject using Atomic (tests still pass!).
  • Created SynchronizedUserDefaults using Atomic, to enforce atomic access to UserDefaults.
  • Refactored DeviceCache using Atomic to enforce all access to UserDefaults are synchronized:
    • DeviceCache no longer stores a UserDefaults instance (only SynchronizedUserDefaults), so we guarantee we don't do unsynchronized modifications.
    • The old threadUnsafe_ methods are now static, which ensures they don't access any local state. Instead, UserDefaults is injected, and DeviceCache calls these methods using SynchronizedUserDefaults.read/SynchronizedUserDefaults.write.
    • The combination of both of these things eliminates potential for programmer error within the class.
    • There's still potential for what's described in #739, that a user of DeviceCache reads data, and THEN writes it again, without synchronizing both operations. This PR doesn't address that, but I haven't found any examples of that being an issue, and if we do, we should move both operations into a single method in DeviceCache that can enforce the semantics.
NachoSoto
NachoSoto

Using XCTSkip instead of silently passing tests (#999)

Fixes #990 and [sc-5246].

NachoSoto
NachoSoto

Add async alternative APIs (#987)

  • added async alternative to logIn

  • added availability check, fixed linting

  • added async / await alternative to logOut

  • added async / await alternative for getCustomerInfo, fixed api availability for presentCodeRedemptionSheet

  • updated a couple of references to purchaserInfo -> customer info

  • fixed a few more references to purchaserInfo

  • added async alternative to getProducts

  • added async alternative to getOfferings

  • added async alternative to purchase(product:)

  • added async alternative to purchase(package:)

  • removed the get prefix for async alternatives

  • added async alternative to purchase(product:discount:)

  • added async alternative to purchase(package:discount:)

  • added async alternative to syncPurchases()

  • added async alternative to restoreTransactions

  • added async alternative to checkTrialOrIntroductoryPriceEligibility

  • disabled swiftlint check

  • added async alternative to paymentDiscount(forProductDiscount:product:)

  • added async alternative to showManageSubscriptionModal

  • added async alternative to beginRefundRequest, fixed availability for macOS

  • added Purchases+async file, and moved implementation of a few async methods there

  • moved products to async file

  • reverted macOS API availability to prevent failing tests

  • added missing availability for macOS

  • moved a few more methods to async file

  • moved remaining async methods to async extension

  • fixed indentation

  • named values in return tuple from logIn async alternative

  • fixed issues with API availability by adding OS checks

  • fixed linting and API call on unavailable method for macOS

  • cleaned up api availability in a couple more places

NachoSoto
NachoSoto

Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation (#974)

  • Obsoleted Purchases.createAlias/identify/reset and simplified IdentityManager implementation

Fixes #732. Instead of worrying about whether IdentityManager.configure was idempotent or if the implementation was correct, I realized that Purchases never called that method after its initialization Therefore, to simplify the whole implementation, the configure part is now in the constructor. Subsequent logIn/logOut calls are the only way to change the identity.

Some of this code was tangled with some of the deprecations in IdentityManager, so to aid in this refactor, I obsoleted 3 methods that were already deprecated in version 3.13.0.

This allowed me to remove the associated tests instead of changing them to use the new API. Following #950, these methods are now separate in Obsoletions.swift, and they point to the new methods.

The remaining tests in PurchasesTests all pass, and the ones in IdentityManagerTests have been converted to initializing new IdentityManager instances. Those pass as well, so I feel confident in this refactor :muscle:

  • SwiftAPITester: removed calls to deprecated methods

  • ObjCAPITester: removed calls to deprecated methods

NachoSoto
NachoSoto

Replaced custom DateFormatter with new ISO8601DateFormatter

Fixes #988.

Unfortunately ISO8601DateFormatter inherits from Formatter, and not DateFormatter, so this introduces a new protocol DateFormatterType that both types can implement.

Another unfortunate difference with the new type compared to the old implementation is that milliseconds are either optional or mandatory. For that reason, I added ISO8601DateFormatter.default which replicates the same behavior by deferring date(from: String) -> Date to one formatter or the other. I added tests to cover this behavior directly, but it was only thanks to the existing ArraySlice tests that I became aware of this requirement.

Additionally, I made Transaction.init(with:productId:dateFormatter) not public and not @objc so it can take a new DateFormatterType. The method didn't need to be public, it was just a pre-Swift-migration leftover.

As a bonus I changed some of the tests in DateFormatter+ExtensionsTests to use XCTUnwrap.

NachoSoto
NachoSoto

DateFormatterType doesn't need to be public

NachoSoto
NachoSoto

Simplified DateFormatterType removing dateDecodingStrategy

commit sha: a6aa90172a007882ed31468f55a4c4718e7d736b

push time in 14 hours ago
open pull request

NachoSoto wants to merge RevenueCat/purchases-ios

NachoSoto
NachoSoto

Replaced custom `DateFormatter` with new `ISO8601DateFormatter`

Fixes #988.

Unfortunately ISO8601DateFormatter inherits from Formatter, and not DateFormatter, so this introduces a new protocol DateFormatterType that both types can implement.

Another unfortunate difference with the new type compared to the old implementation is that milliseconds are either optional or mandatory. For that reason, I added ISO8601DateFormatter.default which replicates the same behavior by deferring date(from: String) -> Date to one formatter or the other. I added tests to cover this behavior directly, but it was only thanks to the existing ArraySlice tests that I became aware of this requirement.

Additionally, I made Transaction.init(with:productId:dateFormatter) not public and not @objc so it can take a new DateFormatterType. The method didn't need to be public, it was just a pre-Swift-migration leftover.

As a bonus I changed some of the tests in DateFormatter+ExtensionsTests to use XCTUnwrap.

NachoSoto
NachoSoto

Great find! I just refactored this by passing a strategy instead so the protocol is simpler. Thanks!

Previous