Files
purchases-ios/Sources/Networking/OfferingsAPI.swift
T
NachoSoto 1234c4ea91 CacheableNetworkOperation: avoid unnecessarily creating operations for cache hits (#2135)
### Changes:
- Created `CacheableNetworkOperationFactory` to abstract operation and
cache key creation
- Instead of always creating an operation, we create a factory first,
which pre-computes the cache key
- This cache key is used to determine if it's a cache hit, as well as
for the subsequently created operation

I'm still investigating a leak (#2105), and I suspect it's coming from
us creating and storing completion blocks, and those staying in memory
beyond the lifetime of the operation.
This refactor and performance improvement helps us avoid get more things
in memory than we need to.

See #2138 for how this is tested.
2022-12-21 08:13:41 -08:00

88 lines
4.3 KiB
Swift

//
// 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
//
// OfferingsAPI.swift
//
// Created by Joshua Liebowitz on 6/15/22.
import Foundation
class OfferingsAPI {
typealias IntroEligibilityResponseHandler = ([String: IntroEligibility], BackendError?) -> Void
typealias OfferSigningResponseHandler = (Result<PostOfferForSigningOperation.SigningData, BackendError>) -> Void
typealias OfferingsResponseHandler = (Result<OfferingsResponse, BackendError>) -> Void
private let offeringsCallbacksCache: CallbackCache<OfferingsCallback>
private let backendConfig: BackendConfiguration
init(backendConfig: BackendConfiguration) {
self.backendConfig = backendConfig
self.offeringsCallbacksCache = CallbackCache<OfferingsCallback>()
}
func getOfferings(appUserID: String,
withRandomDelay randomDelay: Bool,
completion: @escaping OfferingsResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let factory = GetOfferingsOperation.createFactory(
configuration: config,
offeringsCallbackCache: self.offeringsCallbacksCache
)
let offeringsCallback = OfferingsCallback(cacheKey: factory.cacheKey, completion: completion)
let cacheStatus = self.offeringsCallbacksCache.add(offeringsCallback)
self.backendConfig.addCacheableOperation(
with: factory,
withRandomDelay: randomDelay,
cacheStatus: cacheStatus
)
}
func getIntroEligibility(appUserID: String,
receiptData: Data,
productIdentifiers: [String],
completion: @escaping IntroEligibilityResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let getIntroEligibilityOperation = GetIntroEligibilityOperation(configuration: config,
receiptData: receiptData,
productIdentifiers: productIdentifiers,
responseHandler: completion)
self.backendConfig.operationQueue.addOperation(getIntroEligibilityOperation)
}
// swiftlint:disable:next function_parameter_count
func post(offerIdForSigning offerIdentifier: String,
productIdentifier: String,
subscriptionGroup: String,
receiptData: Data,
appUserID: String,
completion: @escaping OfferSigningResponseHandler) {
let config = NetworkOperation.UserSpecificConfiguration(httpClient: self.backendConfig.httpClient,
appUserID: appUserID)
let postOfferData = PostOfferForSigningOperation.PostOfferForSigningData(offerIdentifier: offerIdentifier,
productIdentifier: productIdentifier,
subscriptionGroup: subscriptionGroup,
receiptData: receiptData)
let postOfferForSigningOperation = PostOfferForSigningOperation(configuration: config,
postOfferForSigningData: postOfferData,
responseHandler: completion)
self.backendConfig.operationQueue.addOperation(postOfferForSigningOperation)
}
}
// @unchecked because:
// - Class is not `final` (it's mocked). This implicitly makes subclasses `Sendable` even if they're not thread-safe.
extension OfferingsAPI: @unchecked Sendable {}