Created
December 20, 2021 05:39
-
-
Save simsaens/58754935235cd951bea0dc29631d9c22 to your computer and use it in GitHub Desktop.
These three files extend the default StoreKit API to use async in Swift 5.5. Based on the PromiseKit StoreKit extensions
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SKPayment+Async.swift | |
// | |
// Created by Sim Saens on 15/12/21. | |
// | |
import Foundation | |
import StoreKit | |
//Adds an async extension to SKPayment to purchase products | |
// Based on the PromiseKit StoreKit extensions and modified for async in Swift 5.5 | |
extension SKPayment { | |
func purchase() async throws -> SKPaymentTransaction { | |
try await withCheckedThrowingContinuation { continuation in | |
PaymentObserver(payment: self) { transaction, error in | |
if let error = error { | |
continuation.resume(throwing: error) | |
return | |
} | |
continuation.resume(returning: transaction) | |
} | |
} | |
} | |
} | |
private class PaymentObserver: NSObject, SKPaymentTransactionObserver { | |
enum PaymentError: Error { | |
case cancelled | |
} | |
let completion: (SKPaymentTransaction, Error?) -> () | |
let payment: SKPayment | |
var retainCycle: PaymentObserver? | |
@discardableResult | |
init(payment: SKPayment, completion: @escaping (SKPaymentTransaction, Error?) -> ()) { | |
self.payment = payment | |
self.completion = completion | |
super.init() | |
SKPaymentQueue.default().add(self) | |
SKPaymentQueue.default().add(payment) | |
retainCycle = self | |
} | |
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { | |
guard let transaction = transactions.first(where: { $0.payment == payment }) else { | |
return | |
} | |
switch transaction.transactionState { | |
case .purchased, .restored: | |
queue.finishTransaction(transaction) | |
completion(transaction, nil) | |
queue.remove(self) | |
retainCycle = nil | |
case .failed: | |
let error = transaction.error ?? PaymentError.cancelled | |
queue.finishTransaction(transaction) | |
completion(transaction, error) | |
queue.remove(self) | |
retainCycle = nil | |
default: | |
break | |
} | |
} | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SKPaymentQueue+Async.swift | |
// | |
// Created by Sim Saens on 14/12/21. | |
// | |
import Foundation | |
import StoreKit | |
//Adds an async extension to SKPaymentQueue to restore transactions | |
// Based on the PromiseKit StoreKit extensions and modified for async in Swift 5.5 | |
extension SKPaymentQueue { | |
func restoreCompletedTransactions() async throws -> [SKPaymentTransaction] { | |
try await withCheckedThrowingContinuation{ continuation in | |
PaymentObserver(self) { transactions, error in | |
if let error = error { | |
continuation.resume(throwing: error) | |
return | |
} | |
continuation.resume(returning: transactions) | |
} | |
} | |
} | |
} | |
private class PaymentObserver: NSObject, SKPaymentTransactionObserver { | |
let completion: ([SKPaymentTransaction], Error?) -> () | |
var retainCycle: PaymentObserver? | |
var transactions: [SKPaymentTransaction] = [] | |
@discardableResult | |
init(_ paymentQueue: SKPaymentQueue, completion: @escaping ([SKPaymentTransaction], Error?) -> ()) { | |
self.completion = completion | |
super.init() | |
paymentQueue.add(self) | |
paymentQueue.restoreCompletedTransactions() | |
retainCycle = self | |
} | |
func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { | |
self.transactions += transactions.filter { $0.transactionState == .restored } | |
transactions.forEach(queue.finishTransaction) | |
} | |
func paymentQueueRestoreCompletedTransactionsFinished(_ queue: SKPaymentQueue) { | |
finish(queue) | |
} | |
func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) { | |
finish(queue, with: error) | |
} | |
func finish(_ queue: SKPaymentQueue, with error: Error? = nil) { | |
if let error = error { | |
completion([], error) | |
} else { | |
completion(transactions, nil) | |
} | |
queue.remove(self) | |
retainCycle = nil | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// SKProductsRequest+Async.swift | |
// | |
// Created by Sim Saens on 16/12/21. | |
// | |
import Foundation | |
import StoreKit | |
//Adds an async extension to SKProductsRequest to fetch products from StoreKit | |
// Based on the PromiseKit StoreKit extensions and modified for async in Swift 5.5 | |
extension SKProductsRequest { | |
func perform() async throws -> SKProductsResponse { | |
try await withCheckedThrowingContinuation { continuation in | |
SKDelegate(request: self) { response, error in | |
if let error = error { | |
continuation.resume(throwing: error) | |
return | |
} | |
continuation.resume(returning: response!) | |
} | |
} | |
} | |
} | |
private class SKDelegate: NSObject, SKProductsRequestDelegate { | |
let completion: (SKProductsResponse?, Error?) -> () | |
var retainCycle: SKDelegate? | |
@discardableResult | |
init(request: SKProductsRequest, completion: @escaping (SKProductsResponse?, Error?) -> ()) { | |
self.completion = completion | |
super.init() | |
self.retainCycle = self | |
request.delegate = self | |
request.start() | |
} | |
@objc fileprivate func request(_ request: SKRequest, didFailWithError error: Error) { | |
completion(nil, error) | |
retainCycle = nil | |
} | |
@objc fileprivate func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) { | |
completion(response, nil) | |
retainCycle = nil | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment