diff --git a/Package.swift b/Package.swift index 048f4f333..e98b56b8b 100644 --- a/Package.swift +++ b/Package.swift @@ -1,7 +1,11 @@ -// swift-tools-version: 5.9 +// swift-tools-version: 6.0 import PackageDescription +let swiftSettings: [SwiftSetting] = [ + .enableUpcomingFeature("StrictConcurrency") +] + let package = Package( name: "ProcessOut", defaultLocalization: "en", @@ -26,7 +30,8 @@ let package = Package( exclude: ["swiftgen.yml"], resources: [ .process("Resources") - ] + ], + swiftSettings: swiftSettings ), .target( name: "ProcessOutCheckout3DS", @@ -45,7 +50,8 @@ let package = Package( ], resources: [ .process("Resources") - ] + ], + swiftSettings: swiftSettings ), .target( name: "ProcessOutCoreUI", @@ -54,7 +60,8 @@ let package = Package( ], resources: [ .process("Resources") - ] + ], + swiftSettings: swiftSettings ), .binaryTarget(name: "cmark", path: "Vendor/cmark.xcframework") ] diff --git a/Sources/ProcessOut/Sources/Api/Builders/ProcessOutHttpConnectorBuilder.swift b/Sources/ProcessOut/Sources/Api/Builders/ProcessOutHttpConnectorBuilder.swift index 4d53a121b..97c1bb239 100644 --- a/Sources/ProcessOut/Sources/Api/Builders/ProcessOutHttpConnectorBuilder.swift +++ b/Sources/ProcessOut/Sources/Api/Builders/ProcessOutHttpConnectorBuilder.swift @@ -11,7 +11,7 @@ import Foundation final class ProcessOutHttpConnectorBuilder { /// Connector configuration provider. - var configuration: (() -> HttpConnectorRequestMapperConfiguration)? + var configuration: (@Sendable () -> HttpConnectorRequestMapperConfiguration)? /// Retry strategy to use for failing requests. var retryStrategy: RetryStrategy? = .exponential(maximumRetries: 3, interval: 0.1, rate: 3) @@ -89,7 +89,7 @@ final class ProcessOutHttpConnectorBuilder { extension ProcessOutHttpConnectorBuilder { - func with(configuration: @escaping () -> HttpConnectorRequestMapperConfiguration) -> Self { + func with(configuration: @escaping @Sendable () -> HttpConnectorRequestMapperConfiguration) -> Self { self.configuration = configuration return self } diff --git a/Sources/ProcessOut/Sources/Api/Models/PODeepLinkReceivedEvent.swift b/Sources/ProcessOut/Sources/Api/Models/PODeepLinkReceivedEvent.swift index 9feb2fb01..6a474d5da 100644 --- a/Sources/ProcessOut/Sources/Api/Models/PODeepLinkReceivedEvent.swift +++ b/Sources/ProcessOut/Sources/Api/Models/PODeepLinkReceivedEvent.swift @@ -7,7 +7,8 @@ import Foundation -@_spi(PO) public struct PODeepLinkReceivedEvent: POEventEmitterEvent { +@_spi(PO) +public struct PODeepLinkReceivedEvent: POEventEmitterEvent { /// Url representing deep link or universal link. public let url: URL diff --git a/Sources/ProcessOut/Sources/Api/Models/ProcessOutConfiguration.swift b/Sources/ProcessOut/Sources/Api/Models/ProcessOutConfiguration.swift index 158d87d92..a4b4bc665 100644 --- a/Sources/ProcessOut/Sources/Api/Models/ProcessOutConfiguration.swift +++ b/Sources/ProcessOut/Sources/Api/Models/ProcessOutConfiguration.swift @@ -13,9 +13,9 @@ public typealias ProcessOutApiConfiguration = ProcessOutConfiguration /// Defines configuration parameters that are used to create API singleton. In order to create instance /// of this structure one should use ``ProcessOutConfiguration/production(projectId:appVersion:isDebug:)`` /// method. -public struct ProcessOutConfiguration { +public struct ProcessOutConfiguration: Sendable { - public struct Application { + public struct Application: Sendable { /// Application name. public let name: String? diff --git a/Sources/ProcessOut/Sources/Api/ProcessOut.swift b/Sources/ProcessOut/Sources/Api/ProcessOut.swift index 7a6eea6ac..509f0e1c5 100644 --- a/Sources/ProcessOut/Sources/Api/ProcessOut.swift +++ b/Sources/ProcessOut/Sources/Api/ProcessOut.swift @@ -5,6 +5,8 @@ // Created by Andrii Vysotskyi on 07.10.2022. // +// swiftlint:disable implicitly_unwrapped_optional force_unwrapping + import Foundation import UIKit @@ -12,58 +14,28 @@ import UIKit public typealias ProcessOutApi = ProcessOut /// Provides access to shared api instance and a way to configure it. -/// - NOTE: Methods and properties of this class **must** be only accessed from main thread. -public final class ProcessOut { +/// - NOTE: Instance methods and properties of this class could be access from any thread. +public final class ProcessOut: @unchecked Sendable { /// Current configuration. public var configuration: ProcessOutConfiguration { - _configuration + _configuration.wrappedValue } /// Returns gateway configurations repository. - public private(set) lazy var gatewayConfigurations: POGatewayConfigurationsRepository = { - HttpGatewayConfigurationsRepository(connector: httpConnector) - }() + public private(set) var gatewayConfigurations: POGatewayConfigurationsRepository! - /// Returns invoices service. - public private(set) lazy var invoices: POInvoicesService = { - let repository = HttpInvoicesRepository(connector: httpConnector) - return DefaultInvoicesService(repository: repository, threeDSService: threeDSService, logger: serviceLogger) - }() + /// Invoices service. + public private(set) var invoices: POInvoicesService! - /// Returns alternative payment methods service. - public private(set) lazy var alternativePaymentMethods: POAlternativePaymentMethodsService = { - let serviceConfiguration: () -> AlternativePaymentMethodsServiceConfiguration = { [unowned self] in - let configuration = self.configuration - return .init(projectId: configuration.projectId, baseUrl: configuration.checkoutBaseUrl) - } - return DefaultAlternativePaymentMethodsService(configuration: serviceConfiguration, logger: serviceLogger) - }() + /// Alternative payment methods service. + public private(set) var alternativePaymentMethods: POAlternativePaymentMethodsService! - /// Returns cards repository. - public private(set) lazy var cards: POCardsService = { - let contactMapper = DefaultPassKitContactMapper( - logger: serviceLogger - ) - let requestMapper = DefaultApplePayCardTokenizationRequestMapper( - contactMapper: contactMapper, - decoder: JSONDecoder(), - logger: serviceLogger - ) - let service = DefaultCardsService( - repository: HttpCardsRepository(connector: httpConnector), - applePayCardTokenizationRequestMapper: requestMapper - ) - return service - }() + /// Cards service. + public private(set) var cards: POCardsService! /// Returns customer tokens service. - public private(set) lazy var customerTokens: POCustomerTokensService = { - let repository = HttpCustomerTokensRepository(connector: httpConnector) - return DefaultCustomerTokensService( - repository: repository, threeDSService: threeDSService, logger: serviceLogger - ) - }() + public private(set) var customerTokens: POCustomerTokensService! /// Call this method in your app or scene delegate whenever your implementation receives incoming URL. Only deep /// links are supported. @@ -79,15 +51,15 @@ public final class ProcessOut { /// Logger with application category. @_spi(PO) - public private(set) lazy var logger: POLogger = createLogger(for: Constants.applicationLoggerCategory) + public private(set) var logger: POLogger! /// Event emitter to use for events exchange. @_spi(PO) - public private(set) lazy var eventEmitter: POEventEmitter = LocalEventEmitter(logger: logger) + public private(set) var eventEmitter: POEventEmitter! /// Images repository. @_spi(PO) - public private(set) lazy var images: POImagesRepository = UrlSessionImagesRepository(session: .shared) + public let images: POImagesRepository = UrlSessionImagesRepository(session: .shared) // MARK: - Private Nested Types @@ -101,58 +73,99 @@ public final class ProcessOut { // MARK: - Private Properties - @POUnfairlyLocked - private var _configuration: ProcessOutConfiguration + private var _configuration: POUnfairlyLocked - private lazy var serviceLogger: POLogger = { - createLogger(for: Constants.serviceLoggerCategory) - }() + // MARK: - Private Methods - private lazy var deviceMetadataProvider: DefaultDeviceMetadataProvider = { - let keychain = Keychain(service: Constants.bundleIdentifier) - return DefaultDeviceMetadataProvider(screen: .main, device: .current, bundle: .main, keychain: keychain) - }() + @MainActor + private init(configuration: ProcessOutConfiguration) { + self._configuration = .init(wrappedValue: configuration) + commonInit() + } - private lazy var httpConnector: HttpConnector = { - createConnector(includeLoggerRemoteDestination: true) - }() + @MainActor + private func commonInit() { + let deviceMetadataProvider = Self.createDeviceMetadataProvider() + let remoteLoggerDestination = createRemoteLoggerDestination(deviceMetadataProvider: deviceMetadataProvider) + let serviceLogger = createLogger( + for: Constants.serviceLoggerCategory, + additionalDestinations: remoteLoggerDestination + ) + logger = createLogger( + for: Constants.applicationLoggerCategory, + additionalDestinations: remoteLoggerDestination + ) + let httpConnector = createConnector(deviceMetadataProvider: deviceMetadataProvider) + let threeDSService = Self.create3DSService() + initServices(httpConnector: httpConnector, threeDSService: threeDSService, logger: serviceLogger) + } - private lazy var remoteLoggerDestination: LoggerDestination = { - let configuration: () -> TelemetryServiceConfiguration = { [unowned self] in - let configuration = self.configuration - return TelemetryServiceConfiguration( - isTelemetryEnabled: configuration.isTelemetryEnabled, - applicationVersion: configuration.application?.version, - applicationName: configuration.application?.name - ) - } - // Telemetry service uses repository with "special" connector. Its logs - // are not submitted to backend to avoid recursion. - let repository = DefaultTelemetryRepository( - connector: createConnector(includeLoggerRemoteDestination: false) + private func initServices(httpConnector: HttpConnector, threeDSService: ThreeDSService, logger: POLogger) { + gatewayConfigurations = HttpGatewayConfigurationsRepository( + connector: httpConnector ) - return DefaultTelemetryService( - configuration: configuration, repository: repository, deviceMetadataProvider: deviceMetadataProvider + invoices = Self.createInvoicesService( + httpConnector: httpConnector, threeDSService: threeDSService, logger: logger + ) + alternativePaymentMethods = createAlternativePaymentsService() + cards = Self.createCardsService( + httpConnector: httpConnector, logger: logger ) - }() + customerTokens = Self.createCustomerTokensService( + httpConnector: httpConnector, threeDSService: threeDSService, logger: logger + ) + eventEmitter = LocalEventEmitter(logger: logger) + } + + // MARK: - - private lazy var threeDSService: ThreeDSService = { + private static func createCardsService(httpConnector: HttpConnector, logger: POLogger) -> POCardsService { + let contactMapper = DefaultPassKitContactMapper(logger: logger) + let requestMapper = DefaultApplePayCardTokenizationRequestMapper( + contactMapper: contactMapper, decoder: JSONDecoder(), logger: logger + ) + let service = DefaultCardsService( + repository: HttpCardsRepository(connector: httpConnector), + applePayCardTokenizationRequestMapper: requestMapper + ) + return service + } + + private static func createInvoicesService( + httpConnector: HttpConnector, threeDSService: ThreeDSService, logger: POLogger + ) -> POInvoicesService { + let repository = HttpInvoicesRepository(connector: httpConnector) + return DefaultInvoicesService(repository: repository, threeDSService: threeDSService, logger: logger) + } + + private static func createCustomerTokensService( + httpConnector: HttpConnector, threeDSService: ThreeDSService, logger: POLogger + ) -> POCustomerTokensService { + let repository = HttpCustomerTokensRepository(connector: httpConnector) + return DefaultCustomerTokensService(repository: repository, threeDSService: threeDSService, logger: logger) + } + + private func createAlternativePaymentsService() -> POAlternativePaymentMethodsService { + let serviceConfiguration = { @Sendable [unowned self] () -> AlternativePaymentMethodsServiceConfiguration in + let configuration = self.configuration + return .init(projectId: configuration.projectId, baseUrl: configuration.checkoutBaseUrl) + } + return DefaultAlternativePaymentMethodsService(configuration: serviceConfiguration, logger: logger) + } + + private static func create3DSService() -> DefaultThreeDSService { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .useDefaultKeys let encoder = JSONEncoder() encoder.dataEncodingStrategy = .base64 encoder.keyEncodingStrategy = .useDefaultKeys return DefaultThreeDSService(decoder: decoder, encoder: encoder) - }() - - // MARK: - Private Methods - - private init(configuration: ProcessOutConfiguration) { - self.__configuration = .init(wrappedValue: configuration) } - private func createConnector(includeLoggerRemoteDestination: Bool) -> HttpConnector { - let connectorConfiguration = { [unowned self] in + private func createConnector( + deviceMetadataProvider: DeviceMetadataProvider, remoteLoggerDestination: LoggerDestination? = nil + ) -> HttpConnector { + let connectorConfiguration = { @Sendable [unowned self] in let configuration = self.configuration return HttpConnectorRequestMapperConfiguration( baseUrl: configuration.apiBaseUrl, @@ -163,8 +176,7 @@ public final class ProcessOut { ) } let logger = createLogger( - for: Constants.connectorLoggerCategory, - includeRemoteDestination: includeLoggerRemoteDestination + for: Constants.connectorLoggerCategory, additionalDestinations: remoteLoggerDestination ) let connector = ProcessOutHttpConnectorBuilder() .with(configuration: connectorConfiguration) @@ -174,18 +186,47 @@ public final class ProcessOut { return connector } - private func createLogger(for category: String, includeRemoteDestination: Bool = true) -> POLogger { + private func createLogger( + for category: String, additionalDestinations: LoggerDestination?... + ) -> POLogger { var destinations: [LoggerDestination] = [ SystemLoggerDestination(subsystem: Constants.bundleIdentifier) ] - if includeRemoteDestination { - destinations.append(remoteLoggerDestination) - } - let minimumLevel: () -> LogLevel = { [unowned self] in + destinations.append( + contentsOf: additionalDestinations.compactMap { $0 } + ) + let minimumLevel = { @Sendable [unowned self] () -> LogLevel in configuration.isDebug ? .debug : .info } return POLogger(destinations: destinations, category: category, minimumLevel: minimumLevel) } + + private func createRemoteLoggerDestination( + deviceMetadataProvider: DeviceMetadataProvider + ) -> DefaultTelemetryService { + let configuration = { @Sendable [unowned self] () -> TelemetryServiceConfiguration in + let configuration = self.configuration + return TelemetryServiceConfiguration( + isTelemetryEnabled: configuration.isTelemetryEnabled, + applicationVersion: configuration.application?.version, + applicationName: configuration.application?.name + ) + } + // Telemetry service uses repository with "special" connector. Its logs + // are not submitted to backend to avoid recursion. + let repository = DefaultTelemetryRepository( + connector: createConnector(deviceMetadataProvider: deviceMetadataProvider) + ) + return DefaultTelemetryService( + configuration: configuration, repository: repository, deviceMetadataProvider: deviceMetadataProvider + ) + } + + @MainActor + private static func createDeviceMetadataProvider() -> DeviceMetadataProvider { + let keychain = Keychain(service: Constants.bundleIdentifier) + return DefaultDeviceMetadataProvider(screen: .main, device: .current, bundle: .main, keychain: keychain) + } } // MARK: - Singleton @@ -194,13 +235,13 @@ extension ProcessOut { /// Returns boolean value indicating whether SDK is configured and operational. public static var isConfigured: Bool { - _shared != nil + _shared.wrappedValue != nil } /// Shared instance. public static var shared: ProcessOut { precondition(isConfigured, "ProcessOut must be configured before the shared instance is accessed.") - return _shared + return _shared.wrappedValue! } /// Configures ``ProcessOut/shared`` instance. @@ -208,30 +249,36 @@ extension ProcessOut { /// - force: When set to `false` (the default) only the first invocation takes effect, all /// subsequent calls to this method are ignored. Pass `true` to allow existing shared instance /// reconfiguration (if any). + @MainActor public static func configure(configuration: ProcessOutConfiguration, force: Bool = false) { - assert(Thread.isMainThread, "Method must be called only from main thread") + MainActor.preconditionIsolated("Shared instance must be configured from main thread.") if isConfigured { if force { - shared.$_configuration.withLock { $0 = configuration } + shared._configuration.withLock { $0 = configuration } shared.logger.debug("Did change ProcessOut configuration") } else { shared.logger.debug("ProcessOut can be configured only once, ignored") } } else { Self.prewarm() - _shared = ProcessOut(configuration: configuration) + _shared.withLock { instance in + instance = ProcessOut(configuration: configuration) + } shared.logger.debug("Did complete ProcessOut configuration") } } // MARK: - Private Properties - private static var _shared: ProcessOut! // swiftlint:disable:this implicitly_unwrapped_optional + private static let _shared = POUnfairlyLocked(wrappedValue: nil) // MARK: - Private Methods + @MainActor private static func prewarm() { FontFamily.registerAllCustomFonts() PODefaultPhoneNumberMetadataProvider.shared.prewarm() } } + +// swiftlint:enable implicitly_unwrapped_optional force_unwrapping diff --git a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift index 75cd81cf5..c19ebb228 100644 --- a/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOut/Sources/Api/Utils/Test3DS/POTest3DSService.swift @@ -19,13 +19,14 @@ public final class POTest3DSService: PO3DSService { } /// View controller to use for presentations. + @MainActor public unowned var viewController: UIViewController! // swiftlint:disable:this implicitly_unwrapped_optional // MARK: - PO3DSService public func authenticationRequest( configuration: PO3DS2Configuration, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let request = PO3DS2AuthenticationRequest( deviceData: "", @@ -37,32 +38,36 @@ public final class POTest3DSService: PO3DSService { completion(.success(request)) } - public func handle(challenge: PO3DS2Challenge, completion: @escaping (Result) -> Void) { - let alertController = UIAlertController( - title: String(resource: .Test3DS.title), message: "", preferredStyle: .alert - ) - let acceptAction = UIAlertAction(title: String(resource: .Test3DS.accept), style: .default) { _ in - completion(.success(true)) - } - alertController.addAction(acceptAction) - let rejectAction = UIAlertAction(title: String(resource: .Test3DS.reject), style: .default) { _ in - completion(.success(false)) + public func handle(challenge: PO3DS2Challenge, completion: @escaping @Sendable (Result) -> Void) { + MainActor.assumeIsolated { + let alertController = UIAlertController( + title: String(resource: .Test3DS.title), message: "", preferredStyle: .alert + ) + let acceptAction = UIAlertAction(title: String(resource: .Test3DS.accept), style: .default) { _ in + completion(.success(true)) + } + alertController.addAction(acceptAction) + let rejectAction = UIAlertAction(title: String(resource: .Test3DS.reject), style: .default) { _ in + completion(.success(false)) + } + alertController.addAction(rejectAction) + viewController.present(alertController, animated: true) } - alertController.addAction(rejectAction) - viewController.present(alertController, animated: true) } - public func handle(redirect: PO3DSRedirect, completion: @escaping (Result) -> Void) { - let viewController = PO3DSRedirectViewControllerBuilder() - .with(redirect: redirect) - .with(returnUrl: returnUrl) - .with { [weak self] result in - self?.viewController.presentedViewController?.dismiss(animated: true) { - completion(result) + public func handle(redirect: PO3DSRedirect, completion: @escaping @Sendable (Result) -> Void) { + MainActor.assumeIsolated { + let viewController = PO3DSRedirectViewControllerBuilder() + .with(redirect: redirect) + .with(returnUrl: returnUrl) + .with { [weak self] result in + self?.viewController.presentedViewController?.dismiss(animated: true) { + completion(result) + } } - } - .build() - self.viewController.present(viewController, animated: true) + .build() + self.viewController.present(viewController, animated: true) + } } // MARK: - Private Properties diff --git a/Sources/ProcessOut/Sources/Connectors/Http/HttpConnector.swift b/Sources/ProcessOut/Sources/Connectors/Http/HttpConnector.swift index d4c564d2b..05d989c0a 100644 --- a/Sources/ProcessOut/Sources/Connectors/Http/HttpConnector.swift +++ b/Sources/ProcessOut/Sources/Connectors/Http/HttpConnector.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 10.10.2022. // -protocol HttpConnector: AnyObject { +protocol HttpConnector: AnyObject, Sendable { typealias Failure = HttpConnectorFailure diff --git a/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/DefaultHttpConnectorRequestMapper.swift b/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/DefaultHttpConnectorRequestMapper.swift index 1921031c9..481d435c8 100644 --- a/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/DefaultHttpConnectorRequestMapper.swift +++ b/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/DefaultHttpConnectorRequestMapper.swift @@ -10,7 +10,7 @@ import Foundation final class DefaultHttpConnectorRequestMapper: HttpConnectorRequestMapper { init( - configuration: @escaping () -> HttpConnectorRequestMapperConfiguration, + configuration: @escaping @Sendable () -> HttpConnectorRequestMapperConfiguration, encoder: JSONEncoder, deviceMetadataProvider: DeviceMetadataProvider, logger: POLogger @@ -51,7 +51,7 @@ final class DefaultHttpConnectorRequestMapper: HttpConnectorRequestMapper { // MARK: - Private Properties - private let configuration: () -> HttpConnectorRequestMapperConfiguration + private let configuration: @Sendable () -> HttpConnectorRequestMapperConfiguration private let encoder: JSONEncoder private let deviceMetadataProvider: DeviceMetadataProvider private let logger: POLogger diff --git a/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/HttpConnectorRequestMapper.swift b/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/HttpConnectorRequestMapper.swift index b2970efac..772fb41b0 100644 --- a/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/HttpConnectorRequestMapper.swift +++ b/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/RequestMapper/HttpConnectorRequestMapper.swift @@ -7,7 +7,7 @@ import Foundation -protocol HttpConnectorRequestMapper { +protocol HttpConnectorRequestMapper: Sendable { /// Transforms given `HttpConnectorRequest` to `URLRequest`. func urlRequest(from request: HttpConnectorRequest) async throws -> URLRequest diff --git a/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/UrlSessionHttpConnector.swift b/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/UrlSessionHttpConnector.swift index 6bb2b7901..f6c1a3845 100644 --- a/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/UrlSessionHttpConnector.swift +++ b/Sources/ProcessOut/Sources/Connectors/Http/Implementations/UrlSession/UrlSessionHttpConnector.swift @@ -96,7 +96,7 @@ final class UrlSessionHttpConnector: HttpConnector { } } -private struct Response: Decodable { +private struct Response: Decodable, Sendable { /// Indicates whether request was processed successfully. let success: Bool diff --git a/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorFailure.swift b/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorFailure.swift index 83f1e2606..c332bfec4 100644 --- a/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorFailure.swift +++ b/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorFailure.swift @@ -5,9 +5,9 @@ // Created by Andrii Vysotskyi on 10.10.2022. // -enum HttpConnectorFailure: Error { +enum HttpConnectorFailure: Error, Sendable { - struct InvalidField: Decodable { + struct InvalidField: Decodable, Sendable { /// Field name. let name: String @@ -16,7 +16,7 @@ enum HttpConnectorFailure: Error { let message: String } - struct Server: Decodable { + struct Server: Decodable, Sendable { /// Error type. let errorType: String diff --git a/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorRequest.swift b/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorRequest.swift index 2aee97bac..582f6d73c 100644 --- a/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorRequest.swift +++ b/Sources/ProcessOut/Sources/Connectors/Http/Models/HttpConnectorRequest.swift @@ -7,12 +7,14 @@ import Foundation -struct HttpConnectorRequest { +struct HttpConnectorRequest: Sendable { enum Method: String { case get, put, post } + typealias Body = Sendable & Encodable + /// Request identifier. let id: String @@ -23,10 +25,10 @@ struct HttpConnectorRequest { let path: String /// Query items. - let query: [String: CustomStringConvertible] + let query: [String: String] /// Parameters. - let body: Encodable? + let body: Body? /// Custom headers. let headers: [String: String] @@ -54,7 +56,7 @@ extension HttpConnectorRequest { id: UUID().uuidString, method: .get, path: path, - query: query, + query: query.mapValues(\.description), body: nil, headers: headers, includesDeviceMetadata: false, @@ -64,7 +66,7 @@ extension HttpConnectorRequest { static func post( path: String, - body: Encodable? = nil, + body: Body? = nil, headers: [String: String] = [:], includesDeviceMetadata: Bool = false, requiresPrivateKey: Bool = false @@ -83,7 +85,7 @@ extension HttpConnectorRequest { static func put( path: String, - body: Encodable? = nil, + body: Body? = nil, headers: [String: String] = [:], includesDeviceMetadata: Bool = false, requiresPrivateKey: Bool = false diff --git a/Sources/ProcessOut/Sources/Core/Cancellable/POCancellable.swift b/Sources/ProcessOut/Sources/Core/Cancellable/POCancellable.swift index b5debb144..a6bb0ceef 100644 --- a/Sources/ProcessOut/Sources/Core/Cancellable/POCancellable.swift +++ b/Sources/ProcessOut/Sources/Core/Cancellable/POCancellable.swift @@ -9,7 +9,7 @@ public typealias POCancellableType = POCancellable /// A protocol indicating that an activity or action supports cancellation. -public protocol POCancellable { +public protocol POCancellable: Sendable { /// Cancel the activity. func cancel() diff --git a/Sources/ProcessOut/Sources/Core/CodingUtils/POFallbackDecodable/POFallbackValueProvider.swift b/Sources/ProcessOut/Sources/Core/CodingUtils/POFallbackDecodable/POFallbackValueProvider.swift index e9cff7683..b83a87c2c 100644 --- a/Sources/ProcessOut/Sources/Core/CodingUtils/POFallbackDecodable/POFallbackValueProvider.swift +++ b/Sources/ProcessOut/Sources/Core/CodingUtils/POFallbackDecodable/POFallbackValueProvider.swift @@ -8,7 +8,7 @@ import Foundation /// Contract for providing a default value of a Type. -public protocol POFallbackValueProvider { +public protocol POFallbackValueProvider: Sendable { associatedtype Value diff --git a/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableExcludedCodable.swift b/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableExcludedCodable.swift index a8ce028e2..755370a1f 100644 --- a/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableExcludedCodable.swift +++ b/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableExcludedCodable.swift @@ -30,3 +30,5 @@ extension KeyedEncodingContainer { _ value: POImmutableExcludedCodable, forKey key: KeyedEncodingContainer.Key ) throws { /* Ignored */ } } + +extension POImmutableExcludedCodable: Sendable where Value: Sendable { } diff --git a/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableDecimal.swift b/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableDecimal.swift index 70ad98ea8..5b6b1abf5 100644 --- a/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableDecimal.swift +++ b/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableDecimal.swift @@ -12,7 +12,7 @@ import Foundation /// Property wrapper that allows to encode and decode `Decimal` to/from string representation. Value is coded /// in en_US locale. @propertyWrapper -public struct POImmutableStringCodableDecimal: Codable { +public struct POImmutableStringCodableDecimal: Codable, Sendable { public let wrappedValue: Decimal diff --git a/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableOptionalDecimal.swift b/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableOptionalDecimal.swift index d7364eff8..a5b710029 100644 --- a/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableOptionalDecimal.swift +++ b/Sources/ProcessOut/Sources/Core/CodingUtils/POImmutableStringCodableOptionalDecimal.swift @@ -12,7 +12,7 @@ import Foundation /// Property wrapper that allows to encode and decode optional `Decimal` to/from string representation. Value is coded /// in en_US locale. @propertyWrapper -public struct POImmutableStringCodableOptionalDecimal: Codable { +public struct POImmutableStringCodableOptionalDecimal: Codable, Sendable { public let wrappedValue: Decimal? diff --git a/Sources/ProcessOut/Sources/Core/CodingUtils/POStringCodableColor.swift b/Sources/ProcessOut/Sources/Core/CodingUtils/POStringCodableColor.swift index ca5e91a3a..b6ee712c1 100644 --- a/Sources/ProcessOut/Sources/Core/CodingUtils/POStringCodableColor.swift +++ b/Sources/ProcessOut/Sources/Core/CodingUtils/POStringCodableColor.swift @@ -10,7 +10,7 @@ import UIKit /// Property wrapper that allows to decode UIColor from string representations. @propertyWrapper -public struct POStringCodableColor: Decodable { +public struct POStringCodableColor: Decodable, Sendable { public var wrappedValue: UIColor diff --git a/Sources/ProcessOut/Sources/Core/CodingUtils/VoidCodable.swift b/Sources/ProcessOut/Sources/Core/CodingUtils/VoidCodable.swift index 2eeda7d57..a7c4e89e4 100644 --- a/Sources/ProcessOut/Sources/Core/CodingUtils/VoidCodable.swift +++ b/Sources/ProcessOut/Sources/Core/CodingUtils/VoidCodable.swift @@ -5,4 +5,4 @@ // Created by Andrii Vysotskyi on 30.11.2022. // -struct VoidCodable: Codable { } +struct VoidCodable: Codable, Sendable { } diff --git a/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadata.swift b/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadata.swift index 80da5cd1b..58fe897d0 100644 --- a/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadata.swift +++ b/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadata.swift @@ -7,7 +7,7 @@ import Foundation -struct DeviceMetadata: Encodable { +struct DeviceMetadata: Encodable, Sendable { /// Current device identifier. @POImmutableExcludedCodable diff --git a/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadataProvider.swift b/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadataProvider.swift index 00ef672c6..d61542261 100644 --- a/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadataProvider.swift +++ b/Sources/ProcessOut/Sources/Core/DeviceMetadata/DeviceMetadataProvider.swift @@ -5,7 +5,7 @@ // Created by Simeon Kostadinov on 01/11/2022. // -protocol DeviceMetadataProvider { +protocol DeviceMetadataProvider: Sendable { /// Returns device metadata. var deviceMetadata: DeviceMetadata { get async } diff --git a/Sources/ProcessOut/Sources/Core/EventEmitter/POEventEmitterEvent.swift b/Sources/ProcessOut/Sources/Core/EventEmitter/POEventEmitterEvent.swift index 97ea50358..1f96154df 100644 --- a/Sources/ProcessOut/Sources/Core/EventEmitter/POEventEmitterEvent.swift +++ b/Sources/ProcessOut/Sources/Core/EventEmitter/POEventEmitterEvent.swift @@ -5,7 +5,8 @@ // Created by Andrii Vysotskyi on 10.05.2023. // -@_spi(PO) public protocol POEventEmitterEvent: Sendable { +@_spi(PO) +public protocol POEventEmitterEvent: Sendable { /// Event name. static var name: String { get } diff --git a/Sources/ProcessOut/Sources/Core/Utils/Task+Sleep.swift b/Sources/ProcessOut/Sources/Core/Extensions/Task+Sleep.swift similarity index 100% rename from Sources/ProcessOut/Sources/Core/Utils/Task+Sleep.swift rename to Sources/ProcessOut/Sources/Core/Extensions/Task+Sleep.swift diff --git a/Sources/ProcessOut/Sources/Core/Utils/UIImage+Dynamic.swift b/Sources/ProcessOut/Sources/Core/Extensions/UIImage+Dynamic.swift similarity index 97% rename from Sources/ProcessOut/Sources/Core/Utils/UIImage+Dynamic.swift rename to Sources/ProcessOut/Sources/Core/Extensions/UIImage+Dynamic.swift index 1bcc88a67..ca5969e16 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/UIImage+Dynamic.swift +++ b/Sources/ProcessOut/Sources/Core/Extensions/UIImage+Dynamic.swift @@ -10,6 +10,7 @@ import UIKit extension UIImage { static func dynamic(lightImage: UIImage?, darkImage: UIImage?) -> UIImage? { + assert(Thread.isMainThread) // When image with scale greater than 3 is registed asset created explicitly produced image // is malformed and doesn't contain images for light nor dark styles. guard let image = lightImage ?? darkImage else { diff --git a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/PODefaultPhoneNumberMetadataProvider.swift b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/PODefaultPhoneNumberMetadataProvider.swift index 7be0c4446..20f1c4a73 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/PODefaultPhoneNumberMetadataProvider.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/PODefaultPhoneNumberMetadataProvider.swift @@ -7,7 +7,8 @@ import Foundation -@_spi(PO) public final class PODefaultPhoneNumberMetadataProvider: POPhoneNumberMetadataProvider { +@_spi(PO) +public final class PODefaultPhoneNumberMetadataProvider: POPhoneNumberMetadataProvider { public static let shared = PODefaultPhoneNumberMetadataProvider() @@ -20,19 +21,17 @@ import Foundation public func metadata(for countryCode: String) -> POPhoneNumberMetadata? { let transformedCountryCode = countryCode.applyingTransform(.toLatin, reverse: false) ?? countryCode - if let metadata = metadata { + if let metadata = metadata.wrappedValue { return metadata[transformedCountryCode] } loadMetadata(sync: true) - return metadata?[transformedCountryCode] + return metadata.wrappedValue?[transformedCountryCode] } // MARK: - Private Properties private let dispatchQueue: DispatchQueue - - @POUnfairlyLocked - private var metadata: [String: POPhoneNumberMetadata]? + private let metadata = POUnfairlyLocked<[String: POPhoneNumberMetadata]?>(wrappedValue: nil) // MARK: - Private Methods @@ -42,7 +41,7 @@ import Foundation private func loadMetadata(sync: Bool) { let dispatchWorkItem = DispatchWorkItem { [weak self] in - guard let self, self.metadata == nil else { + guard let self, self.metadata.wrappedValue == nil else { return } let groupedMetadata: [String: POPhoneNumberMetadata] @@ -57,7 +56,7 @@ import Foundation assertionFailure("Failed to load metadata: \(error)") groupedMetadata = [:] } - self.$metadata.withLock { $0 = groupedMetadata } + self.metadata.withLock { $0 = groupedMetadata } } let executor = sync ? dispatchQueue.sync : dispatchQueue.async executor(dispatchWorkItem) diff --git a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormat.swift b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberFormat.swift similarity index 81% rename from Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormat.swift rename to Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberFormat.swift index bcd41d63f..2958aeea1 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormat.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberFormat.swift @@ -5,7 +5,8 @@ // Created by Andrii Vysotskyi on 16.03.2023. // -@_spi(PO) public struct POPhoneNumberFormat: Decodable { +@_spi(PO) +public struct POPhoneNumberFormat: Decodable, Sendable { /// Formatting patern. public let pattern: String diff --git a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberMetadata.swift b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadata.swift similarity index 77% rename from Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberMetadata.swift rename to Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadata.swift index be1e30fae..bb4fdb35f 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberMetadata.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadata.swift @@ -5,7 +5,8 @@ // Created by Andrii Vysotskyi on 16.03.2023. // -@_spi(PO) public struct POPhoneNumberMetadata: Decodable { +@_spi(PO) +public struct POPhoneNumberMetadata: Decodable, Sendable { /// Country code. public let countryCode: String diff --git a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadataProvider.swift b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadataProvider.swift index 761f0f2a8..9d8e25963 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadataProvider.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/MetadataProvider/POPhoneNumberMetadataProvider.swift @@ -5,7 +5,8 @@ // Created by Andrii Vysotskyi on 23.03.2023. // -@_spi(PO) public protocol POPhoneNumberMetadataProvider { +@_spi(PO) +public protocol POPhoneNumberMetadataProvider: Sendable { /// Returns metadata for given country code if any. func metadata(for countryCode: String) -> POPhoneNumberMetadata? diff --git a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormatter.swift b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormatter.swift index e0d4022c3..11017c8b7 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormatter.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/PhoneNumber/POPhoneNumberFormatter.swift @@ -7,7 +7,8 @@ import Foundation -@_spi(PO) public final class POPhoneNumberFormatter: Formatter { +@_spi(PO) +public final class POPhoneNumberFormatter: Formatter { public init(metadataProvider: POPhoneNumberMetadataProvider = PODefaultPhoneNumberMetadataProvider.shared) { regexProvider = RegexProvider.shared diff --git a/Sources/ProcessOut/Sources/Core/Formatters/UrlRequest/UrlRequestFormatter.swift b/Sources/ProcessOut/Sources/Core/Formatters/UrlRequest/UrlRequestFormatter.swift index e85c4ad89..244956dd7 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/UrlRequest/UrlRequestFormatter.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/UrlRequest/UrlRequestFormatter.swift @@ -7,7 +7,7 @@ import Foundation -final class UrlRequestFormatter { +final class UrlRequestFormatter: Sendable { init(prettyPrintedBody: Bool = true) { self.prettyPrintedBody = prettyPrintedBody diff --git a/Sources/ProcessOut/Sources/Core/Formatters/UrlResponse/UrlResponseFormatter.swift b/Sources/ProcessOut/Sources/Core/Formatters/UrlResponse/UrlResponseFormatter.swift index 7233f0763..4814da1a8 100644 --- a/Sources/ProcessOut/Sources/Core/Formatters/UrlResponse/UrlResponseFormatter.swift +++ b/Sources/ProcessOut/Sources/Core/Formatters/UrlResponse/UrlResponseFormatter.swift @@ -7,7 +7,7 @@ import Foundation -final class UrlResponseFormatter { +final class UrlResponseFormatter: Sendable { init(includesHeaders: Bool, prettyPrintedBody: Bool = true) { self.includesHeaders = includesHeaders diff --git a/Sources/ProcessOut/Sources/Core/Keychain/Keychain.swift b/Sources/ProcessOut/Sources/Core/Keychain/Keychain.swift index e83a65b6b..5910572b3 100644 --- a/Sources/ProcessOut/Sources/Core/Keychain/Keychain.swift +++ b/Sources/ProcessOut/Sources/Core/Keychain/Keychain.swift @@ -8,7 +8,7 @@ import Foundation import Security -final class Keychain { +final class Keychain: Sendable { init(service: String) { queryBuilder = KeychainQueryBuilder(service: service) diff --git a/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemAccessibility.swift b/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemAccessibility.swift index 6bffcd397..53df357c6 100644 --- a/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemAccessibility.swift +++ b/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemAccessibility.swift @@ -7,13 +7,13 @@ import Security -struct KeychainItemAccessibility: RawRepresentable { +struct KeychainItemAccessibility: RawRepresentable, Sendable { - let rawValue: CFString + let rawValue: String /// The data in the keychain item cannot be accessed after a restart until /// the device has been unlocked once by the user. static let accessibleAfterFirstUnlockThisDeviceOnly = KeychainItemAccessibility( - rawValue: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly + rawValue: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String ) } diff --git a/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemClass.swift b/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemClass.swift index 40566e251..ffd7b0b97 100644 --- a/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemClass.swift +++ b/Sources/ProcessOut/Sources/Core/Keychain/KeychainItemClass.swift @@ -7,10 +7,10 @@ import Security -struct KeychainItemClass: RawRepresentable { +struct KeychainItemClass: RawRepresentable, Sendable { - let rawValue: CFString + let rawValue: String /// Generic password item. - static let genericPassword = KeychainItemClass(rawValue: kSecClassGenericPassword) + static let genericPassword = KeychainItemClass(rawValue: kSecClassGenericPassword as String) } diff --git a/Sources/ProcessOut/Sources/Core/Logger/Destinations/SystemLoggerDestination.swift b/Sources/ProcessOut/Sources/Core/Logger/Destinations/SystemLoggerDestination.swift index 53f7e878e..e48772e63 100644 --- a/Sources/ProcessOut/Sources/Core/Logger/Destinations/SystemLoggerDestination.swift +++ b/Sources/ProcessOut/Sources/Core/Logger/Destinations/SystemLoggerDestination.swift @@ -31,7 +31,7 @@ final class SystemLoggerDestination: LoggerDestination { private let subsystem: String private let lock: NSLock - private var logs: [String: OSLog] + private nonisolated(unsafe) var logs: [String: OSLog] // MARK: - Private Methods diff --git a/Sources/ProcessOut/Sources/Core/Logger/LoggerDestination.swift b/Sources/ProcessOut/Sources/Core/Logger/LoggerDestination.swift index b98ca8fb6..b759d356c 100644 --- a/Sources/ProcessOut/Sources/Core/Logger/LoggerDestination.swift +++ b/Sources/ProcessOut/Sources/Core/Logger/LoggerDestination.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 25.10.2022. // -protocol LoggerDestination { +protocol LoggerDestination: Sendable { /// Logs given event. func log(event: LogEvent) diff --git a/Sources/ProcessOut/Sources/Core/Logger/Models/LogEvent.swift b/Sources/ProcessOut/Sources/Core/Logger/Models/LogEvent.swift index 973d2ebaa..341c33077 100644 --- a/Sources/ProcessOut/Sources/Core/Logger/Models/LogEvent.swift +++ b/Sources/ProcessOut/Sources/Core/Logger/Models/LogEvent.swift @@ -7,7 +7,7 @@ import Foundation -struct LogEvent { +struct LogEvent: Sendable { /// Logging level. let level: LogLevel @@ -22,7 +22,7 @@ struct LogEvent { let timestamp: Date /// DSO handle. - let dso: UnsafeRawPointer? + nonisolated(unsafe) let dso: UnsafeRawPointer? /// File name. let file: String diff --git a/Sources/ProcessOut/Sources/Core/Logger/Models/POLogAttributeKey.swift b/Sources/ProcessOut/Sources/Core/Logger/Models/POLogAttributeKey.swift index 471eb97e8..507958084 100644 --- a/Sources/ProcessOut/Sources/Core/Logger/Models/POLogAttributeKey.swift +++ b/Sources/ProcessOut/Sources/Core/Logger/Models/POLogAttributeKey.swift @@ -8,7 +8,7 @@ import Foundation @_spi(PO) -public struct POLogAttributeKey: RawRepresentable, ExpressibleByStringLiteral, Hashable { +public struct POLogAttributeKey: RawRepresentable, ExpressibleByStringLiteral, Hashable, Sendable { public init(rawValue: String) { self.rawValue = rawValue diff --git a/Sources/ProcessOut/Sources/Core/Logger/POLogger.swift b/Sources/ProcessOut/Sources/Core/Logger/POLogger.swift index 7df74e90b..f01fde5eb 100644 --- a/Sources/ProcessOut/Sources/Core/Logger/POLogger.swift +++ b/Sources/ProcessOut/Sources/Core/Logger/POLogger.swift @@ -7,11 +7,11 @@ import Foundation -/// An object for writing interpolated string messages to the processout logging system. +/// An object for writing interpolated string messages to the ProcessOut logging system. @_spi(PO) -public struct POLogger { +public struct POLogger: Sendable { - init(destinations: [LoggerDestination] = [], category: String, minimumLevel: @escaping () -> LogLevel) { + init(destinations: [LoggerDestination] = [], category: String, minimumLevel: @escaping @Sendable () -> LogLevel) { self.destinations = destinations self.category = category self.minimumLevel = minimumLevel @@ -83,7 +83,7 @@ public struct POLogger { // MARK: - Private Properties private let destinations: [LoggerDestination] - private let minimumLevel: () -> LogLevel + private let minimumLevel: @Sendable () -> LogLevel private let lock: NSLock private var attributes: [POLogAttributeKey: String] diff --git a/Sources/ProcessOut/Sources/Core/Markdown/MarkdownParser.swift b/Sources/ProcessOut/Sources/Core/Markdown/MarkdownParser.swift index cfa258319..cf3fe646b 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/MarkdownParser.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/MarkdownParser.swift @@ -17,7 +17,9 @@ enum MarkdownParser { guard let document else { preconditionFailure("Failed to parse markdown document") } - return MarkdownDocument(cmarkNode: document) + let markdownDocument = MarkdownDocument(cmarkNode: document) + cmark_node_free(document) + return markdownDocument } /// Escapes given plain text so it can be represented as is, in markdown. diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift index 12a8d2e3e..45212855c 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownBlockQuote.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownBlockQuote: MarkdownBaseNode { +final class MarkdownBlockQuote: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_BLOCK_QUOTE diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift index 5af49fb24..653d97b27 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeBlock.swift @@ -7,26 +7,22 @@ @_implementationOnly import cmark -final class MarkdownCodeBlock: MarkdownBaseNode { +final class MarkdownCodeBlock: MarkdownBaseNode, @unchecked Sendable { - /// Returns the info string from a fenced code block. - private(set) lazy var info: String? = { - guard let info = cmarkNode.pointee.as.code.info else { - return nil - } - return String(cString: info) - }() - - private(set) lazy var code: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() + /// Actual code value. + let code: String // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let literal = cmark_node_get_literal(cmarkNode) { + self.code = String(cString: literal) + } else { + self.code = "" + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_CODE_BLOCK } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift index 3ba6fccb2..0c1dc39d7 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownCodeSpan.swift @@ -7,18 +7,23 @@ @_implementationOnly import cmark -final class MarkdownCodeSpan: MarkdownBaseNode { +final class MarkdownCodeSpan: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var code: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() + /// Code. + let code: String // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let literal = cmark_node_get_literal(cmarkNode) { + code = String(cString: literal) + } else { + assertionFailure("Unable to get text node value") + code = "" + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_CODE } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownDocument.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownDocument.swift index d38f165e8..7e650a6e6 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownDocument.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownDocument.swift @@ -7,13 +7,7 @@ @_implementationOnly import cmark -final class MarkdownDocument: MarkdownBaseNode { - - deinit { - cmark_node_free(cmarkNode) - } - - // MARK: - MarkdownBaseNode +final class MarkdownDocument: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_DOCUMENT diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift index 07335342d..ccfc84b3c 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownEmphasis.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownEmphasis: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownEmphasis: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_EMPH diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownHeading.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownHeading.swift index c64837edb..07f1c41c1 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownHeading.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownHeading.swift @@ -7,14 +7,17 @@ @_implementationOnly import cmark -final class MarkdownHeading: MarkdownBaseNode { +final class MarkdownHeading: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var level: Int = { - Int(cmarkNode.pointee.as.heading.level) - }() + let level: Int // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + level = Int(cmarkNode.pointee.as.heading.level) + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_HEADING } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift index fd73994f6..f0388e79a 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLinebreak.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownLinebreak: MarkdownBaseNode { +final class MarkdownLinebreak: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_LINEBREAK diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLink.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLink.swift index d1d1cd130..bf3778b22 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLink.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownLink.swift @@ -7,17 +7,21 @@ @_implementationOnly import cmark -final class MarkdownLink: MarkdownBaseNode { +final class MarkdownLink: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var url: String? = { - if let url = cmarkNode.pointee.as.link.url { - return String(cString: url) - } - return nil - }() + let url: String? // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let url = cmarkNode.pointee.as.link.url { + self.url = String(cString: url) + } else { + url = nil + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_LINK } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownList.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownList.swift index e5d3fb46c..44d73e36a 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownList.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownList.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownList: MarkdownBaseNode { +final class MarkdownList: MarkdownBaseNode, @unchecked Sendable { enum ListType { @@ -18,7 +18,27 @@ final class MarkdownList: MarkdownBaseNode { case bullet(marker: Character) } - private(set) lazy var type: ListType = { + /// List type. + let type: ListType + + // MARK: - MarkdownBaseNode + + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + type = Self.listType(cmarkNode: cmarkNode) + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + + override static var cmarkNodeType: cmark_node_type { + CMARK_NODE_LIST + } + + override func accept(visitor: V) -> V.Result { + visitor.visit(list: self) + } + + // MARK: - Private Methods + + private static func listType(cmarkNode: CmarkNode) -> ListType { let listNode = cmarkNode.pointee.as.list switch UInt32(listNode.list_type) { case CMARK_BULLET_LIST.rawValue: @@ -40,19 +60,5 @@ final class MarkdownList: MarkdownBaseNode { default: preconditionFailure("Unsupported list type: \(listNode.list_type)") } - }() - - private(set) lazy var isTight: Bool = { - cmarkNode.pointee.as.list.tight - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_LIST - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(list: self) } } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownListItem.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownListItem.swift index 808cc624b..d45e4fe16 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownListItem.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownListItem.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownListItem: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownListItem: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_ITEM diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownNode.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownNode.swift index b2c87df5c..da1890c5e 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownNode.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownNode.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -class MarkdownBaseNode { +class MarkdownBaseNode: @unchecked Sendable { typealias CmarkNode = UnsafeMutablePointer @@ -19,11 +19,20 @@ class MarkdownBaseNode { if validatesType { assert(cmarkNode.pointee.type == Self.cmarkNodeType.rawValue) } - self.cmarkNode = cmarkNode + self.children = Self.children(of: cmarkNode) } /// Returns node children. - private(set) lazy var children: [MarkdownBaseNode] = { + let children: [MarkdownBaseNode] + + /// Accepts given visitor. + func accept(visitor: V) -> V.Result { // swiftlint:disable:this unavailable_function + fatalError("Must be implemented by subclass.") + } + + // MARK: - Private Methods + + private static func children(of cmarkNode: CmarkNode) -> [MarkdownBaseNode] { var cmarkChild = cmarkNode.pointee.first_child var children: [MarkdownBaseNode] = [] while let cmarkNode = cmarkChild { @@ -32,12 +41,5 @@ class MarkdownBaseNode { cmarkChild = cmarkNode.pointee.next } return children - }() - - let cmarkNode: CmarkNode - - /// Accepts given visitor. - func accept(visitor: V) -> V.Result { // swiftlint:disable:this unavailable_function - fatalError("Must be implemented by subclass.") } } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift index 7e5b38a22..1dc9f4c65 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownParagraph.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownParagraph: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownParagraph: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_PARAGRAPH diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift index c35e3a438..c049db6e9 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownSoftbreak.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownSoftbreak: MarkdownBaseNode { +final class MarkdownSoftbreak: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_SOFTBREAK diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownStrong.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownStrong.swift index 089af879f..e9f960a85 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownStrong.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownStrong.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownStrong: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownStrong: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_STRONG diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownText.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownText.swift index 958504d6c..574981d74 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownText.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownText.swift @@ -7,18 +7,23 @@ @_implementationOnly import cmark -final class MarkdownText: MarkdownBaseNode { +final class MarkdownText: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var value: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() + /// Text value. + let value: String // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let literal = cmark_node_get_literal(cmarkNode) { + value = String(cString: literal) + } else { + assertionFailure("Unable to get text node value") + value = "" + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_TEXT } diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift index 1d02e0715..f802f9b01 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownThematicBreak.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownThematicBreak: MarkdownBaseNode { +final class MarkdownThematicBreak: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_THEMATIC_BREAK diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownUnknown.swift b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownUnknown.swift index 13398216f..654488bbc 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownUnknown.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Nodes/MarkdownUnknown.swift @@ -6,7 +6,7 @@ // /// Unknown node. -final class MarkdownUnknown: MarkdownBaseNode { +final class MarkdownUnknown: MarkdownBaseNode, @unchecked Sendable { required init(cmarkNode: CmarkNode, validatesType: Bool = false) { super.init(cmarkNode: cmarkNode, validatesType: false) diff --git a/Sources/ProcessOut/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift b/Sources/ProcessOut/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift index e5aea8978..a2b1797ac 100644 --- a/Sources/ProcessOut/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift +++ b/Sources/ProcessOut/Sources/Core/Markdown/Visitor/MarkdownDebugDescriptionPrinter.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 14.06.2023. // -import Foundation +#if DEBUG final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { @@ -71,11 +71,7 @@ final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { } func visit(codeBlock: MarkdownCodeBlock) -> String { - var attributes: [String: CustomStringConvertible] = [:] - if let info = codeBlock.info { - attributes["info"] = info - } - return description(node: codeBlock, nodeName: "Code Block", attributes: attributes, content: codeBlock.code) + description(node: codeBlock, nodeName: "Code Block", content: codeBlock.code) } func visit(thematicBreak: MarkdownThematicBreak) -> String { @@ -144,3 +140,5 @@ extension MarkdownBaseNode: CustomDebugStringConvertible { return self.accept(visitor: visitor) } } + +#endif diff --git a/Sources/ProcessOut/Sources/Core/PropertyWrappers/ImmutableNullHashable.swift b/Sources/ProcessOut/Sources/Core/PropertyWrappers/ImmutableNullHashable.swift index b89550179..4d5c76ce7 100644 --- a/Sources/ProcessOut/Sources/Core/PropertyWrappers/ImmutableNullHashable.swift +++ b/Sources/ProcessOut/Sources/Core/PropertyWrappers/ImmutableNullHashable.swift @@ -18,3 +18,5 @@ struct ImmutableNullHashable: Hashable { // Ignored } } + +extension ImmutableNullHashable: Sendable where Value: Sendable { } diff --git a/Sources/ProcessOut/Sources/Core/RegexProvider/RegexProvider.swift b/Sources/ProcessOut/Sources/Core/RegexProvider/RegexProvider.swift index a889270fa..e6196dc3a 100644 --- a/Sources/ProcessOut/Sources/Core/RegexProvider/RegexProvider.swift +++ b/Sources/ProcessOut/Sources/Core/RegexProvider/RegexProvider.swift @@ -9,7 +9,7 @@ import Foundation // swiftlint:disable legacy_objc_type -final class RegexProvider { +final class RegexProvider: Sendable { static let shared = RegexProvider() @@ -35,7 +35,7 @@ final class RegexProvider { // MARK: - Private Properties - private let cache: NSCache + private nonisolated(unsafe) let cache: NSCache } // swiftlint:enable legacy_objc_type diff --git a/Sources/ProcessOut/Sources/Core/RetryStrategy/RetryStrategy.swift b/Sources/ProcessOut/Sources/Core/RetryStrategy/RetryStrategy.swift index 4883f0bbe..21326cb6f 100644 --- a/Sources/ProcessOut/Sources/Core/RetryStrategy/RetryStrategy.swift +++ b/Sources/ProcessOut/Sources/Core/RetryStrategy/RetryStrategy.swift @@ -7,7 +7,7 @@ import Foundation -struct RetryStrategy { +struct RetryStrategy: Sendable { /// Returns time interval to void for given retry. func interval(for retry: Int) -> TimeInterval { @@ -18,7 +18,7 @@ struct RetryStrategy { let maximumRetries: Int /// Function to use to calculate delay for given attempt number. - let intervalFunction: (_ retry: Int) -> TimeInterval + let intervalFunction: @Sendable (_ retry: Int) -> TimeInterval } extension RetryStrategy { diff --git a/Sources/ProcessOut/Sources/Core/Utils/AsyncUtils.swift b/Sources/ProcessOut/Sources/Core/Utils/AsyncUtils.swift index dd87f55dd..73521219e 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/AsyncUtils.swift +++ b/Sources/ProcessOut/Sources/Core/Utils/AsyncUtils.swift @@ -15,11 +15,11 @@ func withTimeout( error timeoutError: @autoclosure () -> Error, perform operation: @escaping @Sendable () async throws -> T ) async throws -> T { - @POUnfairlyLocked var isTimedOut = false + let isTimedOut = POUnfairlyLocked(wrappedValue: false) let task = Task(operation: operation) let timeoutTask = Task { try await Task.sleep(seconds: timeout) - $isTimedOut.withLock { value in + isTimedOut.withLock { value in value = true } guard !Task.isCancelled else { @@ -33,7 +33,7 @@ func withTimeout( timeoutTask.cancel() return value } catch { - if task.isCancelled, isTimedOut { + if task.isCancelled, isTimedOut.wrappedValue { throw timeoutError() } timeoutTask.cancel() @@ -49,7 +49,7 @@ func withTimeout( func retry( operation: @escaping @Sendable () async throws -> T, - while condition: @escaping (Result) -> Bool, + while condition: @escaping @Sendable (Result) -> Bool, timeout: TimeInterval, timeoutError: @autoclosure () -> Error, retryStrategy: RetryStrategy? = nil @@ -69,7 +69,7 @@ func retry( private func retry( operation: @escaping @Sendable () async throws -> T, after result: Result, - while condition: @escaping (Result) -> Bool, + while condition: @escaping @Sendable (Result) -> Bool, retryStrategy: RetryStrategy?, attempt: Int ) async throws -> T { diff --git a/Sources/ProcessOut/Sources/Core/Utils/Batcher.swift b/Sources/ProcessOut/Sources/Core/Utils/Batcher.swift index 2d435250b..7899ad5fd 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/Batcher.swift +++ b/Sources/ProcessOut/Sources/Core/Utils/Batcher.swift @@ -7,9 +7,9 @@ import Foundation -final class Batcher { +final class Batcher: Sendable { - typealias Executor = (Array) async -> Bool + typealias Executor = @Sendable (Array) async -> Bool init(executionInterval: TimeInterval = 10, executor: @escaping Executor) { self.executionInterval = executionInterval @@ -38,14 +38,14 @@ final class Batcher { private let executionInterval: TimeInterval private let lock: UnfairLock - private var pendingTasks: [Task] - private var executionTimer: Timer? + private nonisolated(unsafe) var pendingTasks: [Task] + private nonisolated(unsafe) var executionTimer: Timer? // MARK: - Private Methods /// - NOTE: method mutates self but is not thread safe. private func scheduleExecutionUnsafe() { - let timer = Timer(timeInterval: executionInterval, repeats: false) { [weak self] _ in + nonisolated(unsafe) let timer = Timer(timeInterval: executionInterval, repeats: false) { [weak self] _ in guard let self = self else { return } diff --git a/Sources/ProcessOut/Sources/Core/Utils/POStringResource.swift b/Sources/ProcessOut/Sources/Core/Utils/POStringResource.swift index c3fe28e16..7bbdf7370 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/POStringResource.swift +++ b/Sources/ProcessOut/Sources/Core/Utils/POStringResource.swift @@ -7,7 +7,7 @@ import Foundation -@_spi(PO) public struct POStringResource { +@_spi(PO) public struct POStringResource: Sendable { /// The key to use to look up a localized string. let key: String diff --git a/Sources/ProcessOut/Sources/Core/Utils/POTypedRepresentation.swift b/Sources/ProcessOut/Sources/Core/Utils/POTypedRepresentation.swift index d19ade046..47d57d842 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/POTypedRepresentation.swift +++ b/Sources/ProcessOut/Sources/Core/Utils/POTypedRepresentation.swift @@ -102,3 +102,5 @@ extension KeyedDecodingContainer { try type.init(from: try superDecoder(forKey: key)) } } + +extension POTypedRepresentation: Sendable where Wrapped: Sendable { } diff --git a/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/POUnfairlyLocked.swift b/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/POUnfairlyLocked.swift index 2fa43725c..266ed092e 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/POUnfairlyLocked.swift +++ b/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/POUnfairlyLocked.swift @@ -8,8 +8,9 @@ import os /// A thread-safe wrapper around a value. +@_spi(PO) @propertyWrapper -@_spi(PO) public final class POUnfairlyLocked: @unchecked Sendable { +public final class POUnfairlyLocked: @unchecked Sendable { public init(wrappedValue: Value) { value = wrappedValue diff --git a/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/UnfairLock.swift b/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/UnfairLock.swift index 5a12c1c42..e9e0c59cc 100644 --- a/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/UnfairLock.swift +++ b/Sources/ProcessOut/Sources/Core/Utils/UnfairlyLocked/UnfairLock.swift @@ -8,7 +8,7 @@ import os /// An `os_unfair_lock` wrapper. -final class UnfairLock { +final class UnfairLock: Sendable { init() { unfairLock = .allocate(capacity: 1) @@ -30,5 +30,5 @@ final class UnfairLock { // MARK: - Private Properties - private let unfairLock: os_unfair_lock_t + private nonisolated(unsafe) let unfairLock: os_unfair_lock_t } diff --git a/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift b/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift index c29e1741b..293024b92 100644 --- a/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift +++ b/Sources/ProcessOut/Sources/Generated/Sourcery+Generated.swift @@ -82,7 +82,7 @@ extension POCardsService { @discardableResult public func issuerInformation( iin: String, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await issuerInformation(iin: iin) @@ -96,7 +96,7 @@ extension POCardsService { @discardableResult public func tokenize( request: POCardTokenizationRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await tokenize(request: request) @@ -107,7 +107,7 @@ extension POCardsService { @discardableResult public func updateCard( request: POCardUpdateRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await updateCard(request: request) @@ -118,7 +118,7 @@ extension POCardsService { @discardableResult public func tokenize( request: POApplePayCardTokenizationRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await tokenize(request: request) @@ -133,7 +133,7 @@ extension POCustomerTokensService { public func assignCustomerToken( request: POAssignCustomerTokenRequest, threeDSService: PO3DSService, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await assignCustomerToken(request: request, threeDSService: threeDSService) @@ -145,7 +145,7 @@ extension POCustomerTokensService { @discardableResult public func createCustomerToken( request: POCreateCustomerTokenRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await createCustomerToken(request: request) @@ -159,7 +159,7 @@ extension POGatewayConfigurationsRepository { @discardableResult public func all( request: POAllGatewayConfigurationsRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await all(request: request) @@ -170,7 +170,7 @@ extension POGatewayConfigurationsRepository { @discardableResult public func find( request: POFindGatewayConfigurationRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await find(request: request) @@ -180,7 +180,7 @@ extension POGatewayConfigurationsRepository { /// Returns available gateway configurations. @discardableResult public func all( - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await all() @@ -195,7 +195,7 @@ extension POImagesRepository { public func images( at urls: [URL], scale: CGFloat, - completion: @escaping ([URL: UIImage]) -> Void + completion: @escaping @Sendable ([URL: UIImage]) -> Void ) -> POCancellable { invoke(completion: completion) { await images(at: urls, scale: scale) @@ -206,7 +206,7 @@ extension POImagesRepository { @discardableResult public func images( at urls: [URL], - completion: @escaping ([URL: UIImage]) -> Void + completion: @escaping @Sendable ([URL: UIImage]) -> Void ) -> POCancellable { invoke(completion: completion) { await images(at: urls) @@ -218,7 +218,7 @@ extension POImagesRepository { public func image( at url: URL?, scale: CGFloat = 1, - completion: @escaping (UIImage?) -> Void + completion: @escaping @Sendable (UIImage?) -> Void ) -> POCancellable { invoke(completion: completion) { await image(at: url, scale: scale) @@ -231,7 +231,7 @@ extension POImagesRepository { at url1: URL?, _ url2: URL?, scale: CGFloat = 1, - completion: @escaping ((UIImage?, UIImage?)) -> Void + completion: @escaping @Sendable ((UIImage?, UIImage?)) -> Void ) -> POCancellable { invoke(completion: completion) { await images(at: url1, url2, scale: scale) @@ -243,7 +243,7 @@ extension POImagesRepository { @discardableResult public func image( resource: POImageRemoteResource, - completion: @escaping (UIImage?) -> Void + completion: @escaping @Sendable (UIImage?) -> Void ) -> POCancellable { invoke(completion: completion) { await image(resource: resource) @@ -257,7 +257,7 @@ extension POInvoicesService { @discardableResult public func nativeAlternativePaymentMethodTransactionDetails( request: PONativeAlternativePaymentMethodTransactionDetailsRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await nativeAlternativePaymentMethodTransactionDetails(request: request) @@ -271,7 +271,7 @@ extension POInvoicesService { @discardableResult public func initiatePayment( request: PONativeAlternativePaymentMethodRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await initiatePayment(request: request) @@ -282,7 +282,7 @@ extension POInvoicesService { @discardableResult public func invoice( request: POInvoiceRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await invoice(request: request) @@ -294,7 +294,7 @@ extension POInvoicesService { public func authorizeInvoice( request: POInvoiceAuthorizationRequest, threeDSService: PO3DSService, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await authorizeInvoice(request: request, threeDSService: threeDSService) @@ -305,7 +305,7 @@ extension POInvoicesService { @discardableResult public func captureNativeAlternativePayment( request: PONativeAlternativePaymentCaptureRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await captureNativeAlternativePayment(request: request) @@ -317,7 +317,7 @@ extension POInvoicesService { @discardableResult public func createInvoice( request: POInvoiceCreationRequest, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) -> POCancellable { invoke(completion: completion) { try await createInvoice(request: request) @@ -326,9 +326,9 @@ extension POInvoicesService { } /// Invokes given completion with a result of async operation. -private func invoke( - completion: @escaping (Result) -> Void, - after operation: @escaping () async throws -> T +private func invoke( + completion: @escaping @Sendable (Result) -> Void, + after operation: @escaping @Sendable () async throws -> T ) -> POCancellable { Task { @MainActor in do { @@ -344,7 +344,10 @@ private func invoke( } /// Invokes given completion with a result of async operation. -private func invoke(completion: @escaping (T) -> Void, after operation: @escaping () async -> T) -> Task { +private func invoke( + completion: @escaping @Sendable (T) -> Void, + after operation: @escaping @Sendable () async -> T +) -> Task { Task { @MainActor in completion(await operation()) } diff --git a/Sources/ProcessOut/Sources/Legacy/CardPaymentWebView.swift b/Sources/ProcessOut/Sources/Legacy/CardPaymentWebView.swift index 5ebf309a6..eef951d33 100644 --- a/Sources/ProcessOut/Sources/Legacy/CardPaymentWebView.swift +++ b/Sources/ProcessOut/Sources/Legacy/CardPaymentWebView.swift @@ -8,6 +8,7 @@ import Foundation @available(*, deprecated) +@preconcurrency final class CardPaymentWebView: ProcessOutWebView { override func onRedirect(url: URL) { diff --git a/Sources/ProcessOut/Sources/Legacy/ProcessOutRequestManager.swift b/Sources/ProcessOut/Sources/Legacy/ProcessOutRequestManager.swift index 6026771fd..f340a710d 100644 --- a/Sources/ProcessOut/Sources/Legacy/ProcessOutRequestManager.swift +++ b/Sources/ProcessOut/Sources/Legacy/ProcessOutRequestManager.swift @@ -8,6 +8,7 @@ import Foundation @available(*, deprecated) +@preconcurrency final class ProcessOutRequestManager { let apiUrl: String diff --git a/Sources/ProcessOut/Sources/Legacy/ProcessOutWebView.swift b/Sources/ProcessOut/Sources/Legacy/ProcessOutWebView.swift index d1abef5e6..4bbd2eca8 100644 --- a/Sources/ProcessOut/Sources/Legacy/ProcessOutWebView.swift +++ b/Sources/ProcessOut/Sources/Legacy/ProcessOutWebView.swift @@ -9,6 +9,7 @@ import Foundation import WebKit @available(*, deprecated, message: "Use PO3DSRedirectViewControllerBuilder or POAlternativePaymentMethodViewControllerBuilder instead.") +@preconcurrency public class ProcessOutWebView: WKWebView, WKNavigationDelegate, WKUIDelegate { private let REDIRECT_URL_PATTERN = "https:\\/\\/checkout\\.processout\\.(ninja|com)\\/helpers\\/mobile-processout-webview-landing.*" diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/HttpCardsRepository.swift b/Sources/ProcessOut/Sources/Repositories/Cards/HttpCardsRepository.swift index 5e3ffe0f4..a822a2ffb 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/HttpCardsRepository.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/HttpCardsRepository.swift @@ -16,7 +16,7 @@ final class HttpCardsRepository: CardsRepository { // MARK: - CardsRepository func issuerInformation(iin: String) async throws -> POCardIssuerInformation { - struct Response: Decodable { + struct Response: Decodable, Sendable { let cardInformation: POCardIssuerInformation } let httpRequest = HttpConnectorRequest.get(path: "/iins/" + iin) diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/ApplePayCardTokenizationRequest.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/ApplePayCardTokenizationRequest.swift index 1ea2c1521..29db1d7c1 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/ApplePayCardTokenizationRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/ApplePayCardTokenizationRequest.swift @@ -7,9 +7,9 @@ import Foundation -struct ApplePayCardTokenizationRequest: Encodable { +struct ApplePayCardTokenizationRequest: Encodable, Sendable { - struct PaymentMethod { + struct PaymentMethod: Sendable { /// Card display name. let displayName: String? @@ -22,7 +22,7 @@ struct ApplePayCardTokenizationRequest: Encodable { } /// Based on [payment token structure.](https://developer.apple.com/documentation/passkit/apple_pay/payment_token_format_reference#3949537) - struct PaymentData: Codable { + struct PaymentData: Codable, Sendable { /// Encrypted payment data. let data: String @@ -37,7 +37,7 @@ struct ApplePayCardTokenizationRequest: Encodable { let version: String } - struct ApplePayToken { + struct ApplePayToken: Sendable { /// Payment data. let paymentData: PaymentData @@ -49,7 +49,7 @@ struct ApplePayCardTokenizationRequest: Encodable { let transactionIdentifier: String } - struct ApplePay: Encodable { + struct ApplePay: Encodable, Sendable { /// Token details. let token: ApplePayToken diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardTokenizationRequest.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardTokenizationRequest.swift index 64aa46dce..3263a0cae 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardTokenizationRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardTokenizationRequest.swift @@ -8,7 +8,7 @@ import Foundation /// Card details that should be tokenized. -public struct POCardTokenizationRequest: Encodable { +public struct POCardTokenizationRequest: Encodable, Sendable { /// Number of the card. public let number: String diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardUpdateRequest.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardUpdateRequest.swift index 8d9df5ce4..8c0653694 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardUpdateRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POCardUpdateRequest.swift @@ -6,7 +6,7 @@ // /// Updated card details. -public struct POCardUpdateRequest: Encodable { +public struct POCardUpdateRequest: Encodable, Sendable { /// Card id. @POImmutableExcludedCodable diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POContact.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POContact.swift index ab687b2ab..6edc20347 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POContact.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Requests/POContact.swift @@ -8,7 +8,7 @@ import Foundation /// Cardholder information. -public struct POContact: Encodable { +public struct POContact: Encodable, Sendable { /// First line of cardholder’s address. public let address1: String? diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Responses/CardTokenizationResponse.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Responses/CardTokenizationResponse.swift index 163f9463f..d54e9f857 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Responses/CardTokenizationResponse.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Responses/CardTokenizationResponse.swift @@ -7,6 +7,6 @@ import Foundation -struct CardTokenizationResponse: Decodable { +struct CardTokenizationResponse: Decodable, Sendable { let card: POCard } diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCard.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCard.swift index e6b953921..322762bd1 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCard.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCard.swift @@ -9,7 +9,7 @@ import Foundation /// A card object represents a credit or debit card. It contains many useful pieces of information about the card but /// it does not contain the full card number and CVC (which are kept securely in the ProcessOut Vault). -public struct POCard: Decodable, Hashable { +public struct POCard: Decodable, Hashable, @unchecked Sendable { /// Value that uniquely identifies the card. public let id: String diff --git a/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCardIssuerInformation.swift b/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCardIssuerInformation.swift index 21aa03dfa..89b06d80d 100644 --- a/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCardIssuerInformation.swift +++ b/Sources/ProcessOut/Sources/Repositories/Cards/Responses/POCardIssuerInformation.swift @@ -6,7 +6,7 @@ // /// Holds information about card issuing institution that issued the card to the card holder. -public struct POCardIssuerInformation: Decodable { +public struct POCardIssuerInformation: Decodable, Sendable { /// Scheme of the card. @POTypedRepresentation diff --git a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POAssignCustomerTokenRequest.swift b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POAssignCustomerTokenRequest.swift index a59f2abe6..24b924516 100644 --- a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POAssignCustomerTokenRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POAssignCustomerTokenRequest.swift @@ -8,7 +8,7 @@ import Foundation /// Request to use to assign new source to existing customer token and potentially verify it. -public struct POAssignCustomerTokenRequest: Encodable { // sourcery: AutoCodingKeys +public struct POAssignCustomerTokenRequest: Encodable, Sendable { // sourcery: AutoCodingKeys /// Id of the customer who token belongs to. public let customerId: String // sourcery:coding: skip diff --git a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POCreateCustomerTokenRequest.swift b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POCreateCustomerTokenRequest.swift index 6e55ab7a8..db3a39304 100644 --- a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POCreateCustomerTokenRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Requests/POCreateCustomerTokenRequest.swift @@ -8,7 +8,7 @@ import Foundation @_spi(PO) -public struct POCreateCustomerTokenRequest: Encodable { +public struct POCreateCustomerTokenRequest: Encodable, Sendable { /// Customer id to associate created token with. @POImmutableExcludedCodable diff --git a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/AssignCustomerTokenResponse.swift b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/AssignCustomerTokenResponse.swift index 88ef1f2df..fabfb2c71 100644 --- a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/AssignCustomerTokenResponse.swift +++ b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/AssignCustomerTokenResponse.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 27.03.2023. // -struct AssignCustomerTokenResponse: Decodable { +struct AssignCustomerTokenResponse: Decodable, Sendable { /// Optional customer action. let customerAction: ThreeDSCustomerAction? diff --git a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/POCustomerToken.swift b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/POCustomerToken.swift index 26ae04ca9..d25f875ef 100644 --- a/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/POCustomerToken.swift +++ b/Sources/ProcessOut/Sources/Repositories/CustomerTokens/Responses/POCustomerToken.swift @@ -9,10 +9,10 @@ import Foundation /// Customer tokens (usually just called tokens for short) are objects that associate a payment source such as a /// card or APM token with a customer. -public struct POCustomerToken: Decodable { +public struct POCustomerToken: Decodable, Sendable { /// Customer token verification status. - public enum VerificationStatus: String, Decodable { + public enum VerificationStatus: String, Decodable, Sendable { case success, pending, failed, notRequested = "not-requested", unknown } diff --git a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POAllGatewayConfigurationsRequest.swift b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POAllGatewayConfigurationsRequest.swift index fed58a4ad..ccc52976f 100644 --- a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POAllGatewayConfigurationsRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POAllGatewayConfigurationsRequest.swift @@ -5,9 +5,9 @@ // Created by Andrii Vysotskyi on 12.10.2022. // -public struct POAllGatewayConfigurationsRequest { +public struct POAllGatewayConfigurationsRequest: Sendable { - public enum Filter: String { + public enum Filter: String, Sendable { /// Gateways that allow payments using alternative payment methods that allow tokenization. case alternativePaymentMethodsWithTokenization // swiftlint:disable:this identifier_name diff --git a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POFindGatewayConfigurationRequest.swift b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POFindGatewayConfigurationRequest.swift index d6ff8e717..d9aaee978 100644 --- a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POFindGatewayConfigurationRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Requests/POFindGatewayConfigurationRequest.swift @@ -5,11 +5,9 @@ // Created by Andrii Vysotskyi on 27.10.2022. // -import Foundation +public struct POFindGatewayConfigurationRequest: Sendable { -public struct POFindGatewayConfigurationRequest { - - public enum ExpandedProperty: String, Hashable { + public enum ExpandedProperty: String, Hashable, Sendable { case gateway } diff --git a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POAllGatewayConfigurationsResponse.swift b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POAllGatewayConfigurationsResponse.swift index b08149e22..6ebe39edd 100644 --- a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POAllGatewayConfigurationsResponse.swift +++ b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POAllGatewayConfigurationsResponse.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 12.10.2022. // -public struct POAllGatewayConfigurationsResponse: Decodable { +public struct POAllGatewayConfigurationsResponse: Decodable, Sendable { /// Boolean flag indicating whether there are more items to fetch. public let hasMore: Bool diff --git a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POGatewayConfiguration.swift b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POGatewayConfiguration.swift index 64aed6a3c..3105905b3 100644 --- a/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POGatewayConfiguration.swift +++ b/Sources/ProcessOut/Sources/Repositories/GatewayConfigurations/Responses/POGatewayConfiguration.swift @@ -7,15 +7,15 @@ import Foundation -public struct POGatewayConfiguration: Decodable { +public struct POGatewayConfiguration: Decodable, Sendable { - public struct NativeAlternativePaymentMethodConfig: Decodable { + public struct NativeAlternativePaymentMethodConfig: Decodable, Sendable { /// Configuration parameters. public let parameters: [PONativeAlternativePaymentMethodParameter] } - public struct Gateway: Decodable { + public struct Gateway: Decodable, Sendable { /// Name is the name of the payment gateway. public let name: String diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POImageRemoteResource.swift b/Sources/ProcessOut/Sources/Repositories/Images/POImageRemoteResource.swift similarity index 79% rename from Sources/ProcessOut/Sources/Repositories/Shared/Responses/POImageRemoteResource.swift rename to Sources/ProcessOut/Sources/Repositories/Images/POImageRemoteResource.swift index a5d99c487..214ec4174 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POImageRemoteResource.swift +++ b/Sources/ProcessOut/Sources/Repositories/Images/POImageRemoteResource.swift @@ -8,9 +8,9 @@ import Foundation /// Image resource with light/dark image variations. -public struct POImageRemoteResource: Hashable, Decodable { +public struct POImageRemoteResource: Hashable, Decodable, Sendable { - public struct ResourceUrl: Hashable, Decodable { + public struct ResourceUrl: Hashable, Decodable, Sendable { /// Raster asset URLs. public let raster: URL diff --git a/Sources/ProcessOut/Sources/Repositories/Images/POImagesRepository.swift b/Sources/ProcessOut/Sources/Repositories/Images/POImagesRepository.swift index 036bfe10d..686ba6bdc 100644 --- a/Sources/ProcessOut/Sources/Repositories/Images/POImagesRepository.swift +++ b/Sources/ProcessOut/Sources/Repositories/Images/POImagesRepository.swift @@ -8,7 +8,8 @@ import Foundation import UIKit -@_spi(PO) public protocol POImagesRepository { // sourcery: AutoCompletion +@_spi(PO) +public protocol POImagesRepository: Sendable { // sourcery: AutoCompletion /// Attempts to download images at given URLs. func images(at urls: [URL], scale: CGFloat) async -> [URL: UIImage] diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/NativeAlternativePaymentCaptureRequest.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/NativeAlternativePaymentCaptureRequest.swift index 557752683..6f487da18 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/NativeAlternativePaymentCaptureRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/NativeAlternativePaymentCaptureRequest.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 16.12.2022. // -struct NativeAlternativePaymentCaptureRequest: Encodable { +struct NativeAlternativePaymentCaptureRequest: Encodable, Sendable { /// Invoice identifier. @POImmutableExcludedCodable diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodRequest.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodRequest.swift index 17b522a6f..1cddcca60 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodRequest.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 17.10.2022. // -public struct PONativeAlternativePaymentMethodRequest { +public struct PONativeAlternativePaymentMethodRequest: Sendable { /// Invoice id. public let invoiceId: String diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetailsRequest.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetailsRequest.swift index 8fdabeb3b..faf399fc7 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetailsRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetailsRequest.swift @@ -5,9 +5,7 @@ // Created by Andrii Vysotskyi on 01.12.2022. // -import Foundation - -public struct PONativeAlternativePaymentMethodTransactionDetailsRequest { // swiftlint:disable:this type_name +public struct PONativeAlternativePaymentMethodTransactionDetailsRequest: Sendable { // swiftlint:disable:this type_name /// Invoice identifier. public let invoiceId: String diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceAuthorizationRequest.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceAuthorizationRequest.swift index 07c2de63d..6f452ac4b 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceAuthorizationRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceAuthorizationRequest.swift @@ -7,7 +7,7 @@ import Foundation -public struct POInvoiceAuthorizationRequest: Encodable { // sourcery: AutoCodingKeys +public struct POInvoiceAuthorizationRequest: Encodable, Sendable { // sourcery: AutoCodingKeys /// Invoice identifier to to perform authorization for. public let invoiceId: String // sourcery:coding: skip diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceCreationRequest.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceCreationRequest.swift index b2b939775..857f4d3ea 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceCreationRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceCreationRequest.swift @@ -8,7 +8,7 @@ import Foundation @_spi(PO) -public struct POInvoiceCreationRequest: Encodable { +public struct POInvoiceCreationRequest: Encodable, Sendable { /// Name of the invoice (often an internal ID code from the merchant’s systems). Maximum 80 characters long. public let name: String diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceRequest.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceRequest.swift index 618892adb..d21cfb41e 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceRequest.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Requests/POInvoiceRequest.swift @@ -6,7 +6,7 @@ // /// Request to get single invoice details. -public struct POInvoiceRequest { +public struct POInvoiceRequest: Sendable { /// Requested invoice ID. public let invoiceId: String diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/PONativeAlternativePaymentMethodParameter.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameter.swift similarity index 91% rename from Sources/ProcessOut/Sources/Repositories/Shared/Responses/PONativeAlternativePaymentMethodParameter.swift rename to Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameter.swift index 5dc830e70..e96f25f61 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/PONativeAlternativePaymentMethodParameter.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameter.swift @@ -7,9 +7,9 @@ import Foundation -public struct PONativeAlternativePaymentMethodParameter: Decodable { +public struct PONativeAlternativePaymentMethodParameter: Decodable, Sendable { - public enum ParameterType: String, Decodable, Hashable { + public enum ParameterType: String, Decodable, Hashable, Sendable { /// For numeric only fields. case numeric @@ -28,7 +28,7 @@ public struct PONativeAlternativePaymentMethodParameter: Decodable { } /// Describes available value. - public struct AvailableValue: Decodable, Hashable { + public struct AvailableValue: Decodable, Hashable, Sendable { /// Display name of value. public let displayName: String diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameterValues.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameterValues.swift index 29eedc589..c7e81108e 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameterValues.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodParameterValues.swift @@ -8,7 +8,7 @@ import Foundation /// Native alternative payment parameter values. -public struct PONativeAlternativePaymentMethodParameterValues: Decodable { +public struct PONativeAlternativePaymentMethodParameterValues: Decodable, Sendable { /// Message. public let message: String? diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodResponse.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodResponse.swift index 665389c00..aa12839cc 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodResponse.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodResponse.swift @@ -7,12 +7,12 @@ import Foundation -public struct PONativeAlternativePaymentMethodResponse: Decodable { +public struct PONativeAlternativePaymentMethodResponse: Decodable, Sendable { @available(*, deprecated, message: "Use PONativeAlternativePaymentMethodParameterValues directly.") public typealias NativeAlternativePaymentMethodParameterValues = PONativeAlternativePaymentMethodParameterValues - public struct NativeApm: Decodable { + public struct NativeApm: Decodable, Sendable { /// Payment's state. public let state: PONativeAlternativePaymentMethodState diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodState.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodState.swift index 799df0b66..14971d743 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodState.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodState.swift @@ -7,7 +7,7 @@ import Foundation -public enum PONativeAlternativePaymentMethodState: String, Decodable { +public enum PONativeAlternativePaymentMethodState: String, Decodable, Sendable { /// Additional input is required. case customerInput = "CUSTOMER_INPUT" diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetails.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetails.swift index 8b3d5abd8..9c07a70ae 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetails.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/AlternativePayment/PONativeAlternativePaymentMethodTransactionDetails.swift @@ -7,10 +7,10 @@ import Foundation -public struct PONativeAlternativePaymentMethodTransactionDetails: Decodable { +public struct PONativeAlternativePaymentMethodTransactionDetails: Decodable, Sendable { /// Payment gateway information. - public struct Gateway { + public struct Gateway: Sendable { /// Name of the payment gateway that can be displayed. public let displayName: String @@ -27,7 +27,7 @@ public struct PONativeAlternativePaymentMethodTransactionDetails: Decodable { } /// Invoice details. - public struct Invoice: Decodable { + public struct Invoice: Decodable, Sendable { /// Invoice amount. @POImmutableStringCodableDecimal diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POBillingAddressCollectionMode.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/POBillingAddressCollectionMode.swift similarity index 83% rename from Sources/ProcessOut/Sources/Repositories/Shared/Responses/POBillingAddressCollectionMode.swift rename to Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/POBillingAddressCollectionMode.swift index f4c8106c1..564807aae 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POBillingAddressCollectionMode.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/POBillingAddressCollectionMode.swift @@ -6,7 +6,7 @@ // /// Billing address collection modes. -public enum POBillingAddressCollectionMode: String, Decodable { +public enum POBillingAddressCollectionMode: String, Decodable, Sendable { /// Only collect address components that are needed for particular payment method. case automatic diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/PODynamicCheckoutPaymentMethod.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/PODynamicCheckoutPaymentMethod.swift similarity index 89% rename from Sources/ProcessOut/Sources/Repositories/Invoices/Responses/PODynamicCheckoutPaymentMethod.swift rename to Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/PODynamicCheckoutPaymentMethod.swift index e9f20fb43..2702aaabb 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/PODynamicCheckoutPaymentMethod.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/PODynamicCheckoutPaymentMethod.swift @@ -11,11 +11,11 @@ import PassKit /// Dynamic checkout payment method description. @_spi(PO) -public enum PODynamicCheckoutPaymentMethod { +public enum PODynamicCheckoutPaymentMethod: Sendable { // MARK: - Apple Pay - public struct ApplePay: Decodable { // sourcery: AutoCodingKeys + public struct ApplePay: Decodable, Sendable { // sourcery: AutoCodingKeys /// Payment method ID. @_spi(PO) @@ -30,7 +30,7 @@ public enum PODynamicCheckoutPaymentMethod { public let configuration: ApplePayConfiguration // sourcery:coding: key="applepay" } - public struct ApplePayConfiguration: Decodable { + public struct ApplePayConfiguration: Decodable, Sendable { /// Merchant ID. public let merchantId: String @@ -48,7 +48,7 @@ public enum PODynamicCheckoutPaymentMethod { // MARK: - Native APM - public struct NativeAlternativePayment: Decodable { // sourcery: AutoCodingKeys + public struct NativeAlternativePayment: Decodable, Sendable { // sourcery: AutoCodingKeys /// Payment method ID. @_spi(PO) @@ -63,7 +63,7 @@ public enum PODynamicCheckoutPaymentMethod { public let configuration: NativeAlternativePaymentConfiguration // sourcery:coding: key="apm" } - public struct NativeAlternativePaymentConfiguration: Decodable { + public struct NativeAlternativePaymentConfiguration: Decodable, Sendable { /// Gateway configuration ID. public let gatewayConfigurationId: String @@ -71,7 +71,7 @@ public enum PODynamicCheckoutPaymentMethod { // MARK: - APM - public struct AlternativePayment: Decodable { // sourcery: AutoCodingKeys + public struct AlternativePayment: Decodable, Sendable { // sourcery: AutoCodingKeys /// Payment method ID. @_spi(PO) @@ -89,7 +89,7 @@ public enum PODynamicCheckoutPaymentMethod { public let configuration: AlternativePaymentConfiguration // sourcery:coding: key="apm" } - public struct AlternativePaymentConfiguration: Decodable { + public struct AlternativePaymentConfiguration: Decodable, Sendable { /// Gateway configuration ID. public let gatewayConfigurationId: String @@ -100,7 +100,7 @@ public enum PODynamicCheckoutPaymentMethod { // MARK: - Card - public struct Card: Decodable { // sourcery: AutoCodingKeys + public struct Card: Decodable, Sendable { // sourcery: AutoCodingKeys /// Payment method ID. @_spi(PO) @@ -113,7 +113,7 @@ public enum PODynamicCheckoutPaymentMethod { public let configuration: CardConfiguration // sourcery:coding: key="card" } - public struct CardConfiguration: Decodable { + public struct CardConfiguration: Decodable, Sendable { /// Defines whether user will be asked to select scheme if co-scheme is available. let allowSchemeSelection: Bool @@ -128,7 +128,7 @@ public enum PODynamicCheckoutPaymentMethod { public let billingAddress: BillingAddressConfiguration } - public struct BillingAddressConfiguration: Decodable { + public struct BillingAddressConfiguration: Decodable, Sendable { /// List of ISO country codes that is supported for the billing address. When nil, all countries are supported. public let restrictToCountryCodes: Set? @@ -139,7 +139,7 @@ public enum PODynamicCheckoutPaymentMethod { // MARK: - Customer Tokens - public struct CustomerToken { + public struct CustomerToken: Sendable { /// Payment method ID. @_spi(PO) @@ -157,7 +157,7 @@ public enum PODynamicCheckoutPaymentMethod { public let configuration: CustomerTokenConfiguration } - public struct CustomerTokenConfiguration: Decodable { + public struct CustomerTokenConfiguration: Decodable, Sendable { /// Customer token ID. public let customerTokenId: String @@ -168,7 +168,7 @@ public enum PODynamicCheckoutPaymentMethod { // MARK: - Unknown - public struct Unknown { + public struct Unknown: Sendable { /// Transient ID assigned to method during decoding. @_spi(PO) @@ -180,7 +180,7 @@ public enum PODynamicCheckoutPaymentMethod { // MARK: - Common - public struct Display: Decodable { + public struct Display: Decodable, @unchecked Sendable { /// Display name. public let name: String @@ -192,7 +192,7 @@ public enum PODynamicCheckoutPaymentMethod { public private(set) var brandColor: UIColor } - public enum Flow: String, Decodable { + public enum Flow: String, Decodable, Sendable { case express } diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POStringDecodableMerchantCapability.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/POStringDecodableMerchantCapability.swift similarity index 95% rename from Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POStringDecodableMerchantCapability.swift rename to Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/POStringDecodableMerchantCapability.swift index c7c23a735..b7d1a9cfd 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POStringDecodableMerchantCapability.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/DynamicCheckout/POStringDecodableMerchantCapability.swift @@ -9,7 +9,7 @@ import PassKit /// Property wrapper allowing to decode `PKMerchantCapability`. @propertyWrapper -public struct POStringDecodableMerchantCapability: Decodable { +public struct POStringDecodableMerchantCapability: Decodable, Sendable { public let wrappedValue: PKMerchantCapability diff --git a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POInvoice.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POInvoice.swift index a6624c59b..bd7af52d1 100644 --- a/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POInvoice.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/POInvoice.swift @@ -8,7 +8,7 @@ import Foundation /// Invoice details. -public struct POInvoice: Decodable { +public struct POInvoice: Decodable, Sendable { /// String value that uniquely identifies this invoice. public let id: String diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/ThreeDSCustomerAction.swift b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/ThreeDSCustomerAction.swift similarity index 85% rename from Sources/ProcessOut/Sources/Repositories/Shared/Responses/ThreeDSCustomerAction.swift rename to Sources/ProcessOut/Sources/Repositories/Invoices/Responses/ThreeDSCustomerAction.swift index 9bc0f87eb..adfff3869 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/ThreeDSCustomerAction.swift +++ b/Sources/ProcessOut/Sources/Repositories/Invoices/Responses/ThreeDSCustomerAction.swift @@ -7,9 +7,9 @@ import Foundation -struct ThreeDSCustomerAction: Decodable { +struct ThreeDSCustomerAction: Decodable, Sendable { - enum ActionType: String, Decodable { + enum ActionType: String, Decodable, Sendable { /// Device fingerprint is required. case fingerprintMobile = "fingerprint-mobile" diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Decorators/HttpConnectorError/FailureMapper/HttpConnectorFailureMapper.swift b/Sources/ProcessOut/Sources/Repositories/Shared/Decorators/HttpConnectorError/FailureMapper/HttpConnectorFailureMapper.swift index d3d75a2f7..091692af8 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Decorators/HttpConnectorError/FailureMapper/HttpConnectorFailureMapper.swift +++ b/Sources/ProcessOut/Sources/Repositories/Shared/Decorators/HttpConnectorError/FailureMapper/HttpConnectorFailureMapper.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 16.10.2022. // -protocol HttpConnectorFailureMapper { +protocol HttpConnectorFailureMapper: Sendable { /// Creates `POFailure` with given ``HttpConnectorFailure`` instance. func failure(from failure: HttpConnectorFailure) -> POFailure diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/PORepository.swift b/Sources/ProcessOut/Sources/Repositories/Shared/PORepository.swift index 892a71a00..a1b8efa46 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/PORepository.swift +++ b/Sources/ProcessOut/Sources/Repositories/Shared/PORepository.swift @@ -9,7 +9,7 @@ public typealias PORepositoryType = PORepository /// Common protocol that all repositories conform to. -public protocol PORepository { +public protocol PORepository: Sendable { /// Repository's failure type. typealias Failure = POFailure diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Requests/PaginationOptions/POPaginationOptions.swift b/Sources/ProcessOut/Sources/Repositories/Shared/Requests/PaginationOptions/POPaginationOptions.swift index b02e1360e..7ab96e4b1 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Requests/PaginationOptions/POPaginationOptions.swift +++ b/Sources/ProcessOut/Sources/Repositories/Shared/Requests/PaginationOptions/POPaginationOptions.swift @@ -7,13 +7,13 @@ import Foundation -public struct POPaginationOptions { +public struct POPaginationOptions: Sendable { - public enum Order: String { + public enum Order: String, Sendable { case ascending = "asc", descending = "desc" } - public enum Position { + public enum Position: Sendable { case after(String), before(String) } diff --git a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POFailure.swift b/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POFailure.swift index 73efe7145..4d3236aa6 100644 --- a/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POFailure.swift +++ b/Sources/ProcessOut/Sources/Repositories/Shared/Responses/POFailure.swift @@ -10,9 +10,9 @@ import Foundation /// Information about an error that occurred. -public struct POFailure: Error { +public struct POFailure: Error, Sendable { - public struct InvalidField: Decodable { + public struct InvalidField: Decodable, Sendable { /// Field name. public let name: String @@ -26,17 +26,17 @@ public struct POFailure: Error { } } - public enum InternalCode: String { + public enum InternalCode: String, Sendable { case gateway = "gateway-internal-error" case mobile = "processout-mobile.internal" } - public enum TimeoutCode: String { + public enum TimeoutCode: String, Sendable { case gateway = "gateway.timeout" case mobile = "processout-mobile.timeout" } - public enum ValidationCode: String { + public enum ValidationCode: String, Sendable { case general = "request.validation.error" case gateway = "gateway.validation-error" case invalidAddress = "request.validation.invalid-address" @@ -88,7 +88,7 @@ public struct POFailure: Error { case missingType = "request.validation.missing-type" } - public enum NotFoundCode: String { + public enum NotFoundCode: String, Sendable { case activity = "resource.activity.not-found" case addon = "resource.addon.not-found" case alert = "resource.alert.not-found" @@ -127,12 +127,12 @@ public struct POFailure: Error { case webhookEndpoint = "resource.webhook-endpoint.not-found" } - public enum AuthenticationCode: String { + public enum AuthenticationCode: String, Sendable { case invalid = "request.authentication.invalid" case invalidProjectId = "request.authentication.invalid-project-id" } - public enum GenericCode: String { + public enum GenericCode: String, Sendable { /// The card limits were reached (ex: amounts, transactions volume) and the customer should contact its bank. case cardExceededLimits = "card.exceeded-limits" @@ -362,7 +362,7 @@ public struct POFailure: Error { case serviceNotSupported = "service.not-supported" } - public enum Code: Hashable { + public enum Code: Hashable, Sendable { /// No network connection. case networkUnreachable @@ -392,7 +392,7 @@ public struct POFailure: Error { case unknown(rawValue: String) } - /// Failure message. Not intented to be used as a user facing string. + /// Failure message. Not intended to be used as a user facing string. public let message: String? /// Failure code. diff --git a/Sources/ProcessOut/Sources/Repositories/Telemetry/Telemetry.swift b/Sources/ProcessOut/Sources/Repositories/Telemetry/Telemetry.swift index 977c547c7..04acabfb2 100644 --- a/Sources/ProcessOut/Sources/Repositories/Telemetry/Telemetry.swift +++ b/Sources/ProcessOut/Sources/Repositories/Telemetry/Telemetry.swift @@ -7,9 +7,9 @@ import Foundation -struct Telemetry: Encodable { +struct Telemetry: Encodable, Sendable { - struct ApplicationMetadata: Encodable { + struct ApplicationMetadata: Encodable, Sendable { /// Host application name. let name: String? @@ -18,7 +18,7 @@ struct Telemetry: Encodable { let version: String? } - struct DeviceMetadata: Encodable { + struct DeviceMetadata: Encodable, Sendable { /// Device system language. let language: String @@ -30,7 +30,7 @@ struct Telemetry: Encodable { let timeZone: Int } - struct Metadata: Encodable { + struct Metadata: Encodable, Sendable { /// App metadata. let application: ApplicationMetadata @@ -39,7 +39,7 @@ struct Telemetry: Encodable { let device: DeviceMetadata } - struct Event: Encodable { + struct Event: Encodable, Sendable { /// Event timestamp. let timestamp: Date diff --git a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2AuthenticationRequest.swift b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2AuthenticationRequest.swift index 6095d33ca..f42d1fa36 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2AuthenticationRequest.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2AuthenticationRequest.swift @@ -6,7 +6,7 @@ // /// Holds transaction data that the 3DS Server requires to create the AReq. -public struct PO3DS2AuthenticationRequest: Hashable { +public struct PO3DS2AuthenticationRequest: Hashable, Sendable { /// Encrypted device data as a JWE string. public let deviceData: String diff --git a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Challenge.swift b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Challenge.swift index b44d18597..2764a221e 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Challenge.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Challenge.swift @@ -7,7 +7,7 @@ /// Information from the 3DS Server's authentication response that could be used by the 3DS2 SDK to initiate /// the challenge flow. -public struct PO3DS2Challenge: Decodable, Hashable { +public struct PO3DS2Challenge: Decodable, Hashable, Sendable { /// Unique transaction identifier assigned by the ACS. public let acsTransactionId: String diff --git a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Configuration.swift b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Configuration.swift index dfc08200f..0c6d7384b 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Configuration.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2Configuration.swift @@ -6,7 +6,7 @@ // /// Represents the configuration parameters that are required by the 3DS SDK for initialization. -public struct PO3DS2Configuration: Decodable, Hashable { +public struct PO3DS2Configuration: Decodable, Hashable, Sendable { /// The identifier of the directory server to use during the transaction creation phase. public let directoryServerId: String diff --git a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2ConfigurationCardScheme.swift b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2ConfigurationCardScheme.swift index 48dec718e..a2792de03 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2ConfigurationCardScheme.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DS2ConfigurationCardScheme.swift @@ -8,7 +8,7 @@ // todo(andrii-vysotskyi): remove when updating to 5.0.0 /// Available card schemes. -public enum PO3DS2ConfigurationCardScheme: RawRepresentable, Decodable, Hashable { +public enum PO3DS2ConfigurationCardScheme: RawRepresentable, Decodable, Hashable, Sendable { /// Known card schemes. case visa, mastercard, europay, carteBancaire, jcb, diners, discover, unionpay, americanExpress diff --git a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DSRedirect.swift b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DSRedirect.swift index 7b50949ee..8dc9ef9bf 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DSRedirect.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/Models/PO3DSRedirect.swift @@ -8,7 +8,7 @@ import Foundation /// Holds information about 3DS redirect. -public struct PO3DSRedirect: Hashable { +public struct PO3DSRedirect: Hashable, Sendable { /// Redirect url. public let url: URL diff --git a/Sources/ProcessOut/Sources/Services/3DS/PO3DSService.swift b/Sources/ProcessOut/Sources/Services/3DS/PO3DSService.swift index a18a6745f..527ad59f6 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/PO3DSService.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/PO3DSService.swift @@ -9,23 +9,23 @@ public typealias PO3DSServiceType = PO3DSService /// This interface provides methods to process 3-D Secure transactions. -public protocol PO3DSService: AnyObject { +public protocol PO3DSService: AnyObject, Sendable { /// Asks implementation to create request that will be passed to 3DS Server to create the AReq. func authenticationRequest( configuration: PO3DS2Configuration, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) /// Implementation must handle given 3DS2 challenge and call completion with result. Use `true` if challenge /// was handled successfully, if transaction was denied, pass `false`. In all other cases, call completion /// with failure indicating what went wrong. - func handle(challenge: PO3DS2Challenge, completion: @escaping (Result) -> Void) + func handle(challenge: PO3DS2Challenge, completion: @escaping @Sendable (Result) -> Void) /// Asks implementation to handle redirect. If value of ``PO3DSRedirect/timeout`` is present it must be /// respected, meaning if timeout is reached `completion` should be called with instance of ``POFailure`` with /// ``POFailure/code-swift.property`` set to ``POFailure/TimeoutCode/mobile``. - func handle(redirect: PO3DSRedirect, completion: @escaping (Result) -> Void) + func handle(redirect: PO3DSRedirect, completion: @escaping @Sendable (Result) -> Void) } @MainActor @@ -34,7 +34,9 @@ extension PO3DSService { /// Asks implementation to create request that will be passed to 3DS Server to create the AReq. func authenticationRequest(configuration: PO3DS2Configuration) async throws -> PO3DS2AuthenticationRequest { try await withUnsafeThrowingContinuation { continuation in - authenticationRequest(configuration: configuration, completion: continuation.resume) + authenticationRequest(configuration: configuration) { result in + continuation.resume(with: result) + } } } @@ -43,7 +45,9 @@ extension PO3DSService { /// with failure indicating what went wrong. func handle(challenge: PO3DS2Challenge) async throws -> Bool { try await withUnsafeThrowingContinuation { continuation in - handle(challenge: challenge, completion: continuation.resume) + handle(challenge: challenge) { result in + continuation.resume(with: result) + } } } @@ -52,7 +56,9 @@ extension PO3DSService { /// ``POFailure/code-swift.property`` set to ``POFailure/TimeoutCode/mobile``. func handle(redirect: PO3DSRedirect) async throws -> String { try await withUnsafeThrowingContinuation { continuation in - handle(redirect: redirect, completion: continuation.resume) + handle(redirect: redirect) { result in + continuation.resume(with: result) + } } } } diff --git a/Sources/ProcessOut/Sources/Services/3DS/ThreeDSService.swift b/Sources/ProcessOut/Sources/Services/3DS/ThreeDSService.swift index bc629153d..017ed94af 100644 --- a/Sources/ProcessOut/Sources/Services/3DS/ThreeDSService.swift +++ b/Sources/ProcessOut/Sources/Services/3DS/ThreeDSService.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 02.11.2022. // -protocol ThreeDSService { +protocol ThreeDSService: Sendable { typealias Delegate = PO3DSService diff --git a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/DefaultAlternativePaymentMethodsService.swift b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/DefaultAlternativePaymentMethodsService.swift index 3db28b5c4..c0f4dd64d 100644 --- a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/DefaultAlternativePaymentMethodsService.swift +++ b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/DefaultAlternativePaymentMethodsService.swift @@ -9,7 +9,7 @@ import Foundation final class DefaultAlternativePaymentMethodsService: POAlternativePaymentMethodsService { - init(configuration: @escaping () -> AlternativePaymentMethodsServiceConfiguration, logger: POLogger) { + init(configuration: @escaping @Sendable () -> AlternativePaymentMethodsServiceConfiguration, logger: POLogger) { self.configuration = configuration self.logger = logger } @@ -63,7 +63,7 @@ final class DefaultAlternativePaymentMethodsService: POAlternativePaymentMethods // MARK: - Private - private let configuration: () -> AlternativePaymentMethodsServiceConfiguration + private let configuration: @Sendable () -> AlternativePaymentMethodsServiceConfiguration private let logger: POLogger // MARK: - Private Methods diff --git a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/POAlternativePaymentMethodsService.swift b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/POAlternativePaymentMethodsService.swift index ddf6babfe..fc95f9c03 100644 --- a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/POAlternativePaymentMethodsService.swift +++ b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/POAlternativePaymentMethodsService.swift @@ -11,7 +11,7 @@ import Foundation public typealias POAlternativePaymentMethodsServiceType = POAlternativePaymentMethodsService /// Service that provides set of methods to work with alternative payments. -public protocol POAlternativePaymentMethodsService { +public protocol POAlternativePaymentMethodsService: POService { /// Creates the redirection URL for APM Payments and APM token creation. /// diff --git a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Requests/POAlternativePaymentMethodRequest.swift b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Requests/POAlternativePaymentMethodRequest.swift index 37603bd15..20087ddfd 100644 --- a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Requests/POAlternativePaymentMethodRequest.swift +++ b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Requests/POAlternativePaymentMethodRequest.swift @@ -14,7 +14,7 @@ import Foundation /// /// - NOTE: Make sure to supply proper `additionalData` specific for particular payment /// method. -public struct POAlternativePaymentMethodRequest { +public struct POAlternativePaymentMethodRequest: Sendable { /// Invoice identifier to to perform APM payment for. public let invoiceId: String diff --git a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Responses/POAlternativePaymentMethodResponse.swift b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Responses/POAlternativePaymentMethodResponse.swift index 3c63690a7..b849ad69e 100644 --- a/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Responses/POAlternativePaymentMethodResponse.swift +++ b/Sources/ProcessOut/Sources/Services/AlternativePaymentMethods/Responses/POAlternativePaymentMethodResponse.swift @@ -8,9 +8,9 @@ import Foundation /// Result of alternative payment. -public struct POAlternativePaymentMethodResponse { +public struct POAlternativePaymentMethodResponse: Sendable { - public enum APMReturnType { + public enum APMReturnType: Sendable { case authorization, createToken } diff --git a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift index 1abbec15e..9e791a664 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/DefaultCardsService.swift @@ -32,7 +32,7 @@ final class DefaultCardsService: POCardsService { } func tokenize(request: POApplePayCardTokenizationRequest) async throws -> POCard { - let request = try applePayCardTokenizationRequestMapper.tokenizationRequest(from: request) + let request = try await applePayCardTokenizationRequestMapper.tokenizationRequest(from: request) return try await repository.tokenize(request: request) } diff --git a/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/ApplePayCardTokenizationRequestMapper.swift b/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/ApplePayCardTokenizationRequestMapper.swift index 6313fe4cb..38fb2eff1 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/ApplePayCardTokenizationRequestMapper.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/ApplePayCardTokenizationRequestMapper.swift @@ -5,8 +5,10 @@ // Created by Julien.Rodrigues on 25/10/2022. // -protocol ApplePayCardTokenizationRequestMapper { +protocol ApplePayCardTokenizationRequestMapper: Sendable { /// Creates tokenization request with given ``POApplePayCardTokenizationRequest`` instance. - func tokenizationRequest(from request: POApplePayCardTokenizationRequest) throws -> ApplePayCardTokenizationRequest + func tokenizationRequest( + from request: POApplePayCardTokenizationRequest + ) async throws -> ApplePayCardTokenizationRequest } diff --git a/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/DefaultApplePayCardTokenizationRequestMapper.swift b/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/DefaultApplePayCardTokenizationRequestMapper.swift index 130ffbbbc..414557bca 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/DefaultApplePayCardTokenizationRequestMapper.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/Mappers/ApplePayCardTokenizationRequest/DefaultApplePayCardTokenizationRequestMapper.swift @@ -19,6 +19,7 @@ final class DefaultApplePayCardTokenizationRequestMapper: ApplePayCardTokenizati // MARK: - ApplePayCardTokenizationRequestMapper /// - Throws: `POFailure` instance in case of error. + @MainActor func tokenizationRequest( from request: POApplePayCardTokenizationRequest ) throws -> ApplePayCardTokenizationRequest { diff --git a/Sources/ProcessOut/Sources/Services/Cards/Mappers/PassKitContact/PassKitContactMapper.swift b/Sources/ProcessOut/Sources/Services/Cards/Mappers/PassKitContact/PassKitContactMapper.swift index 28acf662b..2f6dca09c 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/Mappers/PassKitContact/PassKitContactMapper.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/Mappers/PassKitContact/PassKitContactMapper.swift @@ -7,7 +7,7 @@ import PassKit -protocol PassKitContactMapper { +protocol PassKitContactMapper: Sendable { /// Converts given `PKContact` instance to `POContact`. func map(contact: PKContact) -> POContact diff --git a/Sources/ProcessOut/Sources/Services/Cards/Requests/POApplePayCardTokenizationRequest.swift b/Sources/ProcessOut/Sources/Services/Cards/Requests/POApplePayCardTokenizationRequest.swift index fb3a0a44e..78b9538f4 100644 --- a/Sources/ProcessOut/Sources/Services/Cards/Requests/POApplePayCardTokenizationRequest.swift +++ b/Sources/ProcessOut/Sources/Services/Cards/Requests/POApplePayCardTokenizationRequest.swift @@ -9,6 +9,7 @@ import Foundation import PassKit /// Apple pay card details. +@MainActor public struct POApplePayCardTokenizationRequest { /// Payment information. diff --git a/Sources/ProcessOut/Sources/Services/Invoices/Requests/PONativeAlternativePaymentCaptureRequest.swift b/Sources/ProcessOut/Sources/Services/Invoices/Requests/PONativeAlternativePaymentCaptureRequest.swift index db03b5ce8..35e7c2e61 100644 --- a/Sources/ProcessOut/Sources/Services/Invoices/Requests/PONativeAlternativePaymentCaptureRequest.swift +++ b/Sources/ProcessOut/Sources/Services/Invoices/Requests/PONativeAlternativePaymentCaptureRequest.swift @@ -7,7 +7,7 @@ import Foundation -public struct PONativeAlternativePaymentCaptureRequest { +public struct PONativeAlternativePaymentCaptureRequest: Sendable { /// Invoice identifier. public let invoiceId: String diff --git a/Sources/ProcessOut/Sources/Services/Shared/POService.swift b/Sources/ProcessOut/Sources/Services/Shared/POService.swift index a372a6d8d..ffdd68cc2 100644 --- a/Sources/ProcessOut/Sources/Services/Shared/POService.swift +++ b/Sources/ProcessOut/Sources/Services/Shared/POService.swift @@ -6,7 +6,7 @@ // /// Common protocol that all services conform to. -public protocol POService { +public protocol POService: Sendable { /// Service's failure type. typealias Failure = POFailure diff --git a/Sources/ProcessOut/Sources/Services/Telemetry/DefaultTelemetryService.swift b/Sources/ProcessOut/Sources/Services/Telemetry/DefaultTelemetryService.swift index f58aa9517..5853df7d6 100644 --- a/Sources/ProcessOut/Sources/Services/Telemetry/DefaultTelemetryService.swift +++ b/Sources/ProcessOut/Sources/Services/Telemetry/DefaultTelemetryService.swift @@ -10,7 +10,7 @@ import Foundation final class DefaultTelemetryService: POService, LoggerDestination { init( - configuration: @escaping () -> TelemetryServiceConfiguration, + configuration: @escaping @Sendable () -> TelemetryServiceConfiguration, repository: TelemetryRepository, deviceMetadataProvider: DeviceMetadataProvider ) { @@ -58,9 +58,10 @@ final class DefaultTelemetryService: POService, LoggerDestination { private let repository: TelemetryRepository private let deviceMetadataProvider: DeviceMetadataProvider - private let configuration: () -> TelemetryServiceConfiguration + private let configuration: @Sendable () -> TelemetryServiceConfiguration - private var batcher: Batcher! // swiftlint:disable:this implicitly_unwrapped_optional + // swiftlint:disable:next implicitly_unwrapped_optional + private nonisolated(unsafe) var batcher: Batcher! // MARK: - Private Methods diff --git a/Sources/ProcessOut/Sources/Services/Telemetry/TelemetryServiceConfiguration.swift b/Sources/ProcessOut/Sources/Services/Telemetry/TelemetryServiceConfiguration.swift index c7692b4eb..7bad576a0 100644 --- a/Sources/ProcessOut/Sources/Services/Telemetry/TelemetryServiceConfiguration.swift +++ b/Sources/ProcessOut/Sources/Services/Telemetry/TelemetryServiceConfiguration.swift @@ -5,7 +5,7 @@ // Created by Andrii Vysotskyi on 09.04.2024. // -struct TelemetryServiceConfiguration { +struct TelemetryServiceConfiguration: Sendable { /// Indicates whether telemetry is enabled. let isTelemetryEnabled: Bool diff --git a/Sources/ProcessOut/Sources/UI/Modules/3DSRedirect/PO3DSRedirectViewControllerBuilder.swift b/Sources/ProcessOut/Sources/UI/Modules/3DSRedirect/PO3DSRedirectViewControllerBuilder.swift index d356642d8..70c8724a5 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/3DSRedirect/PO3DSRedirectViewControllerBuilder.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/3DSRedirect/PO3DSRedirectViewControllerBuilder.swift @@ -10,6 +10,7 @@ import SafariServices /// Builder that can be used to create view controller that is capable of handling 3DS web redirects. @available(*, deprecated, message: "Use ProcessOutUI.SFSafariViewController(redirect:returnUrl:safariConfiguration:completion:) instead") // swiftlint:disable:this line_length +@MainActor public final class PO3DSRedirectViewControllerBuilder { /// Creates builder instance with given redirect information. diff --git a/Sources/ProcessOut/Sources/UI/Modules/AlternativePaymentMethod/POAlternativePaymentMethodViewControllerBuilder.swift b/Sources/ProcessOut/Sources/UI/Modules/AlternativePaymentMethod/POAlternativePaymentMethodViewControllerBuilder.swift index 40e568dda..6da19432e 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/AlternativePaymentMethod/POAlternativePaymentMethodViewControllerBuilder.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/AlternativePaymentMethod/POAlternativePaymentMethodViewControllerBuilder.swift @@ -11,6 +11,7 @@ import SafariServices /// Provides an ability to create view controller that could be used to handle Alternative Payment. Call build() to /// create view controller’s instance. @available(*, deprecated, message: "Use ProcessOutUI.SFSafariViewController(request:returnUrl:safariConfiguration:completion:) instead") // swiftlint:disable:this line_length +@MainActor public final class POAlternativePaymentMethodViewControllerBuilder { /// Creates builder instance with given request. diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Builder/PONativeAlternativePaymentMethodViewControllerBuilder.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Builder/PONativeAlternativePaymentMethodViewControllerBuilder.swift index f637b7c84..c5782bb65 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Builder/PONativeAlternativePaymentMethodViewControllerBuilder.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Builder/PONativeAlternativePaymentMethodViewControllerBuilder.swift @@ -11,6 +11,7 @@ import UIKit /// Alternative Payment. Call ``PONativeAlternativePaymentMethodViewControllerBuilder/build()`` /// to create view controller's instance. @available(*, deprecated, message: "Use ProcessOutUI.PONativeAlternativePaymentViewController instead.") +@MainActor public final class PONativeAlternativePaymentMethodViewControllerBuilder { // swiftlint:disable:this type_name @available(*, deprecated, message: "Use non static method instead.") @@ -73,13 +74,12 @@ public final class PONativeAlternativePaymentMethodViewControllerBuilder { // sw guard let gatewayConfigurationId, let invoiceId else { preconditionFailure("Gateway configuration id and invoice id must be set.") } - let api: ProcessOut = ProcessOut.shared // swiftlint:disable:this redundant_type_annotation - var logger = api.logger + var logger: POLogger = ProcessOut.shared.logger logger[attributeKey: .invoiceId] = invoiceId logger[attributeKey: .gatewayConfigurationId] = gatewayConfigurationId let interactor = PODefaultNativeAlternativePaymentMethodInteractor( - invoicesService: api.invoices, - imagesRepository: api.images, + invoicesService: ProcessOut.shared.invoices, + imagesRepository: ProcessOut.shared.images, configuration: .init( gatewayConfigurationId: gatewayConfigurationId, invoiceId: invoiceId, diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PODefaultNativeAlternativePaymentMethodInteractor.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PODefaultNativeAlternativePaymentMethodInteractor.swift index 670dbbdb1..c68f93f27 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PODefaultNativeAlternativePaymentMethodInteractor.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PODefaultNativeAlternativePaymentMethodInteractor.swift @@ -53,13 +53,17 @@ import UIKit invoiceId: configuration.invoiceId, gatewayConfigurationId: configuration.gatewayConfigurationId ) invoicesService.nativeAlternativePaymentMethodTransactionDetails(request: request) { [weak self] result in - switch result { - case let .success(details): - self?.defaultValues(for: details.parameters) { values in - self?.setStartedStateUnchecked(details: details, defaultValues: values) + MainActor.assumeIsolated { + switch result { + case let .success(details): + self?.defaultValues(for: details.parameters) { values in + MainActor.assumeIsolated { + self?.setStartedStateUnchecked(details: details, defaultValues: values) + } + } + case .failure(let failure): + self?.setFailureStateUnchecked(failure: failure) } - case .failure(let failure): - self?.setFailureStateUnchecked(failure: failure) } } } @@ -113,11 +117,13 @@ import UIKit ) state = .submitting(snapshot: startedState) invoicesService.initiatePayment(request: request) { [weak self] result in - switch result { - case let .success(response): - self?.completeSubmissionUnchecked(with: response, startedState: startedState) - case let .failure(failure): - self?.restoreStartedStateAfterSubmissionFailureIfPossible(failure, replaceErrorMessages: true) + MainActor.assumeIsolated { + switch result { + case let .success(response): + self?.completeSubmissionUnchecked(with: response, startedState: startedState) + case let .failure(failure): + self?.restoreStartedStateAfterSubmissionFailureIfPossible(failure, replaceErrorMessages: true) + } } } } catch let error as POFailure { @@ -204,7 +210,9 @@ import UIKit switch response.nativeApm.state { case .customerInput: defaultValues(for: response.nativeApm.parameterDefinitions) { [weak self] values in - self?.restoreStartedStateAfterSubmission(nativeApm: response.nativeApm, defaultValues: values) + MainActor.assumeIsolated { + self?.restoreStartedStateAfterSubmission(nativeApm: response.nativeApm, defaultValues: values) + } } case .pendingCapture: send(event: .didSubmitParameters(additionalParametersExpected: false)) @@ -236,35 +244,39 @@ import UIKit let logoUrl = logoUrl(gateway: gateway, parameterValues: parameterValues) send(event: .willWaitForCaptureConfirmation(additionalActionExpected: actionMessage != nil)) imagesRepository.images(at: logoUrl, gateway.customerActionImageUrl) { [weak self] logo, actionImage in - guard let self else { - return - } - let request = PONativeAlternativePaymentCaptureRequest( - invoiceId: self.configuration.invoiceId, - gatewayConfigurationId: self.configuration.gatewayConfigurationId, - timeout: self.configuration.paymentConfirmationTimeout - ) - self.captureCancellable = self.invoicesService.captureNativeAlternativePayment( - request: request, - completion: { [weak self] result in - switch result { - case .success: - self?.setCapturedStateUnchecked(gateway: gateway, parameterValues: parameterValues) - case .failure(let failure): - self?.setFailureStateUnchecked(failure: failure) - } + MainActor.assumeIsolated { + guard let self else { + return } - ) - let awaitingCaptureState = State.AwaitingCapture( - paymentProviderName: parameterValues?.providerName, - logoImage: logo, - actionMessage: actionMessage, - actionImage: actionImage, - isDelayed: false - ) - self.state = .awaitingCapture(awaitingCaptureState) - self.logger.debug("Waiting for invoice capture confirmation") - self.schedulePaymentConfirmationDelay() + let request = PONativeAlternativePaymentCaptureRequest( + invoiceId: self.configuration.invoiceId, + gatewayConfigurationId: self.configuration.gatewayConfigurationId, + timeout: self.configuration.paymentConfirmationTimeout + ) + self.captureCancellable = self.invoicesService.captureNativeAlternativePayment( + request: request, + completion: { [weak self] result in + MainActor.assumeIsolated { + switch result { + case .success: + self?.setCapturedStateUnchecked(gateway: gateway, parameterValues: parameterValues) + case .failure(let failure): + self?.setFailureStateUnchecked(failure: failure) + } + } + } + ) + let awaitingCaptureState = State.AwaitingCapture( + paymentProviderName: parameterValues?.providerName, + logoImage: logo, + actionMessage: actionMessage, + actionImage: actionImage, + isDelayed: false + ) + self.state = .awaitingCapture(awaitingCaptureState) + self.logger.debug("Waiting for invoice capture confirmation") + self.schedulePaymentConfirmationDelay() + } } } @@ -303,11 +315,13 @@ import UIKit default: let logoUrl = logoUrl(gateway: gateway, parameterValues: parameterValues) imagesRepository.image(at: logoUrl) { [weak self] logoImage in - let capturedState = State.Captured( - paymentProviderName: parameterValues?.providerName, logoImage: logoImage - ) - self?.state = .captured(capturedState) - self?.send(event: .didCompletePayment) + MainActor.assumeIsolated { + let capturedState = State.Captured( + paymentProviderName: parameterValues?.providerName, logoImage: logoImage + ) + self?.state = .captured(capturedState) + self?.send(event: .didCompletePayment) + } } } } @@ -402,7 +416,7 @@ import UIKit private func defaultValues( for parameters: [PONativeAlternativePaymentMethodParameter]?, - completion: @escaping ([String: State.ParameterValue]) -> Void + completion: @escaping @Sendable ([String: State.ParameterValue]) -> Void ) { guard let parameters, !parameters.isEmpty else { completion([:]) @@ -410,27 +424,28 @@ import UIKit } if let delegate { delegate.nativeAlternativePaymentMethodDefaultValues(for: parameters) { [self] values in - assert(Thread.isMainThread, "Completion must be called on main thread.") - var defaultValues: [String: State.ParameterValue] = [:] - parameters.forEach { parameter in - let defaultValue: String - if let value = values[parameter.key] { - switch parameter.type { - case .email, .numeric, .phone, .text: - defaultValue = self.formatted(value: value, type: parameter.type) - case .singleSelect: - precondition( - parameter.availableValues?.map(\.value).contains(value) == true, - "Unknown `singleSelect` parameter value." - ) - defaultValue = value + MainActor.assumeIsolated { + var defaultValues: [String: State.ParameterValue] = [:] + parameters.forEach { parameter in + let defaultValue: String + if let value = values[parameter.key] { + switch parameter.type { + case .email, .numeric, .phone, .text: + defaultValue = self.formatted(value: value, type: parameter.type) + case .singleSelect: + precondition( + parameter.availableValues?.map(\.value).contains(value) == true, + "Unknown `singleSelect` parameter value." + ) + defaultValue = value + } + } else { + defaultValue = self.defaultValue(for: parameter) } - } else { - defaultValue = self.defaultValue(for: parameter) + defaultValues[parameter.key] = .init(value: defaultValue, recentErrorMessage: nil) } - defaultValues[parameter.key] = .init(value: defaultValue, recentErrorMessage: nil) + completion(defaultValues) } - completion(defaultValues) } } else { var defaultValues: [String: State.ParameterValue] = [:] diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift index 328476082..20996f46f 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodDelegate.swift @@ -6,16 +6,19 @@ // /// Native alternative payment module delegate definition. -public protocol PONativeAlternativePaymentMethodDelegate: AnyObject { +public protocol PONativeAlternativePaymentMethodDelegate: AnyObject, Sendable { /// Invoked when module emits event. + @MainActor func nativeAlternativePaymentMethodDidEmitEvent(_ event: PONativeAlternativePaymentMethodEvent) /// Method provides an ability to supply default values for given parameters. Completion expects dictionary /// where key is a parameter key, and value is desired default. It is not mandatory to provide defaults for /// all parameters. /// - NOTE: completion must be called on `main` thread. + @MainActor func nativeAlternativePaymentMethodDefaultValues( - for parameters: [PONativeAlternativePaymentMethodParameter], completion: @escaping ([String: String]) -> Void + for parameters: [PONativeAlternativePaymentMethodParameter], + completion: @escaping @Sendable ([String: String]) -> Void ) } diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractor.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractor.swift index 67e1a6df7..21e45e569 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractor.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractor.swift @@ -8,7 +8,9 @@ import Foundation // todo(andrii-vysotskyi): migrate interactor and dependencies to UI module when ready -@_spi(PO) public protocol PONativeAlternativePaymentMethodInteractor: AnyObject { +@_spi(PO) +@MainActor +public protocol PONativeAlternativePaymentMethodInteractor: AnyObject { typealias State = PONativeAlternativePaymentMethodInteractorState diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractorState.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractorState.swift index 22b2e3374..979ab5a6b 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractorState.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Interactor/PONativeAlternativePaymentMethodInteractorState.swift @@ -7,9 +7,10 @@ import UIKit -@_spi(PO) public enum PONativeAlternativePaymentMethodInteractorState { +@_spi(PO) +public enum PONativeAlternativePaymentMethodInteractorState { - public struct ParameterValue { + public struct ParameterValue: Sendable { /// Actual parameter value. public let value: String? @@ -91,3 +92,6 @@ import UIKit /// Payment is completed. case captured(Captured) } + +@available(*, unavailable) +extension PONativeAlternativePaymentMethodInteractorState: Sendable { } diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodBackgroundStyle.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodBackgroundStyle.swift index 47456ea82..68864a89b 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodBackgroundStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodBackgroundStyle.swift @@ -9,6 +9,7 @@ import UIKit /// Native alternative payment method screen background style. @available(*, deprecated, message: "Use ProcessOutUI.PONativeAlternativePaymentBackgroundStyle instead.") +@MainActor public struct PONativeAlternativePaymentMethodBackgroundStyle { /// Regular background color. diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodStyle.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodStyle.swift index 4f227505d..3da47284c 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/Models/Style/PONativeAlternativePaymentMethodStyle.swift @@ -9,6 +9,7 @@ import UIKit /// Defines style for native alternative payment method module. @available(*, deprecated, message: "Use ProcessOutUI.PONativeAlternativePaymentStyle instead.") +@MainActor public struct PONativeAlternativePaymentMethodStyle { /// Title style. @@ -81,6 +82,7 @@ public struct PONativeAlternativePaymentMethodStyle { // MARK: - Private Nested Types + @MainActor private enum Constants { static let title = POTextStyle(color: UIColor(resource: .Text.primary), typography: .Medium.title) static let sectionTitle = POTextStyle( diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/View/Cells/NativeAlternativePaymentMethodCell.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/View/Cells/NativeAlternativePaymentMethodCell.swift index a43130564..366e26466 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/View/Cells/NativeAlternativePaymentMethodCell.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/View/Cells/NativeAlternativePaymentMethodCell.swift @@ -8,6 +8,7 @@ import UIKit @available(*, deprecated) +@MainActor protocol NativeAlternativePaymentMethodCell: UICollectionViewCell { /// Tells the cell that it is about to be displayed. @@ -24,6 +25,7 @@ protocol NativeAlternativePaymentMethodCell: UICollectionViewCell { } @available(*, deprecated) +@MainActor protocol NativeAlternativePaymentMethodCellDelegate: AnyObject { /// Should return boolean value indicating whether cells input should return ie resign first responder. diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/DefaultNativeAlternativePaymentMethodViewModel.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/DefaultNativeAlternativePaymentMethodViewModel.swift index c12085f82..9d5be1b7a 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/DefaultNativeAlternativePaymentMethodViewModel.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/DefaultNativeAlternativePaymentMethodViewModel.swift @@ -209,7 +209,9 @@ final class DefaultNativeAlternativePaymentMethodViewModel: withTimeInterval: Constants.captureSuccessCompletionDelay, repeats: false, block: { [weak self] _ in - self?.completion?(.success(())) + MainActor.assumeIsolated { + self?.completion?(.success(())) + } } ) let submittedItem = State.SubmittedItem( @@ -389,8 +391,10 @@ final class DefaultNativeAlternativePaymentMethodViewModel: } self[keyPath: isDisabled] = true let timer = Timer.scheduledTimer(withTimeInterval: interval, repeats: false) { [weak self] _ in - self?[keyPath: isDisabled] = false - self?.configureWithInteractorState() + MainActor.assumeIsolated { + self?[keyPath: isDisabled] = false + self?.configureWithInteractorState() + } } cancelActionTimers[timerKey] = timer } diff --git a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/NativeAlternativePaymentMethodViewModel.swift b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/NativeAlternativePaymentMethodViewModel.swift index 9491c39e4..e9b502a51 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/NativeAlternativePaymentMethodViewModel.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/NativeAlternativePaymentMethod/ViewModel/NativeAlternativePaymentMethodViewModel.swift @@ -6,6 +6,7 @@ // @available(*, deprecated) +@MainActor protocol NativeAlternativePaymentMethodViewModel: ViewModel { /// Submits parameter values. diff --git a/Sources/ProcessOut/Sources/UI/Modules/Safari/DefaultSafariViewModel.swift b/Sources/ProcessOut/Sources/UI/Modules/Safari/DefaultSafariViewModel.swift index 52071eb4a..c6d1279d2 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/Safari/DefaultSafariViewModel.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/Safari/DefaultSafariViewModel.swift @@ -9,7 +9,8 @@ import Foundation import SafariServices @available(*, deprecated) -final class DefaultSafariViewModel: NSObject, SFSafariViewControllerDelegate { +@MainActor +final class DefaultSafariViewModel: NSObject, @preconcurrency SFSafariViewControllerDelegate { init( configuration: DefaultSafariViewModelConfiguration, @@ -30,7 +31,9 @@ final class DefaultSafariViewModel: NSObject, SFSafariViewControllerDelegate { } if let timeout = configuration.timeout { timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in - self?.setCompletedState(with: POFailure(code: .timeout(.mobile))) + MainActor.assumeIsolated { + self?.setCompletedState(with: POFailure(code: .timeout(.mobile))) + } } } deepLinkObserver = eventEmitter.on(PODeepLinkReceivedEvent.self) { [weak self] event in diff --git a/Sources/ProcessOut/Sources/UI/Modules/Safari/SafariViewController+Extensions.swift b/Sources/ProcessOut/Sources/UI/Modules/Safari/SafariViewController+Extensions.swift index d31338c31..b13f287bb 100644 --- a/Sources/ProcessOut/Sources/UI/Modules/Safari/SafariViewController+Extensions.swift +++ b/Sources/ProcessOut/Sources/UI/Modules/Safari/SafariViewController+Extensions.swift @@ -16,6 +16,7 @@ extension SFSafariViewController { // MARK: - Private Nested Types + @MainActor private enum Keys { static var viewModel: UInt8 = 0 } diff --git a/Sources/ProcessOut/Sources/UI/Shared/Architecture/View/BaseViewController.swift b/Sources/ProcessOut/Sources/UI/Shared/Architecture/View/BaseViewController.swift index cade1a550..3fdf1cb8e 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Architecture/View/BaseViewController.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Architecture/View/BaseViewController.swift @@ -66,7 +66,9 @@ class BaseViewController: UIViewController where Model: ViewModel { // There may be UI glitches if view is updated when being tracked by user. So // as a workaround, configuration is postponed to a point when tracking ends. guard RunLoop.current.currentMode != .tracking else { - RunLoop.current.perform(viewModelDidChange) + RunLoop.current.perform { + MainActor.assumeIsolated(self.viewModelDidChange) + } return } // View is configured without animation if it is not yet part of the hierarchy to avoid visual issues. @@ -113,7 +115,9 @@ class BaseViewController: UIViewController where Model: ViewModel { // is extracted from notification and update is scheduled for next run loop iteration. Collection layout // update is needed here in a first place because layout depends on inset, which transitively depends on // keyboard visibility. - RunLoop.current.perform(animator.startAnimation) + RunLoop.current.perform { + MainActor.assumeIsolated(animator.startAnimation) + } } } diff --git a/Sources/ProcessOut/Sources/UI/Shared/Architecture/ViewModel/ViewModel.swift b/Sources/ProcessOut/Sources/UI/Shared/Architecture/ViewModel/ViewModel.swift index 629c4e586..ade646c02 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Architecture/ViewModel/ViewModel.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Architecture/ViewModel/ViewModel.swift @@ -6,6 +6,7 @@ // @available(*, deprecated) +@MainActor protocol ViewModel: AnyObject { associatedtype State diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Layouts/Center/CollectionViewDelegateCenterLayout.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Layouts/Center/CollectionViewDelegateCenterLayout.swift index 678439d74..332392d7f 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Layouts/Center/CollectionViewDelegateCenterLayout.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Layouts/Center/CollectionViewDelegateCenterLayout.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor protocol CollectionViewDelegateCenterLayout: AnyObject, UICollectionViewDelegateFlowLayout { /// Should return index of the section that should be centered. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStateStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStateStyle.swift index 1a3728988..7ebbb0526 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStateStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStateStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Defines input's styling information in a specific state. +@MainActor public struct POInputStateStyle { /// Text style. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStyle.swift index 836e06545..5f13e0576 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/Input/POInputStyle.swift @@ -11,6 +11,7 @@ import UIKit public typealias POTextFieldStyle = POInputStyle /// Defines input control style in both normal and error states. +@MainActor public struct POInputStyle { /// Style for normal state. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POBorderStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POBorderStyle.swift index 49c5a00c0..8283ea8dd 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POBorderStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POBorderStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Style that defines border appearance. Border is always a solid line. +@MainActor public struct POBorderStyle { /// Corner radius. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POShadowStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POShadowStyle.swift index 2bc326bcb..3839d35b7 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POShadowStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Styles/POShadowStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Style that defines shadow appearance. +@MainActor public struct POShadowStyle { /// The color of the shadow. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTextStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTextStyle.swift index e37cacad5..48f4b801c 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTextStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTextStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Text style. +@MainActor public struct POTextStyle { /// Text foreground color. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTypography.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTypography.swift index cb2ee3189..1726a0ef8 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTypography.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Typography/POTypography.swift @@ -8,6 +8,7 @@ import UIKit /// Holds typesetting information that could be applied to displayed text. +@MainActor public struct POTypography { /// Font associated with given typography. @@ -50,6 +51,7 @@ public struct POTypography { extension POTypography { + @MainActor enum Fixed { /// Use for captions, status labels and tags. @@ -70,6 +72,7 @@ extension POTypography { static let labelHeading = POTypography(font: FontFamily.WorkSans.medium.font(size: 14), lineHeight: 18) } + @MainActor enum Medium { /// Use for page titles. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActionsContainer/POActionsContainerStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActionsContainer/POActionsContainerStyle.swift index 303b0ff7b..cce9e0dfa 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActionsContainer/POActionsContainerStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActionsContainer/POActionsContainerStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Actions container style. +@MainActor public struct POActionsContainerStyle { /// Style for primary button. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/ActivityIndicatorViewFactory.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/ActivityIndicatorViewFactory.swift index a07913913..9b8f2ef8b 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/ActivityIndicatorViewFactory.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/ActivityIndicatorViewFactory.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor final class ActivityIndicatorViewFactory { func create(style: POActivityIndicatorStyle) -> POActivityIndicatorView { diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorStyle.swift index 7f38e4f27..9e3bde848 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Possible activity indicator styles. +@MainActor public enum POActivityIndicatorStyle { /// Custom activity indicator. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorView.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorView.swift index b42b82c8d..82843c4ba 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorView.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/ActivityIndicator/POActivityIndicatorView.swift @@ -12,6 +12,7 @@ public typealias POActivityIndicatorViewType = POActivityIndicatorView /// Protocol that activity indicator should conform to in order to be used with /// ``POActivityIndicatorStyle`` custom style. +@MainActor public protocol POActivityIndicatorView: UIView { /// Changes animation state. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStateStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStateStyle.swift index d636dd192..c0cbed388 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStateStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStateStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Defines button's styling information in a specific state. +@MainActor public struct POButtonStateStyle { /// Text typography. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStyle.swift index 5cd2bd91c..3270c2294 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/Button/POButtonStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Defines button style in all possible states. +@MainActor public struct POButtonStyle { /// Style for normal state. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/CodeTextField/CodeTextFieldDelegate.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/CodeTextField/CodeTextFieldDelegate.swift index 0d81b96d1..f74c1a177 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/CodeTextField/CodeTextFieldDelegate.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/CodeTextField/CodeTextFieldDelegate.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor protocol CodeTextFieldDelegate: AnyObject { /// Asks the delegate whether to begin editing in the specified text field. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonKnobStateStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonKnobStateStyle.swift index 197fe377e..64d62f70e 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonKnobStateStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonKnobStateStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Describes radio button knob style in a particular state. +@MainActor public struct PORadioButtonKnobStateStyle { /// Background color. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStateStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStateStyle.swift index 0fa7ed2e5..17cf3b762 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStateStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStateStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Describes radio button style in a particular state, for example when selected. +@MainActor public struct PORadioButtonStateStyle { /// Styling of the radio button knob not including value. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStyle.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStyle.swift index 753bc08b0..e4caec879 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStyle.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/RadioButton/PORadioButtonStyle.swift @@ -8,6 +8,7 @@ import UIKit /// Describes radio button style in different states. +@MainActor public struct PORadioButtonStyle { /// Style to use when radio button is in default state ie enabled and not selected. diff --git a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/TextField/TextFieldContainerView.swift b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/TextField/TextFieldContainerView.swift index 4b791d239..c3cf9877b 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/TextField/TextFieldContainerView.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/DesignSystem/Views/TextField/TextFieldContainerView.swift @@ -74,8 +74,10 @@ final class TextFieldContainerView: UIView { ] NSLayoutConstraint.activate(constraints) placeholderObservation = textField.observe(\.placeholder, options: .old) { [weak self] textField, value in - if textField.placeholder != value.oldValue { - self?.configureWithCurrentState(animated: false) + MainActor.assumeIsolated { + if textField.placeholder != value.oldValue { + self?.configureWithCurrentState(animated: false) + } } } } diff --git a/Sources/ProcessOut/Sources/UI/Shared/Extensions/UIImageView+Extensions.swift b/Sources/ProcessOut/Sources/UI/Shared/Extensions/UIImageView+Extensions.swift index 75a6fc048..297363ce2 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Extensions/UIImageView+Extensions.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Extensions/UIImageView+Extensions.swift @@ -21,6 +21,7 @@ extension UIImageView { // MARK: - Private Nested Types + @MainActor private enum AssociatedKeys { static var widthConstraint: UInt8 = 0 } diff --git a/Sources/ProcessOut/Sources/UI/Shared/Utils/CollectionReusableViewSizeProvider.swift b/Sources/ProcessOut/Sources/UI/Shared/Utils/CollectionReusableViewSizeProvider.swift index ca5297700..9a0ec4d5e 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Utils/CollectionReusableViewSizeProvider.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Utils/CollectionReusableViewSizeProvider.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor final class CollectionReusableViewSizeProvider { init() { diff --git a/Sources/ProcessOut/Sources/UI/Shared/Utils/KeyboardNotification.swift b/Sources/ProcessOut/Sources/UI/Shared/Utils/KeyboardNotification.swift index 52169fd92..f958e3fa2 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Utils/KeyboardNotification.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Utils/KeyboardNotification.swift @@ -7,6 +7,7 @@ import UIKit +@MainActor struct KeyboardNotification { /// Keyboard’s frame at the end of its animation. diff --git a/Sources/ProcessOut/Sources/UI/Shared/Utils/Reusable.swift b/Sources/ProcessOut/Sources/UI/Shared/Utils/Reusable.swift index 2e3a91710..c0e6807a8 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Utils/Reusable.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Utils/Reusable.swift @@ -5,6 +5,7 @@ // Created by Andrii Vysotskyi on 27.04.2023. // +@MainActor protocol Reusable: AnyObject { /// Reuse identifier. diff --git a/Sources/ProcessOut/Sources/UI/Shared/Utils/TextFieldUtils.swift b/Sources/ProcessOut/Sources/UI/Shared/Utils/TextFieldUtils.swift index 434a18428..3231bff95 100644 --- a/Sources/ProcessOut/Sources/UI/Shared/Utils/TextFieldUtils.swift +++ b/Sources/ProcessOut/Sources/UI/Shared/Utils/TextFieldUtils.swift @@ -8,6 +8,7 @@ import Foundation import UIKit +@MainActor enum TextFieldUtils { static func changeText( diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift index ceffb827c..22ed0c005 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/FocusCoordinator.swift @@ -7,10 +7,12 @@ import SwiftUI +@MainActor final class FocusCoordinator: ObservableObject { /// Holds boolean value indicating whether tracked control is currently being edited. - @Published private(set) var isEditing = false + @Published + private(set) var isEditing = false func track(control: UIControl) { guard self.control == nil else { diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift index b1d5006b6..73000ed70 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/FocusState/View+Focused.swift @@ -32,10 +32,14 @@ extension POBackport where Wrapped: View { @available(iOS 14, *) private struct FocusModifier: ViewModifier { - init(binding: Binding, value: Value) { - self._binding = binding - self.value = value - } + /// The state binding to register. + @Binding + private(set) var binding: Value? + + /// The value to match against when determining whether the binding should change. + let value: Value + + // MARK: - ViewModifier func body(content: Content) -> some View { content @@ -63,13 +67,6 @@ private struct FocusModifier: ViewModifier { // MARK: - Private Properties - /// The value to match against when determining whether the binding should change. - private let value: Value - - /// The state binding to register. - @Binding - private var binding: Value? - /// Indicates whether @State private var isVisible = false diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift b/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift index 23c2927dd..16d97b214 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/OnSubmit/View+OnSubmit.swift @@ -7,34 +7,50 @@ import SwiftUI +extension POBackport where Wrapped: Any { + + @MainActor + final class SubmitAction: Sendable { + + typealias Action = () -> Void // swiftlint:disable:this nesting + + nonisolated init() { + actions = [] + } + + func callAsFunction() { + actions.forEach { $0() } + } + + func append(action: @escaping Action) { + actions.append(action) + } + + // MARK: - Private Properties + + private nonisolated(unsafe) var actions: [Action] + } +} + extension POBackport where Wrapped: View { /// Adds an action to perform when the user submits a value to this view. /// - NOTE: Only works with `POTextField`. public func onSubmit(_ action: @escaping () -> Void) -> some View { - wrapped.environment(\.backportSubmitAction, action) + wrapped.transformEnvironment(\.backportSubmitAction) { $0.append(action: action) } } } extension EnvironmentValues { - var backportSubmitAction: (() -> Void)? { - get { - self[Key.self] - } - set { - let oldValue = backportSubmitAction - let box = { - oldValue?() - newValue?() - } - self[Key.self] = box - } + var backportSubmitAction: POBackport.SubmitAction { + get { self[Key.self] } + set { self[Key.self] = newValue } } // MARK: - Private Properties private struct Key: EnvironmentKey { - static let defaultValue: (() -> Void)? = nil + static let defaultValue = POBackport.SubmitAction() } } diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift b/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift index c20bce60b..f3e2d8809 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/POBackport.swift @@ -8,7 +8,9 @@ import SwiftUI /// Provides a convenient method for backporting API. -@_spi(PO) public struct POBackport { +@_spi(PO) +@MainActor +public struct POBackport { /// The underlying content this backport represents. public let wrapped: Wrapped @@ -23,7 +25,8 @@ import SwiftUI extension View { /// Wraps a SwiftUI `View` that can be extended to provide backport functionality. - @_spi(PO) public var backport: POBackport { + @_spi(PO) + public var backport: POBackport { .init(self) } } diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift b/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift index d45c0ad8c..a1aaa90d5 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/SubmitLabel/View+SubmitLabel.swift @@ -10,7 +10,7 @@ import SwiftUI extension POBackport where Wrapped == Any { /// A semantic label describing the label of submission within a view hierarchy. - public struct SubmitLabel: Equatable { + public struct SubmitLabel: Equatable, Sendable { let returnKeyType: UIReturnKeyType diff --git a/Sources/ProcessOutCoreUI/Sources/Backports/Task/View+Task.swift b/Sources/ProcessOutCoreUI/Sources/Backports/Task/View+Task.swift index 90659d77e..2da36731f 100644 --- a/Sources/ProcessOutCoreUI/Sources/Backports/Task/View+Task.swift +++ b/Sources/ProcessOutCoreUI/Sources/Backports/Task/View+Task.swift @@ -14,7 +14,9 @@ extension POBackport where Wrapped: View { @available(iOS 14, *) @ViewBuilder public func task( - id value: T, priority: TaskPriority = .userInitiated, _ action: @escaping @Sendable () async -> Void + id value: T, + priority: TaskPriority = .userInitiated, + @_inheritActorContext _ action: @escaping @Sendable () async -> Void ) -> some View where T: Equatable { if #available(iOS 15, *) { wrapped.task(id: value, priority: priority, action) @@ -23,11 +25,11 @@ extension POBackport where Wrapped: View { } } - @ViewBuilder @available(iOS, deprecated: 15) @available(iOS 14, *) + @ViewBuilder public func task( - priority: TaskPriority = .userInitiated, _ action: @escaping @Sendable () async -> Void + priority: TaskPriority = .userInitiated, @_inheritActorContext _ action: @escaping @Sendable () async -> Void ) -> some View { task(id: 0, priority: priority, action) } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift b/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift index 43d25a0ab..640018fca 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Containers/HorizontalSizeReader/HorizontalSizeReader.swift @@ -33,7 +33,7 @@ struct HorizontalSizeReader: View { private struct WidthPreferenceKey: PreferenceKey, Equatable { - static var defaultValue: CGFloat = 0 + static let defaultValue: CGFloat = 0 static func reduce(value: inout CGFloat, nextValue: () -> CGFloat) { // An empty reduce implementation takes the first value diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift index 5790d5044..9e4df9b82 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/MarkdownParser.swift @@ -17,6 +17,8 @@ enum MarkdownParser { guard let document else { preconditionFailure("Failed to parse markdown document") } - return MarkdownDocument(cmarkNode: document) + let markdownDocument = MarkdownDocument(cmarkNode: document) + cmark_node_free(document) + return markdownDocument } } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift index b91204e2a..ae127000b 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownBlockQuote.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownBlockQuote: MarkdownBaseNode { +final class MarkdownBlockQuote: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_BLOCK_QUOTE diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift index ff5e1476c..adfd07111 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeBlock.swift @@ -7,26 +7,22 @@ @_implementationOnly import cmark -final class MarkdownCodeBlock: MarkdownBaseNode { +final class MarkdownCodeBlock: MarkdownBaseNode, @unchecked Sendable { - /// Returns the info string from a fenced code block. - private(set) lazy var info: String? = { - guard let info = cmarkNode.pointee.as.code.info else { - return nil - } - return String(cString: info) - }() - - private(set) lazy var code: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() + /// Actual code value. + let code: String // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let literal = cmark_node_get_literal(cmarkNode) { + self.code = String(cString: literal) + } else { + self.code = "" + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_CODE_BLOCK } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift index 0fea4bbf7..c36af6b9a 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownCodeSpan.swift @@ -7,18 +7,23 @@ @_implementationOnly import cmark -final class MarkdownCodeSpan: MarkdownBaseNode { +final class MarkdownCodeSpan: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var code: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() + /// Code. + let code: String // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let literal = cmark_node_get_literal(cmarkNode) { + code = String(cString: literal) + } else { + assertionFailure("Unable to get text node value") + code = "" + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_CODE } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift index bb4afee92..447a72153 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownDocument.swift @@ -7,13 +7,7 @@ @_implementationOnly import cmark -final class MarkdownDocument: MarkdownBaseNode { - - deinit { - cmark_node_free(cmarkNode) - } - - // MARK: - MarkdownBaseNode +final class MarkdownDocument: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_DOCUMENT diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift index 9430c10db..9bca8252d 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownEmphasis.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownEmphasis: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownEmphasis: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_EMPH diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift index a0f84d70a..764b6f9c3 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownHeading.swift @@ -7,14 +7,17 @@ @_implementationOnly import cmark -final class MarkdownHeading: MarkdownBaseNode { +final class MarkdownHeading: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var level: Int = { - Int(cmarkNode.pointee.as.heading.level) - }() + let level: Int // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + level = Int(cmarkNode.pointee.as.heading.level) + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_HEADING } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift index dd1e3d02c..b9b37ca28 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLinebreak.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownLinebreak: MarkdownBaseNode { +final class MarkdownLinebreak: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_LINEBREAK diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift index 7de304ff2..4b6c6f3a6 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownLink.swift @@ -7,17 +7,21 @@ @_implementationOnly import cmark -final class MarkdownLink: MarkdownBaseNode { +final class MarkdownLink: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var url: String? = { - if let url = cmarkNode.pointee.as.link.url { - return String(cString: url) - } - return nil - }() + let url: String? // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let url = cmarkNode.pointee.as.link.url { + self.url = String(cString: url) + } else { + url = nil + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_LINK } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift index fcd219153..8e1cd66aa 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownList.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownList: MarkdownBaseNode { +final class MarkdownList: MarkdownBaseNode, @unchecked Sendable { enum ListType { @@ -18,7 +18,27 @@ final class MarkdownList: MarkdownBaseNode { case bullet(marker: Character) } - private(set) lazy var type: ListType = { + /// List type. + let type: ListType + + // MARK: - MarkdownBaseNode + + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + type = Self.listType(cmarkNode: cmarkNode) + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + + override static var cmarkNodeType: cmark_node_type { + CMARK_NODE_LIST + } + + override func accept(visitor: V) -> V.Result { + visitor.visit(list: self) + } + + // MARK: - Private Methods + + private static func listType(cmarkNode: CmarkNode) -> ListType { let listNode = cmarkNode.pointee.as.list switch UInt32(listNode.list_type) { case CMARK_BULLET_LIST.rawValue: @@ -40,19 +60,5 @@ final class MarkdownList: MarkdownBaseNode { default: preconditionFailure("Unsupported list type: \(listNode.list_type)") } - }() - - private(set) lazy var isTight: Bool = { - cmarkNode.pointee.as.list.tight - }() - - // MARK: - MarkdownBaseNode - - override static var cmarkNodeType: cmark_node_type { - CMARK_NODE_LIST - } - - override func accept(visitor: V) -> V.Result { - visitor.visit(list: self) } } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift index f4e9911f8..ba8fdfab8 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownListItem.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownListItem: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownListItem: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_ITEM diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift index 5c4c57970..f3716fc39 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownNode.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -class MarkdownBaseNode { +class MarkdownBaseNode: @unchecked Sendable { typealias CmarkNode = UnsafeMutablePointer @@ -19,11 +19,20 @@ class MarkdownBaseNode { if validatesType { assert(cmarkNode.pointee.type == Self.cmarkNodeType.rawValue) } - self.cmarkNode = cmarkNode + self.children = Self.children(of: cmarkNode) } /// Returns node children. - private(set) lazy var children: [MarkdownBaseNode] = { + let children: [MarkdownBaseNode] + + /// Accepts given visitor. + func accept(visitor: V) -> V.Result { // swiftlint:disable:this unavailable_function + fatalError("Must be implemented by subclass.") + } + + // MARK: - Private Methods + + private static func children(of cmarkNode: CmarkNode) -> [MarkdownBaseNode] { var cmarkChild = cmarkNode.pointee.first_child var children: [MarkdownBaseNode] = [] while let cmarkNode = cmarkChild { @@ -32,12 +41,5 @@ class MarkdownBaseNode { cmarkChild = cmarkNode.pointee.next } return children - }() - - let cmarkNode: CmarkNode - - /// Accepts given visitor. - func accept(visitor: V) -> V.Result { // swiftlint:disable:this unavailable_function - fatalError("Must be implemented by subclass.") } } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift index bbe1e3337..7f52cd21e 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownParagraph.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownParagraph: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownParagraph: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_PARAGRAPH diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift index 10dcf6e16..523b0cc53 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownSoftbreak.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownSoftbreak: MarkdownBaseNode { +final class MarkdownSoftbreak: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_SOFTBREAK diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift index badad3cc1..c67f4568e 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownStrong.swift @@ -7,9 +7,7 @@ @_implementationOnly import cmark -final class MarkdownStrong: MarkdownBaseNode { - - // MARK: - MarkdownBaseNode +final class MarkdownStrong: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_STRONG diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift index 62c841d91..020705c12 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownText.swift @@ -7,18 +7,23 @@ @_implementationOnly import cmark -final class MarkdownText: MarkdownBaseNode { +final class MarkdownText: MarkdownBaseNode, @unchecked Sendable { - private(set) lazy var value: String = { - guard let literal = cmark_node_get_literal(cmarkNode) else { - assertionFailure("Unable to get text node value") - return "" - } - return String(cString: literal) - }() + /// Text value. + let value: String // MARK: - MarkdownBaseNode + required init(cmarkNode: MarkdownBaseNode.CmarkNode, validatesType: Bool = true) { + if let literal = cmark_node_get_literal(cmarkNode) { + value = String(cString: literal) + } else { + assertionFailure("Unable to get text node value") + value = "" + } + super.init(cmarkNode: cmarkNode, validatesType: validatesType) + } + override static var cmarkNodeType: cmark_node_type { CMARK_NODE_TEXT } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift index 384788c9f..169489ca4 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownThematicBreak.swift @@ -7,7 +7,7 @@ @_implementationOnly import cmark -final class MarkdownThematicBreak: MarkdownBaseNode { +final class MarkdownThematicBreak: MarkdownBaseNode, @unchecked Sendable { override static var cmarkNodeType: cmark_node_type { CMARK_NODE_THEMATIC_BREAK diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift index 2fefc5028..2f1526934 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Nodes/MarkdownUnknown.swift @@ -6,7 +6,7 @@ // /// Unknown node. -final class MarkdownUnknown: MarkdownBaseNode { +final class MarkdownUnknown: MarkdownBaseNode, @unchecked Sendable { required init(cmarkNode: CmarkNode, validatesType: Bool = false) { super.init(cmarkNode: cmarkNode, validatesType: false) diff --git a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift index a1460bf98..d80bb87cd 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/MarkdownParser/Visitor/MarkdownDebugDescriptionPrinter.swift @@ -7,17 +7,7 @@ #if DEBUG -import Foundation - -extension MarkdownBaseNode: CustomDebugStringConvertible { - - var debugDescription: String { - let visitor = MarkdownDebugDescriptionPrinter() - return self.accept(visitor: visitor) - } -} - -private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { +final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { init(level: Int = 0) { self.level = level @@ -81,11 +71,7 @@ private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { } func visit(codeBlock: MarkdownCodeBlock) -> String { - var attributes: [String: CustomStringConvertible] = [:] - if let info = codeBlock.info { - attributes["info"] = info - } - return description(node: codeBlock, nodeName: "Code Block", attributes: attributes, content: codeBlock.code) + description(node: codeBlock, nodeName: "Code Block", content: codeBlock.code) } func visit(thematicBreak: MarkdownThematicBreak) -> String { @@ -147,4 +133,12 @@ private final class MarkdownDebugDescriptionPrinter: MarkdownVisitor { } } +extension MarkdownBaseNode: CustomDebugStringConvertible { + + var debugDescription: String { + let visitor = MarkdownDebugDescriptionPrinter() + return self.accept(visitor: visitor) + } +} + #endif diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift index 6563f3c25..65df4394e 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Blink/View+Blink.swift @@ -31,5 +31,6 @@ private struct BlinkViewModifier: ViewModifier { // MARK: - Private Properties - @State private var isVisible = true + @State + private var isVisible = true } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift index 501489096..761c5eb5b 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/KeyboardType/View+KeyboardType.swift @@ -11,7 +11,8 @@ extension View { /// Sets the keyboard type for this view. In addition to calling the native counterpart, /// the implementation also exposes given type as an environment so works with `POTextField`. - @_spi(PO) public func poKeyboardType(_ type: UIKeyboardType) -> some View { + @_spi(PO) + public func poKeyboardType(_ type: UIKeyboardType) -> some View { environment(\.poKeyboardType, type).keyboardType(type) } } diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift index 5836eabc5..40b0c00ec 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/Modify/View+Modify.swift @@ -7,7 +7,8 @@ import SwiftUI -@_spi(PO) extension View { +@_spi(PO) +extension View { @ViewBuilder public func modify(when condition: Bool, @ViewBuilder _ transform: (Self) -> some View) -> some View { diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift index 99d6ca981..1428f4939 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/OnSizeChange/View+OnSizeChange.swift @@ -35,7 +35,7 @@ private struct SizeModifier: ViewModifier { private struct SizePreferenceKey: PreferenceKey { - static var defaultValue: CGSize = .zero + static let defaultValue: CGSize = .zero static func reduce(value: inout CGSize, nextValue: () -> CGSize) { value = nextValue() diff --git a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift index 0a666047b..22aeb924a 100644 --- a/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift +++ b/Sources/ProcessOutCoreUI/Sources/Core/Modifiers/TextContentType/View+TextContentType.swift @@ -11,7 +11,8 @@ extension View { /// Sets the text content type for this view. In addition to calling the native counterpart, /// the implementation also exposes given type as an environment so works with `POTextField`. - @_spi(PO) public func poTextContentType(_ type: UITextContentType?) -> some View { + @_spi(PO) + public func poTextContentType(_ type: UITextContentType?) -> some View { environment(\.poTextContentType, type).textContentType(type) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift index c1d9799d5..e1a550ee2 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/POActionsContainerStyle.swift @@ -8,6 +8,7 @@ import SwiftUI /// Actions container style. +@MainActor public struct POActionsContainerStyle { /// Style for primary button. diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift index ce6ca39ca..362775bc5 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ActionsContainer/View+ActionsContainerStyle.swift @@ -26,7 +26,8 @@ extension EnvironmentValues { // MARK: - Private Nested Types - private struct Key: EnvironmentKey { + @MainActor + private struct Key: @preconcurrency EnvironmentKey { static let defaultValue = POActionsContainerStyle.default } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift index 292e1c9cb..7ed2f7e67 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/AsyncImage/POAsyncImage.swift @@ -8,7 +8,6 @@ import SwiftUI @_spi(PO) -@MainActor @available(iOS 14, *) public struct POAsyncImage: View { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift index 1e15bbac9..d4e715615 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Border/POBorderStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Style that defines border appearance. Border is always a solid line. -public struct POBorderStyle { +public struct POBorderStyle: Sendable { /// Corner radius. public let radius: CGFloat diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift index f23ce30c2..929775ec9 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButton.swift @@ -67,6 +67,7 @@ private struct ButtonRepresentable: UIViewRepresentable { } } +@MainActor private final class ButtonCoordinator { init(action: @escaping () -> Void) { diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift index 51f71ee25..c6f940158 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/PassKit/POPassKitPaymentButtonStyle.swift @@ -9,7 +9,7 @@ import PassKit /// PassKit button style. @available(iOS 14.0, *) -public struct POPassKitPaymentButtonStyle { +public struct POPassKitPaymentButtonStyle: Sendable { /// Native style value. public let style: PKPaymentButtonStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift index 72a639b39..520045c2a 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Button/Regular/POButtonStateStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Defines button's styling information in a specific state. -public struct POButtonStateStyle { +public struct POButtonStateStyle: Sendable { /// Title typography. public let title: POTextStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift index fa0793b18..4156277d9 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/CodeFieldViewCoordinator.swift @@ -7,6 +7,7 @@ import Foundation +@MainActor final class CodeFieldViewCoordinator { var representable: CodeFieldRepresentable! // swiftlint:disable:this implicitly_unwrapped_optional diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift index f00b5a66b..e6006106e 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/POCodeField.swift @@ -24,8 +24,7 @@ public struct POCodeField: View { focusCoordinator.beginEditing() textIndex = newIndex } - style - .makeBody(configuration: configuration) + AnyView(style.makeBody(configuration: configuration)) .background( CodeFieldRepresentable( length: length, text: $text, textIndex: $textIndex, isMenuVisible: $isMenuVisible diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift deleted file mode 100644 index 86df19d78..000000000 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/AnyCodeFieldStyle.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AnyCodeFieldStyle.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 13.06.2024. -// - -import SwiftUI - -struct AnyCodeFieldStyle: CodeFieldStyle { - - init(erasing style: some CodeFieldStyle) { - _makeBody = { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - func makeBody(configuration: CodeFieldStyleConfiguration) -> some View { - _makeBody(configuration) - } - - // MARK: - Private Properties - - private let _makeBody: (CodeFieldStyleConfiguration) -> AnyView -} diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift index 0ef91e704..5979d08f1 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyle.swift @@ -7,6 +7,7 @@ import SwiftUI +@MainActor protocol CodeFieldStyle { /// A view that represents the body of a button. diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyleConfiguration.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyleConfiguration.swift index f18891d26..addc3d1b7 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyleConfiguration.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/CodeFieldStyleConfiguration.swift @@ -34,3 +34,6 @@ struct CodeFieldStyleConfiguration { private let _setIndex: (_ index: Index) -> Void } + +@available(*, unavailable) +extension CodeFieldStyleConfiguration: Sendable { } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift index f448589d5..ccc195765 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/CodeField/Style/View+CodeFieldStyle.swift @@ -11,22 +11,23 @@ extension View { /// Sets the style for picker views within this view. @available(iOS 14.0, *) - func codeFieldStyle(_ style: some CodeFieldStyle) -> some View { - environment(\.codeFieldStyle, AnyCodeFieldStyle(erasing: style)) + func codeFieldStyle(_ style: any CodeFieldStyle) -> some View { + environment(\.codeFieldStyle, style) } } @available(iOS 14.0, *) extension EnvironmentValues { - var codeFieldStyle: AnyCodeFieldStyle { + var codeFieldStyle: any CodeFieldStyle { get { self[Key.self] } set { self[Key.self] = newValue } } // MARK: - Private Properties - private struct Key: EnvironmentKey { - static let defaultValue = AnyCodeFieldStyle(erasing: .default) + @MainActor + private struct Key: @preconcurrency EnvironmentKey { + static let defaultValue: any CodeFieldStyle = .default } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ConfirmationDialog/View+ConfirmationDialog.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ConfirmationDialog/View+ConfirmationDialog.swift index 7f0a4da5b..01df2a11e 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ConfirmationDialog/View+ConfirmationDialog.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ConfirmationDialog/View+ConfirmationDialog.swift @@ -9,8 +9,8 @@ import SwiftUI extension View { - @_spi(PO) @available(iOS 14, *) + @_spi(PO) public func poConfirmationDialog(item: Binding) -> some View { modifier(ContentModifier(confirmationDialog: item)) } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift index 849f5b784..4b74457b8 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStateStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Defines input's styling information in a specific state. -public struct POInputStateStyle { +public struct POInputStateStyle: Sendable { /// Text style. public let text: POTextStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift index 22619c26d..1c357336d 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/POInputStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Defines input control style in both normal and error states. -public struct POInputStyle { +public struct POInputStyle: Sendable { /// Style for normal state. public let normal: POInputStateStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift index df5d74fc9..3fb67e693 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/InputStyle/View+InputStyle.swift @@ -9,7 +9,8 @@ import SwiftUI extension View { - @_spi(PO) public func inputStyle(_ style: POInputStyle) -> some View { + @_spi(PO) + public func inputStyle(_ style: POInputStyle) -> some View { environment(\.inputStyle, style) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift deleted file mode 100644 index bcf912062..000000000 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/AnyMessageViewStyle.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// AnyMessageViewStyle.swift -// ProcessOutCoreUI -// -// Created by Andrii Vysotskyi on 03.06.2024. -// - -import SwiftUI - -struct AnyMessageViewStyle: POMessageViewStyle { - - init(erasing style: any POMessageViewStyle) { - _makeBody = { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - func makeBody(configuration: Configuration) -> AnyView { - _makeBody(configuration) - } - - // MARK: - Private Properties - - private let _makeBody: (Configuration) -> AnyView -} diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift index 3e70f3d77..8401ba324 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/MessageView+Style.swift @@ -13,21 +13,22 @@ extension View { @_spi(PO) @available(iOS 14.0, *) public func messageViewStyle(_ style: any POMessageViewStyle) -> some View { - environment(\.messageViewStyle, AnyMessageViewStyle(erasing: style)) + environment(\.messageViewStyle, style) } } @available(iOS 14.0, *) extension EnvironmentValues { - var messageViewStyle: AnyMessageViewStyle { + var messageViewStyle: any POMessageViewStyle { get { self[Key.self] } set { self[Key.self] = newValue } } // MARK: - Private Properties - private struct Key: EnvironmentKey { - static let defaultValue = AnyMessageViewStyle(erasing: .toast) + @MainActor + private struct Key: @preconcurrency EnvironmentKey { + static let defaultValue: any POMessageViewStyle = .toast } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift index df735f4fb..03e2bbf3e 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageView.swift @@ -19,7 +19,7 @@ public struct POMessageView: View { public var body: some View { let configuration = POMessageViewStyleConfiguration(label: Text(message.text), severity: message.severity) - style.makeBody(configuration: configuration) + AnyView(style.makeBody(configuration: configuration)) } // MARK: - Private Properties diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift index c35adaaa4..9514cbef0 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyle.swift @@ -7,6 +7,7 @@ import SwiftUI +@MainActor public protocol POMessageViewStyle { /// A view that represents the body of a message. diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyleConfiguration.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyleConfiguration.swift index 26d85c7c2..1df272b90 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyleConfiguration.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POMessageViewStyleConfiguration.swift @@ -21,3 +21,6 @@ public struct POMessageViewStyleConfiguration { self.severity = severity } } + +@available(*, unavailable) +extension POMessageViewStyleConfiguration: Sendable { } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift index 44219545b..79eac29ee 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Message/POToastMessageStyle.swift @@ -11,7 +11,7 @@ import SwiftUI public struct POToastMessageStyle: POMessageViewStyle { /// Style for specific severity. - public struct Severity { + public struct Severity: Sendable { /// Icon image. public let icon: Image? diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift index 1b07b1032..ad9a7e6f6 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPicker.swift @@ -23,7 +23,7 @@ public struct POPicker: View { let configuration = POPickerStyleConfiguration( elements: data.map(createConfigurationElement), isInvalid: isInvalid ) - style.makeBody(configuration: configuration) + AnyView(style.makeBody(configuration: configuration)) } // MARK: - Private Properties diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift index 66d4e2eea..58355681f 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/POPickerStyle.swift @@ -9,7 +9,9 @@ import SwiftUI /// A type that specifies the appearance and interaction of all pickers /// within a view hierarchy. -@_spi(PO) public protocol POPickerStyle { +@_spi(PO) +@MainActor +public protocol POPickerStyle { /// A view representing the appearance and interaction of a `POPicker`. associatedtype Body: View @@ -20,7 +22,8 @@ import SwiftUI @ViewBuilder func makeBody(configuration: POPickerStyleConfiguration) -> Self.Body } -@_spi(PO) public struct POPickerStyleConfiguration { +@_spi(PO) +public struct POPickerStyleConfiguration { /// Picker elements. public let elements: [POPickerStyleConfigurationElement] @@ -29,7 +32,8 @@ import SwiftUI public let isInvalid: Bool } -@_spi(PO) public struct POPickerStyleConfigurationElement: Identifiable { +@_spi(PO) +public struct POPickerStyleConfigurationElement: Identifiable { /// The stable identity of the element. public let id: AnyHashable @@ -44,19 +48,5 @@ import SwiftUI public let select: () -> Void } -struct AnyPickerStyle: POPickerStyle { - - init(erasing style: Style) { - _makeBody = { configuration in - AnyView(style.makeBody(configuration: configuration)) - } - } - - func makeBody(configuration: POPickerStyleConfiguration) -> some View { - _makeBody(configuration) - } - - // MARK: - Private Properties - - private let _makeBody: (POPickerStyleConfiguration) -> AnyView -} +@available(*, unavailable) +extension POPickerStyleConfiguration: Sendable { } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift index 0462a10e0..c5bec397d 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Picker/View+PickerStyle.swift @@ -12,22 +12,23 @@ extension View { /// Sets the style for picker views within this view. @_spi(PO) @available(iOS 14, *) - public func pickerStyle(_ style: Style) -> some View { - environment(\.pickerStyle, AnyPickerStyle(erasing: style)) + public func pickerStyle(_ style: any POPickerStyle) -> some View { + environment(\.pickerStyle, style) } } @available(iOS 14, *) extension EnvironmentValues { - var pickerStyle: AnyPickerStyle { + var pickerStyle: any POPickerStyle { get { self[Key.self] } set { self[Key.self] = newValue } } // MARK: - Private Properties - private struct Key: EnvironmentKey { - static let defaultValue = AnyPickerStyle(erasing: .radioGroup) + @MainActor + private struct Key: @preconcurrency EnvironmentKey { + static let defaultValue: any POPickerStyle = .radioGroup } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ProgressView/View+ProgressViewStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ProgressView/View+ProgressViewStyle.swift index 96f5433ca..cd4a511ef 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/ProgressView/View+ProgressViewStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/ProgressView/View+ProgressViewStyle.swift @@ -11,8 +11,8 @@ extension View { /// Sets the style for progress views in this view. This method should be used when /// specific style type is unknown and there is no possibility to use generic. - @_spi(PO) @available(iOS 14, *) + @_spi(PO) public func poProgressViewStyle(_ style: any ProgressViewStyle) -> some View { AnyView(self.progressViewStyle(style)) } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift index 8e287ec57..2562521c2 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonKnobStateStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Describes radio button knob style in a particular state. -public struct PORadioButtonKnobStateStyle { +public struct PORadioButtonKnobStateStyle: Sendable { /// Background color. public let backgroundColor: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift index ab7501801..81eb5dc08 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/PORadioButtonStateStyle.swift @@ -8,7 +8,7 @@ import UIKit /// Describes radio button style in a particular state, for example when selected. -public struct PORadioButtonStateStyle { +public struct PORadioButtonStateStyle: Sendable { /// Styling of the radio button knob not including value. public let knob: PORadioButtonKnobStateStyle diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift index a494136de..4622c0c85 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/RadioButton/View+RadioButtonSelected.swift @@ -9,7 +9,8 @@ import SwiftUI extension View { - @_spi(PO) public func radioButtonSelected(_ isSelected: Bool) -> some View { + @_spi(PO) + public func radioButtonSelected(_ isSelected: Bool) -> some View { environment(\.isRadioButtonSelected, isSelected) } } diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift index aa82aa14a..046333494 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Shadow/POShadowStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Style that defines shadow appearance. -public struct POShadowStyle { +public struct POShadowStyle: Sendable { /// The color of the shadow. public let color: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift index c4d858a3b..41ab25f69 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Spacing/POSpacing.swift @@ -7,7 +7,8 @@ import Foundation -@_spi(PO) public enum POSpacing { +@_spi(PO) +public enum POSpacing { /// Extra small spacing. public static let extraSmall: CGFloat = 4 diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift index 205beb3ba..a050991a4 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Text/POTextStyle.swift @@ -8,7 +8,7 @@ import SwiftUI /// Text style. -public struct POTextStyle { +public struct POTextStyle: Sendable { /// Text foreground color. public let color: Color diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift index c34e9a7f1..4286150d9 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/TextField/POTextField.swift @@ -117,7 +117,7 @@ private struct TextFieldRepresentable: UIViewRepresentable { // MARK: - func willReturn() { - submitAction?() + submitAction() } // MARK: - Private Nested Types diff --git a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift index dc37fcf41..4c8af8e94 100644 --- a/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift +++ b/Sources/ProcessOutCoreUI/Sources/DesignSystem/Typography/POTypography.swift @@ -8,7 +8,7 @@ import UIKit /// Holds typesetting information that could be applied to displayed text. -public struct POTypography { +public struct POTypography: Sendable { /// Font assosiated with given typography. public let font: UIFont diff --git a/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/ColorResource.swift b/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/ColorResource.swift index a6a6113e3..9707897d7 100644 --- a/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/ColorResource.swift +++ b/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/ColorResource.swift @@ -11,7 +11,7 @@ import SwiftUI /// A color resource. /// - NOTE: This type wraps natively generated `ColorResource` to make resources publicly accessible. -@_spi(PO) public struct POColorResource { +@_spi(PO) public struct POColorResource: Sendable { fileprivate init(_ colorResource: ColorResource) { self.colorResource = colorResource diff --git a/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift b/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift index 6f949b8b9..a2f2b7943 100644 --- a/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift +++ b/Sources/ProcessOutCoreUI/Sources/ResourceSymbols/FontResource.swift @@ -10,7 +10,7 @@ import UIKit /// A font resource. -struct FontResource { +struct FontResource: Sendable { /// Font resource name. fileprivate let weight: UIFont.Weight diff --git a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift index dd19cbd82..87b203d90 100644 --- a/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift +++ b/Sources/ProcessOutUI/Sources/Api/Test3DS/POTest3DSService.swift @@ -21,7 +21,7 @@ public final class POTest3DSService: PO3DSService { public func authenticationRequest( configuration: PO3DS2Configuration, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let request = PO3DS2AuthenticationRequest( deviceData: "", @@ -33,26 +33,28 @@ public final class POTest3DSService: PO3DSService { completion(.success(request)) } - public func handle(challenge: PO3DS2Challenge, completion: @escaping (Result) -> Void) { - guard let presentingViewController = PresentingViewControllerProvider.find() else { - completion(.success(false)) - return - } - let alertController = UIAlertController( - title: String(resource: .Test3DS.title), message: "", preferredStyle: .alert - ) - let acceptAction = UIAlertAction(title: String(resource: .Test3DS.accept), style: .default) { _ in - completion(.success(true)) - } - alertController.addAction(acceptAction) - let rejectAction = UIAlertAction(title: String(resource: .Test3DS.reject), style: .default) { _ in - completion(.success(false)) + public func handle(challenge: PO3DS2Challenge, completion: @escaping @Sendable (Result) -> Void) { + MainActor.assumeIsolated { + guard let presentingViewController = PresentingViewControllerProvider.find() else { + completion(.success(false)) + return + } + let alertController = UIAlertController( + title: String(resource: .Test3DS.title), message: "", preferredStyle: .alert + ) + let acceptAction = UIAlertAction(title: String(resource: .Test3DS.accept), style: .default) { _ in + completion(.success(true)) + } + alertController.addAction(acceptAction) + let rejectAction = UIAlertAction(title: String(resource: .Test3DS.reject), style: .default) { _ in + completion(.success(false)) + } + alertController.addAction(rejectAction) + presentingViewController.present(alertController, animated: true) } - alertController.addAction(rejectAction) - presentingViewController.present(alertController, animated: true) } - public func handle(redirect: PO3DSRedirect, completion: @escaping (Result) -> Void) { + public func handle(redirect: PO3DSRedirect, completion: @escaping @Sendable (Result) -> Void) { Task { @MainActor in let session = POWebAuthenticationSession(redirect: redirect, returnUrl: returnUrl, completion: completion) if await session.start() { diff --git a/Sources/ProcessOutUI/Sources/Core/Interactor/BaseInteractor.swift b/Sources/ProcessOutUI/Sources/Core/Interactor/BaseInteractor.swift index b9517c273..03dacbb6c 100644 --- a/Sources/ProcessOutUI/Sources/Core/Interactor/BaseInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Core/Interactor/BaseInteractor.swift @@ -23,7 +23,6 @@ class BaseInteractor: Interactor { var didChange: (() -> Void)? var willChange: ((State) -> Void)? - @MainActor func start() { // Does nothing } diff --git a/Sources/ProcessOutUI/Sources/Core/Interactor/Interactor.swift b/Sources/ProcessOutUI/Sources/Core/Interactor/Interactor.swift index edc80e604..ddaf04a36 100644 --- a/Sources/ProcessOutUI/Sources/Core/Interactor/Interactor.swift +++ b/Sources/ProcessOutUI/Sources/Core/Interactor/Interactor.swift @@ -5,6 +5,7 @@ // Created by Andrii Vysotskyi on 19.10.2023. // +@MainActor protocol Interactor: AnyObject { associatedtype State diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift index 9146443c0..c86aeba78 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecification.swift @@ -5,25 +5,25 @@ // Created by Andrii Vysotskyi on 26.10.2023. // -struct AddressSpecification { +struct AddressSpecification: Sendable { - enum Unit: String, CaseIterable, Decodable { + enum Unit: String, CaseIterable, Decodable, Sendable { case street, city, state, postcode } - enum CityUnit: String, Decodable { + enum CityUnit: String, Decodable, Sendable { case city, district, postTown, suburb } - enum StateUnit: String, Decodable { + enum StateUnit: String, Decodable, Sendable { case area, county, department, doSi, emirate, island, oblast, parish, prefecture, province, state } - enum PostcodeUnit: String, Decodable { + enum PostcodeUnit: String, Decodable, Sendable { case postcode, eircode, pin, zip } - struct State: Decodable { + struct State: Decodable, Sendable { let abbreviation, name: String } diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift index ad8a8027c..8194cd93a 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/AddressSpecification/AddressSpecificationProvider.swift @@ -8,7 +8,7 @@ import Foundation @_spi(PO) import ProcessOut -final class AddressSpecificationProvider { +final class AddressSpecificationProvider: Sendable { static let shared = AddressSpecificationProvider() @@ -20,9 +20,9 @@ final class AddressSpecificationProvider { // MARK: - AddressSpecificationProvider /// Returns supported country codes. - private(set) lazy var countryCodes: [String] = { + var countryCodes: [String] { Array(loadSpecifications().keys) - }() + } /// Returns address spec for given country code or default if country is unknown. func specification(for countryCode: String) -> AddressSpecification { @@ -37,8 +37,7 @@ final class AddressSpecificationProvider { // MARK: - Private Properties - @POUnfairlyLocked - private var specifications: [String: AddressSpecification]? + private let specifications = POUnfairlyLocked<[String: AddressSpecification]?>(wrappedValue: nil) // MARK: - Private Methods @@ -48,7 +47,7 @@ final class AddressSpecificationProvider { @discardableResult private func loadSpecifications() -> [String: AddressSpecification] { - $specifications.withLock { specifications in + specifications.withLock { specifications in if let specifications { return specifications } diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift index e72b28ee6..8ab4eea6b 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/CardScheme/CardSchemeProvider.swift @@ -9,15 +9,15 @@ import Foundation @_spi(PO) import ProcessOut // todo(andrii-vysotskyi): support more schemes -final class CardSchemeProvider { +final class CardSchemeProvider: Sendable { - struct Issuer { + struct Issuer: Sendable { let scheme: POCardScheme let numbers: IssuerNumbers let length: Int } - enum IssuerNumbers { + enum IssuerNumbers: Sendable { case range(ClosedRange), exact(Int), set(Set) } diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift index dadd4c3f8..365bba15e 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/CardSchemeImage/CardSchemeImageProvider.swift @@ -8,7 +8,7 @@ import SwiftUI import ProcessOut -final class CardSchemeImageProvider { +final class CardSchemeImageProvider: Sendable { static let shared = CardSchemeImageProvider() diff --git a/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift b/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift index b57e46664..4216b3e76 100644 --- a/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift +++ b/Sources/ProcessOutUI/Sources/Core/Providers/PresentingViewController/PresentingViewControllerProvider.swift @@ -10,6 +10,7 @@ import UIKit enum PresentingViewControllerProvider { /// Attempts to find view controller that can modally present other view controller. + @MainActor static func find() -> UIViewController? { let rootViewController = UIApplication.shared .connectedScenes diff --git a/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift b/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift index 502a3e84b..51a4ea39f 100644 --- a/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Core/Utils/POConfirmationDialogConfiguration.swift @@ -6,7 +6,7 @@ // /// Confirmation dialog configuration. -public struct POConfirmationDialogConfiguration { +public struct POConfirmationDialogConfiguration: Sendable { /// Confirmation title. Use empty string to hide title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Core/ViewModel/AnyViewModel.swift b/Sources/ProcessOutUI/Sources/Core/ViewModel/AnyViewModel.swift index 5b514ee4f..e5e360eba 100644 --- a/Sources/ProcessOutUI/Sources/Core/ViewModel/AnyViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Core/ViewModel/AnyViewModel.swift @@ -23,13 +23,17 @@ final class AnyViewModel: ViewModel { // MARK: - CardTokenizationViewModel + var state: State { + get { base.state } + set { base.state = newValue } + } + func start() { base.start() } - var state: State { - get { base.state } - set { base.state = newValue } + func stop() { + base.stop() } // MARK: - Private Properties @@ -46,13 +50,17 @@ private class ViewModelBox: AnyViewModelBase where T: ViewModel { let base: T + override var state: T.State { + get { base.state } + set { base.state = newValue } + } + override func start() { base.start() } - override var state: T.State { - get { base.state } - set { base.state = newValue } + override func stop() { + base.stop() } } @@ -60,13 +68,17 @@ private class ViewModelBox: AnyViewModelBase where T: ViewModel { private class AnyViewModelBase: ViewModel { + var state: State { + get { fatalError("Not implemented") } + set { fatalError("Not implemented") } + } + func start() { fatalError("Not implemented") } - var state: State { - get { fatalError("Not implemented") } - set { fatalError("Not implemented") } + func stop() { + fatalError("Not implemented") } } diff --git a/Sources/ProcessOutUI/Sources/Core/ViewModel/ViewModel.swift b/Sources/ProcessOutUI/Sources/Core/ViewModel/ViewModel.swift index 313177198..b890ca334 100644 --- a/Sources/ProcessOutUI/Sources/Core/ViewModel/ViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Core/ViewModel/ViewModel.swift @@ -7,6 +7,7 @@ import Combine +@MainActor protocol ViewModel: ObservableObject { associatedtype State @@ -16,4 +17,7 @@ protocol ViewModel: ObservableObject { /// Starts view model. func start() + + /// Stops view model. + func stop() } diff --git a/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/PO3DSRedirectController.swift b/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/PO3DSRedirectController.swift index 35d064012..61538fbe1 100644 --- a/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/PO3DSRedirectController.swift +++ b/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/PO3DSRedirectController.swift @@ -15,7 +15,8 @@ import ProcessOut /// class initialized with 3DSRedirect, but it does not depend on the UIKit framework. This means that /// the controller can be used in places where a view controller cannot (for example, in SwiftUI applications). @available(*, deprecated, message: "Use POWebAuthenticationSession instead.") -public final class PO3DSRedirectController { +@MainActor +public final class PO3DSRedirectController: Sendable { /// - Parameters: /// - redirect: redirect to handle. @@ -79,7 +80,7 @@ public final class PO3DSRedirectController { } /// Completion to invoke when redirect handling ends. - public var completion: ((Result) -> Void)? + public var completion: (@Sendable (Result) -> Void)? /// The preferred color to tint the background of the navigation bar and toolbar. public var preferredBarTintColor: UIColor? @@ -90,7 +91,7 @@ public final class PO3DSRedirectController { // MARK: - Private Nested Types private enum AssociatedKeys { - static var redirectController: UInt8 = 0 + nonisolated(unsafe) static var redirectController: UInt8 = 0 } // MARK: - Private Properties diff --git a/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/POWebAuthenticationSession+3DSRedirect.swift b/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/POWebAuthenticationSession+3DSRedirect.swift index 5127ff5f4..2542194e6 100644 --- a/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/POWebAuthenticationSession+3DSRedirect.swift +++ b/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/POWebAuthenticationSession+3DSRedirect.swift @@ -19,7 +19,7 @@ extension POWebAuthenticationSession { public convenience init( redirect: PO3DSRedirect, returnUrl: URL, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let completionBox: Completion = { result in completion(result.map(Self.token(with:))) @@ -30,7 +30,7 @@ extension POWebAuthenticationSession { // MARK: - Private Methods - private static func token(with url: URL) -> String { + private static nonisolated func token(with url: URL) -> String { let components = URLComponents(url: url, resolvingAgainstBaseURL: true) return components?.queryItems?.first { $0.name == "token" }?.value ?? "" } diff --git a/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/SFSafariViewController+3DSRedirect.swift b/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/SFSafariViewController+3DSRedirect.swift index b1ecbd305..1d3d81607 100644 --- a/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/SFSafariViewController+3DSRedirect.swift +++ b/Sources/ProcessOutUI/Sources/Modules/3DSRedirect/SFSafariViewController+3DSRedirect.swift @@ -24,7 +24,7 @@ extension SFSafariViewController { redirect: PO3DSRedirect, returnUrl: URL, safariConfiguration: SFSafariViewController.Configuration = .init(), - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { self.init(url: redirect.url, configuration: safariConfiguration) let api: ProcessOut = ProcessOut.shared // swiftlint:disable:this redundant_type_annotation @@ -34,7 +34,7 @@ extension SFSafariViewController { eventEmitter: api.eventEmitter, logger: api.logger, completion: { result in - completion(result.map(Self.token(with:))) + completion(result.map(Self.token)) } ) setViewModel(viewModel) @@ -43,7 +43,7 @@ extension SFSafariViewController { // MARK: - Private Methods - private static func token(with url: URL) -> String { + private static nonisolated func token(with url: URL) -> String { let components = URLComponents(url: url, resolvingAgainstBaseURL: true) return components?.queryItems?.first { $0.name == "token" }?.value ?? "" } diff --git a/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/POWebAuthenticationSession+AlternativePayment.swift b/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/POWebAuthenticationSession+AlternativePayment.swift index a094a3dda..8e3127e70 100644 --- a/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/POWebAuthenticationSession+AlternativePayment.swift +++ b/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/POWebAuthenticationSession+AlternativePayment.swift @@ -19,7 +19,7 @@ extension POWebAuthenticationSession { public convenience init( request: POAlternativePaymentMethodRequest, returnUrl: URL, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let url = ProcessOut.shared.alternativePaymentMethods.alternativePaymentMethodUrl(request: request) self.init(alternativePaymentMethodUrl: url, returnUrl: returnUrl, completion: completion) @@ -35,17 +35,17 @@ extension POWebAuthenticationSession { public convenience init( alternativePaymentMethodUrl url: URL, returnUrl: URL, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let completionBox: Completion = { result in - completion(result.flatMap(Self.response(with:))) + completion(result.flatMap(Self.response)) } self.init(url: url, callback: .customScheme(returnUrl.scheme ?? ""), completion: completionBox) } // MARK: - Private Methods - private static func response(with url: URL) -> Result { + private static nonisolated func response(with url: URL) -> Result { let result = Result { try ProcessOut.shared.alternativePaymentMethods.alternativePaymentMethodResponse(url: url) } diff --git a/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/SFSafariViewController+AlternativePayment.swift b/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/SFSafariViewController+AlternativePayment.swift index 02aab1d86..fd53eb53d 100644 --- a/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/SFSafariViewController+AlternativePayment.swift +++ b/Sources/ProcessOutUI/Sources/Modules/AlternativePayment/SFSafariViewController+AlternativePayment.swift @@ -24,11 +24,15 @@ extension SFSafariViewController { request: POAlternativePaymentMethodRequest, returnUrl: URL, safariConfiguration: SFSafariViewController.Configuration = Configuration(), - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { let url = ProcessOut.shared.alternativePaymentMethods.alternativePaymentMethodUrl(request: request) - self.init(url: url, configuration: safariConfiguration) - commonInit(returnUrl: returnUrl, completion: completion) + self.init( + alternativePaymentMethodUrl: url, + returnUrl: returnUrl, + safariConfiguration: safariConfiguration, + completion: completion + ) } /// Creates view controller that is capable of handling Alternative Payment. @@ -46,33 +50,24 @@ extension SFSafariViewController { alternativePaymentMethodUrl url: URL, returnUrl: URL, safariConfiguration: SFSafariViewController.Configuration = Configuration(), - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { self.init(url: url, configuration: safariConfiguration) - commonInit(returnUrl: returnUrl, completion: completion) - } - - // MARK: - Private Nested Types - - private typealias Completion = (Result) -> Void - - // MARK: - Private Methods - - private func commonInit(returnUrl: URL, completion: @escaping Completion) { - let api: ProcessOut = ProcessOut.shared // swiftlint:disable:this redundant_type_annotation let viewModel = DefaultSafariViewModel( callback: .customScheme(returnUrl.scheme ?? ""), - eventEmitter: api.eventEmitter, - logger: api.logger, + eventEmitter: ProcessOut.shared.eventEmitter, + logger: ProcessOut.shared.logger, completion: { result in - completion(result.flatMap(Self.response(with:))) + completion(result.flatMap(Self.response)) } ) self.setViewModel(viewModel) viewModel.start() } - private static func response(with url: URL) -> Result { + // MARK: - Private Methods + + private nonisolated static func response(with url: URL) -> Result { let result = Result { try ProcessOut.shared.alternativePaymentMethods.alternativePaymentMethodResponse(url: url) } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift index 822e7d611..31e8c4cf0 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POBillingAddressConfiguration.swift @@ -8,7 +8,7 @@ import ProcessOut /// Billing address collection configuration. -public struct POBillingAddressConfiguration { +public struct POBillingAddressConfiguration: Sendable { @available(*, deprecated, message: "Use POBillingAddressCollectionMode directly.") public typealias CollectionMode = POBillingAddressCollectionMode diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift index cffb610e8..f2035fc30 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Configuration/POCardTokenizationConfiguration.swift @@ -10,7 +10,7 @@ import ProcessOut /// A configuration object that defines a card tokenization module behaves. /// Use `nil` as a value for a nullable property to indicate that default value should be used. -public struct POCardTokenizationConfiguration { +public struct POCardTokenizationConfiguration: Sendable { /// Custom title. Use empty string to hide title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift index 1cad02ce8..e4cec010f 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationDelegate.swift @@ -8,9 +8,10 @@ import ProcessOut /// Card tokenization module delegate definition. -public protocol POCardTokenizationDelegate: AnyObject { +public protocol POCardTokenizationDelegate: AnyObject, Sendable { /// Invoked when module emits event. + @MainActor func cardTokenizationDidEmitEvent(_ event: POCardTokenizationEvent) /// Allows delegate to additionally process tokenized card before ending module's lifecycle. For example @@ -22,15 +23,18 @@ public protocol POCardTokenizationDelegate: AnyObject { /// Allows to choose preferred scheme that will be selected by default based on issuer information. Default /// implementation returns primary scheme. + @MainActor func preferredScheme(issuerInformation: POCardIssuerInformation) -> String? /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. + @MainActor func shouldContinueTokenization(after failure: POFailure) -> Bool } extension POCardTokenizationDelegate { + @MainActor public func cardTokenizationDidEmitEvent(_ event: POCardTokenizationEvent) { // Ignored } @@ -39,10 +43,12 @@ extension POCardTokenizationDelegate { // Ignored } + @MainActor public func preferredScheme(issuerInformation: POCardIssuerInformation) -> String? { issuerInformation.scheme } + @MainActor public func shouldContinueTokenization(after failure: POFailure) -> Bool { true } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift index d544d05f9..421dc74cb 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Delegate/POCardTokenizationEvent.swift @@ -8,7 +8,7 @@ import ProcessOut /// Describes events that could happen during card tokenization lifecycle. -public enum POCardTokenizationEvent { +public enum POCardTokenizationEvent: Sendable { /// Initial event that is sent prior any other event. case willStart diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractor.swift index 8d31a11f2..77bfe744c 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractor.swift @@ -7,6 +7,7 @@ import ProcessOut +@MainActor protocol CardTokenizationInteractor: Interactor { /// Delegate. @@ -23,7 +24,4 @@ protocol CardTokenizationInteractor: Interactor /// Starts card tokenization. func tokenize() - - /// Cancells tokenization if possible. - func cancel() } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractorState.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractorState.swift index 5e3d5a707..349886426 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractorState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/CardTokenizationInteractorState.swift @@ -133,3 +133,6 @@ extension CardTokenizationInteractorState.Started { return parameters.allSatisfy(\.isValid) && address.areParametersValid } } + +@available(*, unavailable) +extension CardTokenizationInteractorState: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/DefaultCardTokenizationInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/DefaultCardTokenizationInteractor.swift index e1096bd0c..9e50d84ad 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/DefaultCardTokenizationInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Interactor/DefaultCardTokenizationInteractor.swift @@ -124,7 +124,7 @@ final class DefaultCardTokenizationInteractor: preferredScheme: startedState.preferredScheme?.rawValue, metadata: configuration.metadata ) - Task { @MainActor in + Task { do { let card = try await cardsService.tokenize(request: request) logger.debug("Did tokenize card: \(String(describing: card))") @@ -258,20 +258,20 @@ final class DefaultCardTokenizationInteractor: return } logger.debug("Will fetch issuer information", attributes: ["IIN": iin]) - issuerInformationCancellable = cardsService.issuerInformation(iin: iin) { [logger, weak self] result in - guard let self, case .started(var startedState) = self.state else { - return - } - switch result { - case .failure(let failure) where failure.code == .cancelled: - break - case .failure(let failure): - // Inability to select co-scheme is considered minor issue and we still want - // users to be able to continue tokenization. So errors are silently ignored. - logger.info("Did fail to fetch issuer information: \(failure)", attributes: ["IIN": iin]) - case .success(let issuerInformation): + issuerInformationCancellable = Task { + do { + let issuerInformation = try await cardsService.issuerInformation(iin: iin) + guard case .started(var startedState) = self.state else { + return + } update(startedState: &startedState, issuerInformation: issuerInformation, resolvePreferredScheme: true) self.setStateUnchecked(.started(startedState)) + } catch let failure as POFailure where failure.code == .cancelled { + // Ignored + } catch { + // Inability to select co-scheme is considered minor issue and we still want + // users to be able to continue tokenization. So errors are silently ignored. + logger.info("Did fail to fetch issuer information: \(error)", attributes: ["IIN": iin]) } } } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift index 91ab8f016..e5b127370 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/POCardTokenizationStyle.swift @@ -13,6 +13,7 @@ import SwiftUI /// For more information about styling specific components, see /// [the dedicated documentation.](https://swiftpackageindex.com/processout/processout-ios/documentation/processoutcoreui) @available(iOS 14, *) +@MainActor public struct POCardTokenizationStyle { /// Title style. @@ -64,16 +65,14 @@ public struct POCardTokenizationStyle { extension POCardTokenizationStyle { /// Default card tokenization style. - public static var `default`: POCardTokenizationStyle { - POCardTokenizationStyle( - title: POTextStyle(color: Color(poResource: .Text.primary), typography: .title), - sectionTitle: POTextStyle(color: Color(poResource: .Text.primary), typography: .label1), - input: .medium, - radioButton: .radio, - errorDescription: POTextStyle(color: Color(poResource: .Text.error), typography: .label2), - backgroundColor: Color(poResource: .Surface.default), - actionsContainer: .default, - separatorColor: Color(poResource: .Border.subtle) - ) - } + public static let `default` = POCardTokenizationStyle( + title: POTextStyle(color: Color(poResource: .Text.primary), typography: .title), + sectionTitle: POTextStyle(color: Color(poResource: .Text.primary), typography: .label1), + input: .medium, + radioButton: .radio, + errorDescription: POTextStyle(color: Color(poResource: .Text.error), typography: .label2), + backgroundColor: Color(poResource: .Surface.default), + actionsContainer: .default, + separatorColor: Color(poResource: .Border.subtle) + ) } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift index 22808e69a..4c541179f 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/Style/View+CardTokenizationStyle.swift @@ -26,7 +26,8 @@ extension EnvironmentValues { // MARK: - Private Nested Types - private struct Key: EnvironmentKey { + @MainActor + private struct Key: @preconcurrency EnvironmentKey { static let defaultValue = POCardTokenizationStyle.default } } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/View/POCardTokenizationView.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/View/POCardTokenizationView.swift index bbb4e5156..c06cbf71a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/View/POCardTokenizationView.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/View/POCardTokenizationView.swift @@ -31,6 +31,7 @@ public struct POCardTokenizationView: View { style.backgroundColor.ignoresSafeArea() } .onAppear(perform: viewModel.start) + .onDisappear(perform: viewModel.stop) } // MARK: - Private Properties diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/CardTokenizationViewModelState.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/CardTokenizationViewModelState.swift index 3aa40d9cf..c1fbc840b 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/CardTokenizationViewModelState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/CardTokenizationViewModelState.swift @@ -117,3 +117,6 @@ extension CardTokenizationViewModelState.Item: Identifiable { } } } + +@available(*, unavailable) +extension CardTokenizationViewModelState: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/DefaultCardTokenizationViewModel.swift b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/DefaultCardTokenizationViewModel.swift index ca2bc6435..d723a915b 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/DefaultCardTokenizationViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardTokenization/ViewModel/DefaultCardTokenizationViewModel.swift @@ -28,6 +28,10 @@ final class DefaultCardTokenizationViewModel: ViewModel { interactor.start() } + func stop() { + interactor.cancel() + } + // MARK: - Private Nested Types private typealias InteractorState = CardTokenizationInteractorState diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift index 741b5b5b7..576450318 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Configuration/POCardUpdateConfiguration.swift @@ -7,7 +7,7 @@ /// A configuration object that defines how a card update module behaves. /// Use `nil` as a value for a nullable property to indicate that default value should be used. -public struct POCardUpdateConfiguration { +public struct POCardUpdateConfiguration: Sendable { /// Card id that needs to be updated. public let cardId: String diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift index 4bd63feba..d2968aeec 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateDelegate.swift @@ -8,29 +8,33 @@ import ProcessOut /// Card update module delegate definition. -public protocol POCardUpdateDelegate: AnyObject { - - /// Invoked when module emits event. - func cardUpdateDidEmitEvent(_ event: POCardUpdateEvent) +public protocol POCardUpdateDelegate: AnyObject, Sendable { /// Asks delegate to resolve card information based on card id. func cardInformation(cardId: String) async -> POCardUpdateInformation? + /// Invoked when module emits event. + @MainActor + func cardUpdateDidEmitEvent(_ event: POCardUpdateEvent) + /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. + @MainActor func shouldContinueUpdate(after failure: POFailure) -> Bool } extension POCardUpdateDelegate { - public func cardUpdateDidEmitEvent(_ event: POCardUpdateEvent) { - // Ignored - } - public func cardInformation(cardId: String) async -> POCardUpdateInformation? { nil } + @MainActor + public func cardUpdateDidEmitEvent(_ event: POCardUpdateEvent) { + // Ignored + } + + @MainActor public func shouldContinueUpdate(after failure: POFailure) -> Bool { true } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift index 008e8a205..07246e657 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateEvent.swift @@ -6,7 +6,7 @@ // /// Describes events that could happen during card update lifecycle. -public enum POCardUpdateEvent { +public enum POCardUpdateEvent: Sendable { /// Initial event that is sent prior any other event. case willStart diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift index 7284ca7ff..c52b52dd3 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Delegate/POCardUpdateInformation.swift @@ -8,7 +8,7 @@ import ProcessOut /// Short card information necessary for CVC update. -public struct POCardUpdateInformation { +public struct POCardUpdateInformation: Sendable { /// Masked card number displayed to user as is if set. public let maskedNumber: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractor.swift index 7c08d3256..4b6d512d7 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractor.swift @@ -7,6 +7,7 @@ import ProcessOut +@MainActor protocol CardUpdateInteractor: Interactor { /// Updates CVC value. @@ -17,7 +18,4 @@ protocol CardUpdateInteractor: Interactor { /// Attempts to update card with new CVC. func submit() - - /// Cancells update if possible. - func cancel() } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractorState.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractorState.swift index 9f0b8e974..9292873b8 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractorState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/CardUpdateInteractorState.swift @@ -52,3 +52,6 @@ enum CardUpdateInteractorState: Equatable { /// Card update has finished. This is a sink state. case completed } + +@available(*, unavailable) +extension CardUpdateInteractorState: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/DefaultCardUpdateInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/DefaultCardUpdateInteractor.swift index 6674cb770..9d204070b 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/DefaultCardUpdateInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/Interactor/DefaultCardUpdateInteractor.swift @@ -77,7 +77,6 @@ final class DefaultCardUpdateInteractor: BaseInteractor) -> Void ) { let viewModel = { - var logger = ProcessOut.shared.logger + var logger: POLogger = ProcessOut.shared.logger logger[attributeKey: .cardId] = configuration.cardId let interactor = DefaultCardUpdateInteractor( cardsService: ProcessOut.shared.cards, diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModel.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModel.swift index fbc555582..524677a3e 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModel.swift @@ -8,6 +8,7 @@ import Combine @_spi(PO) import ProcessOutCoreUI +@MainActor protocol CardUpdateViewModel: ObservableObject { /// Screen title. diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelItem.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelItem.swift index 5c85830d1..e4bc913de 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelItem.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelItem.swift @@ -69,3 +69,6 @@ extension CardUpdateViewModelItem: Identifiable { static let progressId = UUID().uuidString } } + +@available(*, unavailable) +extension CardUpdateViewModelItem: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelSection.swift b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelSection.swift index 4c0a9589a..8ad176dba 100644 --- a/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelSection.swift +++ b/Sources/ProcessOutUI/Sources/Modules/CardUpdate/ViewModel/CardUpdateViewModelSection.swift @@ -18,3 +18,6 @@ struct CardUpdateViewModelSection: Identifiable { /// Section items. let items: [CardUpdateViewModelItem] } + +@available(*, unavailable) +extension CardUpdateViewModelSection: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift index 2c0ea3e7b..5d92b2d49 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutAlternativePaymentConfiguration.swift @@ -9,9 +9,9 @@ import Foundation /// Alternative payment specific dynamic checkout configuration. @_spi(PO) -public struct PODynamicCheckoutAlternativePaymentConfiguration { +public struct PODynamicCheckoutAlternativePaymentConfiguration: Sendable { - public struct PaymentConfirmation { + public struct PaymentConfirmation: Sendable { /// Amount of time (in seconds) that module is allowed to wait before receiving final payment confirmation. /// Default timeout is 3 minutes while maximum value is 15 minutes. @@ -36,7 +36,7 @@ public struct PODynamicCheckoutAlternativePaymentConfiguration { } } - public struct CancelButton { + public struct CancelButton: Sendable { /// Cancel button title. Use `nil` for default title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift index 989df6a93..4cb624ac7 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutCardConfiguration.swift @@ -9,10 +9,10 @@ import ProcessOut /// Card specific dynamic checkout configuration. @_spi(PO) -public struct PODynamicCheckoutCardConfiguration { +public struct PODynamicCheckoutCardConfiguration: Sendable { /// Billing address collection configuration. - public struct BillingAddress { + public struct BillingAddress: Sendable { /// Default address information. public let defaultAddress: POContact? diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift index 422b97c0c..676e06623 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Configuration/PODynamicCheckoutConfiguration.swift @@ -10,9 +10,9 @@ import ProcessOut /// Dynamic checkout configuration. @_spi(PO) -public struct PODynamicCheckoutConfiguration { +public struct PODynamicCheckoutConfiguration: Sendable { - public struct PaymentSuccess { + public struct PaymentSuccess: Sendable { /// Custom success message to display user when payment completes. public let message: String? @@ -27,7 +27,7 @@ public struct PODynamicCheckoutConfiguration { } } - public struct CancelButton { + public struct CancelButton: Sendable { /// Cancel button title. public let title: String? diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift index fc77c3fb9..0109a026f 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutDelegate.swift @@ -10,10 +10,11 @@ import ProcessOut /// Dynamic checkout module delegate. @_spi(PO) -public protocol PODynamicCheckoutDelegate: AnyObject { +public protocol PODynamicCheckoutDelegate: AnyObject, Sendable { /// Invoked when module emits dynamic checkout event. /// - NOTE: default implementation does nothing. + @MainActor func dynamicCheckout(didEmitEvent event: PODynamicCheckoutEvent) /// Called when dynamic checkout is about to authorize invoice with given request. @@ -26,6 +27,7 @@ public protocol PODynamicCheckoutDelegate: AnyObject { /// Asks delegate whether user should be allowed to continue after failure or module should complete. /// Default implementation returns `true`. + @MainActor func dynamicCheckout(shouldContinueAfter failure: POFailure) -> Bool /// Your implementation could return a request that will be used to fetch new invoice to replace existing one @@ -35,15 +37,18 @@ public protocol PODynamicCheckoutDelegate: AnyObject { // MARK: - Card Payment /// Invoked when module emits event. + @MainActor func dynamicCheckout(didEmitCardTokenizationEvent event: POCardTokenizationEvent) /// Allows to choose preferred scheme that will be selected by default based on issuer information. Default /// implementation returns primary scheme. + @MainActor func dynamicCheckout(preferredSchemeFor issuerInformation: POCardIssuerInformation) -> String? // MARK: - Alternative Payment /// Invoked when module emits alternative payment event. + @MainActor func dynamicCheckout(didEmitAlternativePaymentEvent event: PONativeAlternativePaymentEvent) /// Method provides an ability to supply default values for given parameters. @@ -57,15 +62,18 @@ public protocol PODynamicCheckoutDelegate: AnyObject { // MARK: - Pass Kit /// Gives implementation an opportunity to modify payment request before it is used to authorize invoice. + @MainActor func dynamicCheckout(willAuthorizeInvoiceWith request: PKPaymentRequest) async } extension PODynamicCheckoutDelegate { + @MainActor public func dynamicCheckout(didEmitEvent event: PODynamicCheckoutEvent) { // Ignored } + @MainActor public func dynamicCheckout(shouldContinueAfter failure: POFailure) -> Bool { true } @@ -74,14 +82,17 @@ extension PODynamicCheckoutDelegate { nil } + @MainActor public func dynamicCheckout(didEmitCardTokenizationEvent event: POCardTokenizationEvent) { // Ignored } + @MainActor public func dynamicCheckout(preferredSchemeFor issuerInformation: POCardIssuerInformation) -> String? { issuerInformation.scheme } + @MainActor public func dynamicCheckout(didEmitAlternativePaymentEvent event: PONativeAlternativePaymentEvent) { // Ignored } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift index 7192777eb..3403212e7 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Delegate/PODynamicCheckoutEvent.swift @@ -9,7 +9,7 @@ import ProcessOut /// Events emitted by dynamic checkout module during its lifecycle. @_spi(PO) -public enum PODynamicCheckoutEvent { +public enum PODynamicCheckoutEvent: Sendable { /// Initial event that is sent prior any other event. case willStart diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/ChildProvider/DynamicCheckoutInteractorChildProvider.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/ChildProvider/DynamicCheckoutInteractorChildProvider.swift index 15a470d1b..16922623e 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/ChildProvider/DynamicCheckoutInteractorChildProvider.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/ChildProvider/DynamicCheckoutInteractorChildProvider.swift @@ -9,6 +9,7 @@ /// - NOTE: Your implementation should expect that instances created /// by provider are going to be different every time you call a method. +@MainActor protocol DynamicCheckoutInteractorChildProvider { /// Creates and returns card tokenization interactor. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutDefaultInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutDefaultInteractor.swift index 5a46f8c04..28f58b66a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutDefaultInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutDefaultInteractor.swift @@ -148,7 +148,6 @@ final class DynamicCheckoutDefaultInteractor: // MARK: - Starting State - @MainActor private func continueStartUnchecked() async { do { let invoice = try await invoicesService.invoice(request: configuration.invoiceRequest) @@ -351,7 +350,7 @@ final class DynamicCheckoutDefaultInteractor: shouldInvalidateInvoice: true ) state = .paymentProcessing(paymentProcessingState) - Task { @MainActor in + Task { do { try await passKitPaymentSession.start(invoiceId: startedState.invoice.id, request: request) setSuccessState() @@ -430,7 +429,7 @@ final class DynamicCheckoutDefaultInteractor: shouldInvalidateInvoice: true ) state = .paymentProcessing(paymentProcessingState) - Task { @MainActor in + Task { do { _ = try await alternativePaymentSession.start(url: method.configuration.redirectUrl) setSuccessState() @@ -662,7 +661,7 @@ final class DynamicCheckoutDefaultInteractor: } state = .success send(event: .didCompletePayment) - Task { @MainActor in + Task { try? await Task.sleep(seconds: configuration.paymentSuccess?.duration ?? 0) completion(.success(())) } @@ -753,7 +752,8 @@ extension DynamicCheckoutDefaultInteractor: PONativeAlternativePaymentDelegate { } func nativeAlternativePaymentMethodDefaultValues( - for parameters: [PONativeAlternativePaymentMethodParameter], completion: @escaping ([String: String]) -> Void + for parameters: [PONativeAlternativePaymentMethodParameter], + completion: @escaping @Sendable ([String: String]) -> Void ) { Task { @MainActor in let values = await delegate?.dynamicCheckout(alternativePaymentDefaultsFor: parameters) ?? [:] diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutInteractor.swift index c55311625..7f8044cd9 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Interactor/DynamicCheckout/DynamicCheckoutInteractor.swift @@ -5,6 +5,7 @@ // Created by Andrii Vysotskyi on 05.03.2024. // +@MainActor protocol DynamicCheckoutInteractor: Interactor { /// Configuration. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentDefaultSession.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentDefaultSession.swift index 198b099b8..ff1f34a45 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentDefaultSession.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentDefaultSession.swift @@ -8,7 +8,6 @@ import Foundation import ProcessOut -@MainActor final class DynamicCheckoutAlternativePaymentDefaultSession: DynamicCheckoutAlternativePaymentSession { init(configuration: PODynamicCheckoutAlternativePaymentConfiguration) { @@ -19,15 +18,18 @@ final class DynamicCheckoutAlternativePaymentDefaultSession: DynamicCheckoutAlte guard let returnUrl = configuration.returnUrl else { throw POFailure(message: "Return URL must be set.", code: .generic(.mobile)) } - // swiftlint:disable:next implicitly_unwrapped_optional - var continuation: UnsafeContinuation! - let session = POWebAuthenticationSession(alternativePaymentMethodUrl: url, returnUrl: returnUrl) { result in - continuation.resume(with: result) + return try await withCheckedThrowingContinuation { continuation in + let session = POWebAuthenticationSession(alternativePaymentMethodUrl: url, returnUrl: returnUrl) { result in + continuation.resume(with: result) + } + Task { + guard await !session.start() else { + return + } + let failure = POFailure(message: "Unable to start alternative payment.", code: .generic(.mobile)) + continuation.resume(throwing: failure) + } } - guard await session.start() else { - throw POFailure(message: "Unable to start alternative payment.", code: .generic(.mobile)) - } - return try await withUnsafeThrowingContinuation { continuation = $0 } } // MARK: - Private Properties diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentSession.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentSession.swift index 2f5e32ec2..82ae0e29b 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentSession.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/AlternativePayment/DynamicCheckoutAlternativePaymentSession.swift @@ -8,6 +8,7 @@ import Foundation import ProcessOut +@MainActor protocol DynamicCheckoutAlternativePaymentSession { /// Starts alternative payment. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentDefaultSession.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentDefaultSession.swift index 3907e7d18..d3dafad82 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentDefaultSession.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentDefaultSession.swift @@ -9,7 +9,6 @@ import Foundation import PassKit import ProcessOut -@MainActor final class DynamicCheckoutPassKitPaymentDefaultSession: DynamicCheckoutPassKitPaymentSession { init(delegate: PODynamicCheckoutDelegate?, invoicesService: POInvoicesService) { diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentSession.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentSession.swift index 25a70418d..7528f2a52 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentSession.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Sessions/PassKit/DynamicCheckoutPassKitPaymentSession.swift @@ -7,6 +7,7 @@ import PassKit +@MainActor protocol DynamicCheckoutPassKitPaymentSession { /// Boolean value indicating whether PassKit payments are supported. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift index 8bf8f6ca1..8e43014c8 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/PODynamicCheckoutStyle.swift @@ -15,8 +15,10 @@ import SwiftUI /// [the dedicated documentation.](https://swiftpackageindex.com/processout/processout-ios/documentation/processoutcoreui) @available(iOS 14, *) @_spi(PO) +@MainActor public struct PODynamicCheckoutStyle { + @MainActor public struct RegularPaymentMethod { /// Payment method title. @@ -35,6 +37,7 @@ public struct PODynamicCheckoutStyle { } } + @MainActor public struct PaymentSuccess { /// Success message style. diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift index bd5513eb7..b20806db6 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/Style/View+DynamicCheckoutStyle.swift @@ -27,7 +27,8 @@ extension EnvironmentValues { // MARK: - Private Nested Types - private struct Key: EnvironmentKey { + @MainActor + private struct Key: @preconcurrency EnvironmentKey { static let defaultValue = PODynamicCheckoutStyle.default } } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView+Init.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView+Init.swift index 8523f90f8..a20dcc315 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView+Init.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView+Init.swift @@ -21,7 +21,7 @@ extension PODynamicCheckoutView { completion: @escaping (Result) -> Void ) { let viewModel = { - let logger = ProcessOut.shared.logger + let logger: POLogger = ProcessOut.shared.logger let interactor = DynamicCheckoutDefaultInteractor( configuration: configuration, delegate: delegate, diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView.swift index 534ab7236..c9e014c33 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/View/PODynamicCheckoutView.swift @@ -41,6 +41,7 @@ public struct PODynamicCheckoutView: View { } .backport.geometryGroup() .onAppear(perform: viewModel.start) + .onDisappear(perform: viewModel.stop) .poConfirmationDialog(item: $viewModel.state.confirmationDialog) } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift index cb4b4fd2e..66ce2722a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DefaultDynamicCheckoutViewModel.swift @@ -28,6 +28,10 @@ final class DefaultDynamicCheckoutViewModel: ViewModel { interactor.start() } + func stop() { + interactor.cancel() + } + // MARK: - Private Nested Types private enum ButtonId { diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelItem.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelItem.swift index 10480cdf6..f477a9542 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelItem.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelItem.swift @@ -165,3 +165,6 @@ extension DynamicCheckoutViewModelItem: Identifiable, AnimationIdentityProvider static let progressId = UUID().uuidString } } + +@available(*, unavailable) +extension DynamicCheckoutViewModelItem: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift index 7815bc0bd..87bc98d49 100644 --- a/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/DynamicCheckout/ViewModel/DynamicCheckoutViewModelState.swift @@ -45,7 +45,9 @@ extension DynamicCheckoutViewModelState: AnimationIdentityProvider { [sections.map(\.animationIdentity), actions.map(\.id)] } - static let idle = DynamicCheckoutViewModelState(sections: [], actions: [], isCompleted: false) + static var idle: Self { + Self(sections: [], actions: [], isCompleted: false) + } } extension DynamicCheckoutViewModelState.Section: AnimationIdentityProvider { @@ -54,3 +56,6 @@ extension DynamicCheckoutViewModelState.Section: AnimationIdentityProvider { [id, items.map(\.animationIdentity), AnyHashable(isTight), AnyHashable(areBezelsVisible)] } } + +@available(*, unavailable) +extension DynamicCheckoutViewModelState: Sendable { } diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift index 13d663540..2634523e0 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentDefaultInteractor.swift @@ -121,7 +121,6 @@ final class NativeAlternativePaymentDefaultInteractor: // MARK: - Starting State - @MainActor private func continueStartUnchecked() async { let details: PONativeAlternativePaymentMethodTransactionDetails do { @@ -164,7 +163,6 @@ final class NativeAlternativePaymentDefaultInteractor: // MARK: - Submission State - @MainActor private func continueSubmissionUnchecked( startedState: NativeAlternativePaymentInteractorState.Started, values: [String: String] ) async { @@ -203,7 +201,6 @@ final class NativeAlternativePaymentDefaultInteractor: // MARK: - Awaiting Capture State - @MainActor private func setAwaitingCaptureStateUnchecked( gateway: PONativeAlternativePaymentMethodTransactionDetails.Gateway, parameterValues: PONativeAlternativePaymentMethodParameterValues? @@ -250,7 +247,8 @@ final class NativeAlternativePaymentDefaultInteractor: guard let timeInterval = configuration.paymentConfirmation.showProgressIndicatorAfter else { return } - Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: false) { [weak self] _ in + Task { [weak self] in + try? await Task.sleep(seconds: timeInterval) guard let self, case .awaitingCapture(var awaitingCaptureState) = self.state else { return } @@ -261,7 +259,6 @@ final class NativeAlternativePaymentDefaultInteractor: // MARK: - Captured State - @MainActor private func setCapturedStateUnchecked( gateway: PONativeAlternativePaymentMethodTransactionDetails.Gateway, parameterValues: PONativeAlternativePaymentMethodParameterValues? @@ -330,7 +327,6 @@ final class NativeAlternativePaymentDefaultInteractor: logger.debug("One or more parameters are not valid: \(invalidFields), waiting for parameters to update") } - @MainActor private func restoreStartedStateAfterSubmission( nativeApm: PONativeAlternativePaymentMethodResponse.NativeApm ) async { @@ -370,7 +366,6 @@ final class NativeAlternativePaymentDefaultInteractor: // MARK: - Cancellation Availability - @MainActor private func enableCancellationAfterDelay() { let disabledFor = disableDuration(of: configuration.secondaryAction) guard disabledFor > 0 else { @@ -392,7 +387,6 @@ final class NativeAlternativePaymentDefaultInteractor: } } - @MainActor private func enableCaptureCancellationAfterDelay() { let disabledFor = disableDuration(of: configuration.paymentConfirmation.secondaryAction) guard disabledFor > 0 else { @@ -441,7 +435,6 @@ final class NativeAlternativePaymentDefaultInteractor: self.state = state } - @MainActor private func createParameters( specifications: [PONativeAlternativePaymentMethodParameter] ) async -> [NativeAlternativePaymentInteractorState.Parameter] { @@ -497,7 +490,6 @@ final class NativeAlternativePaymentDefaultInteractor: // MARK: - Default Values /// Updates parameters with default values. - @MainActor private func setDefaultValues( parameters: inout [NativeAlternativePaymentInteractorState.Parameter] ) async { @@ -507,7 +499,8 @@ final class NativeAlternativePaymentDefaultInteractor: let defaultValues = await withCheckedContinuation { continuation in if let delegate { delegate.nativeAlternativePaymentMethodDefaultValues( - for: parameters.map(\.specification), completion: continuation.resume + for: parameters.map(\.specification), + completion: { continuation.resume(returning: $0) } ) } else { continuation.resume(returning: [:]) diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift index 833e28571..357ba0e3f 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Interactor/NativeAlternativePaymentInteractor.swift @@ -5,6 +5,7 @@ // Created by Andrii Vysotskyi on 29.02.2024. // +@MainActor protocol NativeAlternativePaymentInteractor: Interactor { /// Configuration. diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift index 721f48c90..6a3d48794 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentBackgroundStyle.swift @@ -9,6 +9,7 @@ import SwiftUI @_spi(PO) import ProcessOutCoreUI /// Native alternative payment method screen background style. +@MainActor public struct PONativeAlternativePaymentBackgroundStyle { /// Regular background color. diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift index 4f95a21a9..d1e55cb0e 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/PONativeAlternativePaymentStyle.swift @@ -10,6 +10,7 @@ import SwiftUI /// Defines style for native alternative payment module. @available(iOS 14, *) +@MainActor public struct PONativeAlternativePaymentStyle { /// Title style. diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift index 4d0233e1f..7ae99467d 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/Style/View+NativeAlternativePaymentMethodStyle.swift @@ -26,7 +26,8 @@ extension EnvironmentValues { // MARK: - Private Nested Types - private struct Key: EnvironmentKey { + @MainActor + private struct Key: @preconcurrency EnvironmentKey { static let defaultValue = PONativeAlternativePaymentStyle.default } } diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView+Init.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView+Init.swift index 5b17bc11e..6b422d781 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView+Init.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView+Init.swift @@ -24,7 +24,7 @@ extension PONativeAlternativePaymentView { completion: @escaping (Result) -> Void ) { let viewModel = { - var logger = ProcessOut.shared.logger + var logger: POLogger = ProcessOut.shared.logger logger[attributeKey: .invoiceId] = configuration.invoiceId logger[attributeKey: .gatewayConfigurationId] = configuration.gatewayConfigurationId let interactor = NativeAlternativePaymentDefaultInteractor( diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView.swift index 119811f3a..0888c61ef 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/View/PONativeAlternativePaymentView.swift @@ -37,6 +37,7 @@ public struct PONativeAlternativePaymentView: View { .animation(.default, value: viewModel.state.isCaptured) } .onAppear(perform: viewModel.start) + .onDisappear(perform: viewModel.stop) .poConfirmationDialog(item: $viewModel.state.confirmationDialog) } diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/DefaultNativeAlternativePaymentViewModel.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/DefaultNativeAlternativePaymentViewModel.swift index f93405dda..1a6b412b0 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/DefaultNativeAlternativePaymentViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/DefaultNativeAlternativePaymentViewModel.swift @@ -18,10 +18,6 @@ final class DefaultNativeAlternativePaymentViewModel: ViewModel { observeChanges(interactor: interactor) } - deinit { - interactor.cancel() - } - // MARK: - NativeAlternativePaymentViewModel @AnimatablePublished @@ -31,6 +27,10 @@ final class DefaultNativeAlternativePaymentViewModel: ViewModel { interactor.start() } + func stop() { + interactor.cancel() + } + // MARK: - Private Nested Types private typealias InteractorState = NativeAlternativePaymentInteractorState diff --git a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift index c76e60cda..7fe02eb5a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift +++ b/Sources/ProcessOutUI/Sources/Modules/NativeAlternativePayment/ViewModel/NativeAlternativePaymentViewModelState.swift @@ -35,5 +35,7 @@ extension NativeAlternativePaymentViewModelState: AnimationIdentityProvider { extension NativeAlternativePaymentViewModelState { /// Idle state. - static let idle = Self(sections: [], actions: [], isCaptured: false, focusedItemId: nil, confirmationDialog: nil) + static var idle: Self { + Self(sections: [], actions: [], isCaptured: false, focusedItemId: nil, confirmationDialog: nil) + } } diff --git a/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationController.swift b/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationController.swift index e3705adef..3acc4cb55 100644 --- a/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationController.swift +++ b/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationController.swift @@ -9,20 +9,21 @@ import PassKit @_spi(PO) import ProcessOut /// An object that presents a sheet that prompts the user to authorize a payment request +@MainActor public final class POPassKitPaymentAuthorizationController: NSObject { /// Determine whether this device can process payment requests. - public static func canMakePayments() -> Bool { + public nonisolated static func canMakePayments() -> Bool { PKPaymentAuthorizationController.canMakePayments() } /// Determine whether this device can process payment requests using specific payment network brands. - public static func canMakePayments(usingNetworks supportedNetworks: [PKPaymentNetwork]) -> Bool { + public nonisolated static func canMakePayments(usingNetworks supportedNetworks: [PKPaymentNetwork]) -> Bool { PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks) } /// Determine whether this device can process payments using the specified networks and capabilities bitmask. - public static func canMakePayments( + public nonisolated static func canMakePayments( usingNetworks supportedNetworks: [PKPaymentNetwork], capabilities: PKMerchantCapability ) -> Bool { PKPaymentAuthorizationController.canMakePayments(usingNetworks: supportedNetworks, capabilities: capabilities) @@ -30,10 +31,10 @@ public final class POPassKitPaymentAuthorizationController: NSObject { /// Initialize the controller with a payment request. public init?(paymentRequest: PKPaymentRequest) { - if PKPaymentAuthorizationViewController(paymentRequest: paymentRequest) == nil { + guard Self.canMakePayments() else { return nil } - _didPresentApplePay = .init(wrappedValue: false) + didPresentApplePay = false self.paymentRequest = paymentRequest controller = PKPaymentAuthorizationController(paymentRequest: paymentRequest) errorMapper = DefaultPassKitPaymentErrorMapper(logger: ProcessOut.shared.logger) @@ -49,7 +50,7 @@ public final class POPassKitPaymentAuthorizationController: NSObject { completion?(false) return } - $didPresentApplePay.withLock { $0 = true } + didPresentApplePay = true // Bound lifecycle of self to underlying PKPaymentAuthorizationController objc_setAssociatedObject(controller, &AssociatedObjectKeys.controller, self, .OBJC_ASSOCIATION_RETAIN) controller.present(completion: completion) @@ -83,7 +84,7 @@ public final class POPassKitPaymentAuthorizationController: NSObject { // MARK: - Private Nested Types private enum AssociatedObjectKeys { - static var controller: UInt8 = 0 + nonisolated(unsafe) static var controller: UInt8 = 0 } // MARK: - Private Properties @@ -93,8 +94,6 @@ public final class POPassKitPaymentAuthorizationController: NSObject { private let errorMapper: PassKitPaymentErrorMapper private let cardsService: POCardsService - - @POUnfairlyLocked private var didPresentApplePay: Bool } @@ -173,7 +172,9 @@ extension POPassKitPaymentAuthorizationController: PKPaymentAuthorizationControl return update ?? PKPaymentRequestPaymentMethodUpdate() } - public func presentationWindow(for _: PKPaymentAuthorizationController) -> UIWindow? { - delegate?.presentationWindow(for: self) + public nonisolated func presentationWindow(for _: PKPaymentAuthorizationController) -> UIWindow? { + MainActor.assumeIsolated { + delegate?.presentationWindow(for: self) + } } } diff --git a/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationControllerDelegate.swift b/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationControllerDelegate.swift index 5825d6b82..971a13b0c 100644 --- a/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationControllerDelegate.swift +++ b/Sources/ProcessOutUI/Sources/Modules/PassKitPaymentAuthorization/POPassKitPaymentAuthorizationControllerDelegate.swift @@ -87,6 +87,7 @@ public protocol POPassKitPaymentAuthorizationControllerDelegate: AnyObject { extension POPassKitPaymentAuthorizationControllerDelegate { + @MainActor public func paymentAuthorizationController( _ controller: POPassKitPaymentAuthorizationController, didFailToTokenizePayment payment: PKPayment, @@ -95,6 +96,7 @@ extension POPassKitPaymentAuthorizationControllerDelegate { nil } + @MainActor public func paymentAuthorizationControllerWillAuthorizePayment( _ controller: POPassKitPaymentAuthorizationController ) { @@ -102,6 +104,7 @@ extension POPassKitPaymentAuthorizationControllerDelegate { } @available(iOS 14.0, *) + @MainActor public func paymentAuthorizationControllerDidRequestMerchantSessionUpdate( controller: POPassKitPaymentAuthorizationController ) async -> PKPaymentRequestMerchantSessionUpdate? { @@ -109,6 +112,7 @@ extension POPassKitPaymentAuthorizationControllerDelegate { } @available(iOS 15.0, *) + @MainActor public func paymentAuthorizationController( _ controller: POPassKitPaymentAuthorizationController, didChangeCouponCode couponCode: String @@ -116,6 +120,7 @@ extension POPassKitPaymentAuthorizationControllerDelegate { nil } + @MainActor public func paymentAuthorizationController( _ controller: POPassKitPaymentAuthorizationController, didSelectShippingMethod shippingMethod: PKShippingMethod @@ -123,6 +128,7 @@ extension POPassKitPaymentAuthorizationControllerDelegate { nil } + @MainActor public func paymentAuthorizationController( _ controller: POPassKitPaymentAuthorizationController, didSelectShippingContact contact: PKContact @@ -130,6 +136,7 @@ extension POPassKitPaymentAuthorizationControllerDelegate { nil } + @MainActor public func paymentAuthorizationController( _ controller: POPassKitPaymentAuthorizationController, didSelectPaymentMethod paymentMethod: PKPaymentMethod diff --git a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/DefaultSafariViewModel.swift b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/DefaultSafariViewModel.swift index c81fbe579..005e8ef38 100644 --- a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/DefaultSafariViewModel.swift +++ b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/DefaultSafariViewModel.swift @@ -9,14 +9,15 @@ import Foundation import SafariServices @_spi(PO) import ProcessOut -final class DefaultSafariViewModel: NSObject, SFSafariViewControllerDelegate { +@MainActor +final class DefaultSafariViewModel: NSObject, Sendable, @preconcurrency SFSafariViewControllerDelegate { init( callback: POWebAuthenticationSessionCallback, timeout: TimeInterval? = nil, eventEmitter: POEventEmitter, logger: POLogger, - completion: @escaping (Result) -> Void + completion: @escaping @Sendable (Result) -> Void ) { self.callback = callback self.timeout = timeout @@ -32,7 +33,9 @@ final class DefaultSafariViewModel: NSObject, SFSafariViewControllerDelegate { } if let timeout { timeoutTimer = Timer.scheduledTimer(withTimeInterval: timeout, repeats: false) { [weak self] _ in - self?.setCompletedState(with: POFailure(code: .timeout(.mobile))) + MainActor.assumeIsolated { + self?.setCompletedState(with: POFailure(code: .timeout(.mobile))) + } } } deepLinkObserver = eventEmitter.on(PODeepLinkReceivedEvent.self) { [weak self] event in @@ -59,7 +62,7 @@ final class DefaultSafariViewModel: NSObject, SFSafariViewControllerDelegate { } } - func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo url: URL) { + nonisolated func safariViewController(_ controller: SFSafariViewController, initialLoadDidRedirectTo url: URL) { logger.debug("Safari did redirect to url: \(url)") } diff --git a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSession.swift b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSession.swift index 0a4be1baa..33bb00a9a 100644 --- a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSession.swift +++ b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSession.swift @@ -10,17 +10,17 @@ import AuthenticationServices @_spi(PO) import ProcessOut /// A session that an app uses to authenticate a payment. -public final class POWebAuthenticationSession { +@MainActor +public final class POWebAuthenticationSession: Sendable { /// A completion handler for the web authentication session. - typealias Completion = (Result) -> Void + typealias Completion = @Sendable (Result) -> Void /// Only call this method once for a given POWebAuthenticationSession instance after initialization. /// Calling the start() method on a canceled session results in a failure. /// /// After you call start(), the session instance stores a strong reference to itself. To avoid deallocation during /// the authentication process, the session keeps the reference until after it calls the completion handler. - @MainActor public func start() async -> Bool { guard state == nil else { preconditionFailure("Session start must be attempted only once.") @@ -41,7 +41,6 @@ public final class POWebAuthenticationSession { /// /// If the session has already presented a view with the authentication webpage, calling this method dismisses /// that view. Calling cancel() on an already canceled/completed session has no effect. - @MainActor public func cancel() async { guard case .started(let viewController) = state else { return @@ -71,7 +70,7 @@ public final class POWebAuthenticationSession { // MARK: - Private Nested Types private enum AssociatedKeys { - static var controller: UInt8 = 0 + nonisolated(unsafe) static var controller: UInt8 = 0 } private enum State { @@ -105,7 +104,7 @@ public final class POWebAuthenticationSession { return viewController } - private func complete(with result: Result) { + private nonisolated func complete(with result: Result) { Task { @MainActor in await self.cancel() state = .completed diff --git a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSessionCallback.swift b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSessionCallback.swift index fb0368c13..1ced4cbe8 100644 --- a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSessionCallback.swift +++ b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/POWebAuthenticationSessionCallback.swift @@ -8,7 +8,7 @@ import Foundation /// An object used to evaluate navigation events in an authentication session. -public struct POWebAuthenticationSessionCallback: @unchecked Sendable { +public struct POWebAuthenticationSessionCallback: Sendable { /// Creates a callback object that matches against URLs with the given custom scheme. /// - Parameter customScheme: The custom scheme that the app expects in the callback URL. @@ -17,5 +17,5 @@ public struct POWebAuthenticationSessionCallback: @unchecked Sendable { } /// Check whether a given main-frame navigation URL matches the callback expected by the client app. - let matchesURL: (_ url: URL) -> Bool + let matchesURL: @Sendable (_ url: URL) -> Bool } diff --git a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/SafariViewController+Extensions.swift b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/SafariViewController+Extensions.swift index bccff14ca..f6a2beb39 100644 --- a/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/SafariViewController+Extensions.swift +++ b/Sources/ProcessOutUI/Sources/Modules/WebAuthentication/SafariViewController+Extensions.swift @@ -17,6 +17,6 @@ extension SFSafariViewController { // MARK: - Private Nested Types private enum Keys { - static var viewModel: UInt8 = 0 + nonisolated(unsafe) static var viewModel: UInt8 = 0 } } diff --git a/Templates/AutoCompletion.stencil b/Templates/AutoCompletion.stencil index 91b307c88..0dc1d0e02 100644 --- a/Templates/AutoCompletion.stencil +++ b/Templates/AutoCompletion.stencil @@ -18,7 +18,7 @@ extension {{ type.name }} { {% for parameter in method.parameters %} {{ parameter.asSource }}, {% endfor %} - completion: @escaping ({% if method.throws %}Result<{{ method.returnTypeName.asSource }}, POFailure>{% else %}{{ method.returnTypeName.asSource }}{% endif %}) -> Void + completion: @escaping @Sendable ({% if method.throws %}Result<{{ method.returnTypeName.asSource }}, POFailure>{% else %}{{ method.returnTypeName.asSource }}{% endif %}) -> Void ) -> POCancellable { invoke(completion: completion) { {% if method.throws %}try {% endif %}await {{ method.callName }}({% for parameter in method.parameters %}{% if parameter.argumentLabel %}{{ parameter.argumentLabel }}: {% endif %}{{ parameter.name }}{% if not forloop.last %}, {% endif %}{% endfor %}) @@ -29,9 +29,9 @@ extension {{ type.name }} { {% if forloop.last %} /// Invokes given completion with a result of async operation. -private func invoke( - completion: @escaping (Result) -> Void, - after operation: @escaping () async throws -> T +private func invoke( + completion: @escaping @Sendable (Result) -> Void, + after operation: @escaping @Sendable () async throws -> T ) -> POCancellable { Task { @MainActor in do { @@ -47,7 +47,10 @@ private func invoke( } /// Invokes given completion with a result of async operation. -private func invoke(completion: @escaping (T) -> Void, after operation: @escaping () async -> T) -> Task { +private func invoke( + completion: @escaping @Sendable (T) -> Void, + after operation: @escaping @Sendable () async -> T +) -> Task { Task { @MainActor in completion(await operation()) } diff --git a/project.yml b/project.yml index 00c2f632e..3c9140d6b 100644 --- a/project.yml +++ b/project.yml @@ -8,6 +8,7 @@ settings: SUPPORTS_MACCATALYST: false LOCALIZED_STRING_MACRO_NAMES: "$(inherited) POStringResource" LOCALIZATION_PREFERS_STRING_CATALOGS: true + SWIFT_STRICT_CONCURRENCY: complete options: transitivelyLinkDependencies: true packages: