Skip to content

Release 2.0.0

Choose a tag to compare

@Ramiz69 Ramiz69 released this 05 Sep 20:04
· 5 commits to main since this release
c1039dc

Raises the deployment target to OS 26 and fixes a set of concurrency, entitlement and API defects found in an audit of the library. Every defect was reproduced before being fixed, and the package now has a test target that pins the behaviour.

⚠️ Breaking changes

  • Platforms start at iOS / macOS / tvOS / watchOS / visionOS 26. Stay on 1.0.6 for anything earlier.
  • StoreProduct.product is now optional. It is nil on values you build yourself, and a new public initializer takes the fields directly — so PurchasesProtocol can finally be mocked in tests and previews. Previously no conformer outside the module could produce a single return value.
  • ProductType gains unknown and PurchasesError gains unhandledPurchaseResult, so exhaustive switch statements need a new branch. A StoreKit product type this SDK does not recognise no longer passes silently as nonConsumable.
  • purchasedProducts no longer replays to a late subscriber. Every access now returns its own stream, so several observers each receive every event.

Fixed

  • Purchase events were lost whenever a second observer appeared. AsyncStream has exactly one consumer, so two for await loops split the sequence instead of each receiving it — one observer saw [1,3,5,7,9], the other [2,4,6,8,10].
  • The event buffer was unbounded and grew for the lifetime of the process: 100,000 yields with no consumer retained all of them. Per-subscriber buffers are now bufferingNewest(32).
  • The transaction listener retained the manager forever. Transaction.updates never ends, so deinit was unreachable and the cancel and finish it performs were dead code.
  • The singleton was read and written without synchronisation. ThreadSanitizer reports a data race on a single configure call racing ordinary shared reads, and the guarding precondition was itself a check-then-act on that memory.
  • The PurchasesProtocol default implementation recursed forever for any conformer that did not implement the requirement — which is exactly what a mock is. The same omission is now a compile-time error.
  • Refunded subscriptions still counted as active in activeSubscriptions() and activeSubscription(inGroup:), the two places that answer "is this user subscribed".
  • Product order changed on every launch, shuffling the paywall, and could include products that were never configured.
  • Duplicate purchase events, two force unwraps, and a redundant network round trip on every purchase.

Also

PurchasesError.notConfigured is finally thrown, by the new non-trapping PurchasesManager.resolved(), and the error type gains Equatable and LocalizedError.

The package gains its first test target — 24 tests in 5 suites — and CI is repaired: it could not build the 6.2 manifest with its pinned Xcode 16.2, and now also runs the tests.

See the migration notes in the README before upgrading from 1.x.