Asyncify updateConversionRates

This commit is contained in:
Max Radermacher
2025-07-23 12:27:10 -05:00
committed by GitHub
parent 6097c287a3
commit c9f521f408
7 changed files with 63 additions and 58 deletions

View File

@@ -248,12 +248,14 @@ struct StripeCurrencyPickerDataSource: CurrencyPickerDataSource {
class PaymentsCurrencyPickerDataSource: NSObject, CurrencyPickerDataSource {
let currentCurrencyCode = SSKEnvironment.shared.paymentsCurrenciesRef.currentCurrencyCode
let preferredCurrencyInfos = SSKEnvironment.shared.paymentsCurrenciesRef.preferredCurrencyInfos
@MainActor
private(set) var supportedCurrencyInfos = SSKEnvironment.shared.paymentsCurrenciesRef.supportedCurrencyInfosWithCurrencyConversions {
didSet { updateTableContents?() }
}
var updateTableContents: (() -> Void)?
@MainActor
override init() {
super.init()
@@ -268,6 +270,7 @@ class PaymentsCurrencyPickerDataSource: NSObject, CurrencyPickerDataSource {
}
@objc
@MainActor
private func paymentConversionRatesDidChange() {
supportedCurrencyInfos = SSKEnvironment.shared.paymentsCurrenciesRef.supportedCurrencyInfosWithCurrencyConversions
}

View File

@@ -77,6 +77,7 @@ class SendPaymentHelper {
)
}
@MainActor
public func refreshObservedValues() {
updateCurrentCurrencyConversion()

View File

@@ -16,6 +16,7 @@ public protocol PaymentsCurrencies: AnyObject {
func setCurrentCurrencyCode(_ currencyCode: Currency.Code, transaction: DBWriteTransaction)
@MainActor
func updateConversionRates()
func warmCaches()
@@ -102,6 +103,7 @@ public class MockPaymentsCurrencies: PaymentsCurrenciesSwift, PaymentsCurrencies
public let supportedCurrencyInfosWithCurrencyConversions: [Currency.Info] = []
@MainActor
public func updateConversionRates() {}
public func conversionInfo(forCurrencyCode currencyCode: Currency.Code) -> CurrencyConversionInfo? {

View File

@@ -8,6 +8,7 @@ public class PaymentsCurrenciesImpl: PaymentsCurrenciesSwift, PaymentsCurrencies
private let appReadiness: AppReadiness
private var refreshEvent: RefreshEvent?
@MainActor
public init(appReadiness: AppReadiness) {
self.appReadiness = appReadiness
@@ -134,18 +135,18 @@ public class PaymentsCurrenciesImpl: PaymentsCurrenciesSwift, PaymentsCurrencies
}
}
self._conversionRates = newConversionRates
NotificationCenter.default.postOnMainThread(name: Self.paymentConversionRatesDidChange,
object: nil)
NotificationCenter.default.postOnMainThread(name: Self.paymentConversionRatesDidChange, object: nil)
}
}
private let isUpdateInFlight = AtomicBool(false, lock: .sharedGlobal)
@objc
@MainActor
public func updateConversionRates() {
guard
appReadiness.isAppReady,
CurrentAppContext().isMainAppAndActive,
CurrentAppContext().isMainAppAndActiveIsolated,
DependenciesBridge.shared.tsAccountManager.registrationStateWithMaybeSneakyTransaction.isRegistered
else {
return
@@ -153,8 +154,7 @@ public class PaymentsCurrenciesImpl: PaymentsCurrenciesSwift, PaymentsCurrencies
guard SSKEnvironment.shared.paymentsHelperRef.arePaymentsEnabled else {
return
}
if let conversionRates = self.conversionRates,
!conversionRates.isStale {
if let conversionRates = self.conversionRates, !conversionRates.isStale {
// No need to update.
return
}
@@ -163,55 +163,54 @@ public class PaymentsCurrenciesImpl: PaymentsCurrenciesSwift, PaymentsCurrencies
// Update already in flight.
return
}
Task {
defer { isUpdateInFlight.set(false) }
do {
try await self._updateConversionRates()
} catch {
owsFailDebugUnlessNetworkFailure(error)
}
}
}
firstly(on: DispatchQueue.global()) { () -> Promise<HTTPResponse> in
let request = OWSRequestFactory.currencyConversionRequest()
return SSKEnvironment.shared.networkManagerRef.makePromise(request: request)
}.map(on: DispatchQueue.global()) { response in
guard let json = response.responseBodyJson else {
throw OWSAssertionError("Missing or invalid JSON")
private func _updateConversionRates() async throws {
let request = OWSRequestFactory.currencyConversionRequest()
let response = try await SSKEnvironment.shared.networkManagerRef.asyncRequest(request)
guard let json = response.responseBodyJson else {
throw OWSAssertionError("Missing or invalid JSON")
}
guard let parser = ParamParser(responseObject: json) else {
throw OWSAssertionError("Invalid responseObject.")
}
let timestamp: UInt64 = try parser.required(key: "timestamp")
let serviceDate = Date(millisecondsSince1970: timestamp)
let currencyObjects: [Any] = try parser.required(key: "currencies")
var conversionRateMap = ConversionRateMap()
for currencyObject in currencyObjects {
guard let currencyParser = ParamParser(responseObject: currencyObject) else {
throw OWSAssertionError("Invalid currencyObject.")
}
guard let parser = ParamParser(responseObject: json) else {
throw OWSAssertionError("Invalid responseObject.")
let base: String = try currencyParser.required(key: "base")
guard base == PaymentsConstants.mobileCoinCurrencyIdentifier else {
continue
}
let timestamp: UInt64 = try parser.required(key: "timestamp")
let serviceDate = Date(millisecondsSince1970: timestamp)
let currencyObjects: [Any] = try parser.required(key: "currencies")
var conversionRateMap = ConversionRateMap()
for currencyObject in currencyObjects {
guard let currencyParser = ParamParser(responseObject: currencyObject) else {
throw OWSAssertionError("Invalid currencyObject.")
}
let base: String = try currencyParser.required(key: "base")
guard base == PaymentsConstants.mobileCoinCurrencyIdentifier else {
let conversionObjects: [String: NSNumber] = try currencyParser.required(key: "conversions")
for (currencyCode, nsExchangeRate) in conversionObjects {
guard currencyCode.count == 3 else {
Logger.warn("Ignoring invalid currencyCode: \(currencyCode)")
continue
}
let conversionObjects: [String: NSNumber] = try currencyParser.required(key: "conversions")
for (currencyCode, nsExchangeRate) in conversionObjects {
guard currencyCode.count == 3 else {
Logger.warn("Ignoring invalid currencyCode: \(currencyCode)")
continue
}
let exchangeRate = nsExchangeRate.doubleValue
guard exchangeRate > 0 else {
Logger.warn("Ignoring invalid exchangeRate: \(exchangeRate), currencyCode: \(currencyCode)")
continue
}
conversionRateMap[currencyCode] = exchangeRate
let exchangeRate = nsExchangeRate.doubleValue
guard exchangeRate > 0 else {
Logger.warn("Ignoring invalid exchangeRate: \(exchangeRate), currencyCode: \(currencyCode)")
continue
}
conversionRateMap[currencyCode] = exchangeRate
}
return ConversionRates(conversionRateMap: conversionRateMap, serviceDate: serviceDate)
}.done(on: DispatchQueue.global()) { (conversionRates: ConversionRates) in
Logger.info("Success.")
self.setConversionRates(conversionRates)
isUpdateInFlight.set(false)
}.catch(on: DispatchQueue.global()) { error in
owsFailDebugUnlessNetworkFailure(error)
isUpdateInFlight.set(false)
}
self.setConversionRates(ConversionRates(conversionRateMap: conversionRateMap, serviceDate: serviceDate))
Logger.info("Success.")
}
public static let paymentConversionRatesDidChange = NSNotification.Name("paymentConversionRatesDidChange")

View File

@@ -8,11 +8,9 @@ import Foundation
// This class can be used to coordinate the refresh of a
// value obtained from the network.
@MainActor
public class RefreshEvent {
public typealias Block = () -> Void
private let block: Block
private let block: @MainActor () -> Void
private let refreshInterval: TimeInterval
@@ -29,7 +27,7 @@ public class RefreshEvent {
public init(
appReadiness: AppReadiness,
refreshInterval: TimeInterval,
block: @escaping Block
block: @escaping @MainActor () -> Void,
) {
self.appReadiness = appReadiness
self.refreshInterval = refreshInterval
@@ -107,13 +105,13 @@ public class RefreshEvent {
guard refreshTimer == nil else {
return
}
refreshTimer = WeakTimer.scheduledTimer(timeInterval: refreshInterval,
target: self,
userInfo: nil,
repeats: true) { [weak self] _ in
self?.fireEvent()
}
refreshTimer = WeakTimer.scheduledTimer(
timeInterval: refreshInterval,
target: self,
userInfo: nil,
repeats: true,
action: { [weak self] _ in self?.fireEvent() },
)
fireEvent()
}
}

View File

@@ -40,6 +40,7 @@ public class SUIEnvironment: NSObject {
SwiftSingletons.register(self)
}
@MainActor
public func setUp(
appReadiness: AppReadiness,
authCredentialManager: any AuthCredentialManager

View File

@@ -19,6 +19,7 @@ public class PaymentsImpl: NSObject, PaymentsSwift {
public static let maxPaymentMemoMessageLength: Int = 32
@MainActor
public init(appReadiness: AppReadiness) {
self.appReadiness = appReadiness
self.paymentsReconciliation = PaymentsReconciliation(appReadiness: appReadiness)