Skip to content

Commit

Permalink
Merge pull request #222 from bizz84/feature/remove-SKProduct-caching
Browse files Browse the repository at this point in the history
Remove SKProduct caching.
  • Loading branch information
bizz84 authored May 29, 2017
2 parents c644adc + c49f43e commit 8e51120
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 68 deletions.
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,11 @@

All notable changes to this project will be documented in this file.

## [WIP] 0.10.2 Remove SKProduct caching

* Remove SKProduct caching ([#222](https://github.com/bizz84/SwiftyStoreKit/pull/222), related issue: [#212](https://github.com/bizz84/SwiftyStoreKit/issues/212))
* Adds new purchase product method based on SKProduct

## 0.10.1 Danger, xcpretty integration

* Adds Danger for better Pull Request etiquette ([#215](https://github.com/bizz84/SwiftyStoreKit/pull/215)).
Expand Down
18 changes: 17 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]
}
```

### Purchase a product
### Purchase a product (given a product id)

* **Atomic**: to be used when the content is delivered immediately.

Expand Down Expand Up @@ -126,6 +126,22 @@ SwiftyStoreKit.purchaseProduct("com.musevisions.SwiftyStoreKit.Purchase1", quant
}
```

### Purchase a product (given a SKProduct)

This is a variant of the method above that can be used to purchase a product when the corresponding `SKProduct` has already been retrieved with `retrieveProductsInfo`:

```swift
SwiftyStoreKit.retrieveProductsInfo(["com.musevisions.SwiftyStoreKit.Purchase1"]) { result in
if let product = result.retrievedProducts.first {
SwiftyStoreKit.purchaseProduct(product, quantity: 1, atomically: true) { result in
// handle result (same as above)
}
}
}
```

Using this `purchaseProduct` method guarantees that only one network call is made to StoreKit to perform the purchase, as opposed to one call to get the product and another to perform the purchase.

### Restore previous purchases

According to [Apple - Restoring Purchased Products](https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Restoring.html#//apple_ref/doc/uid/TP40008267-CH8-SW9):
Expand Down
32 changes: 2 additions & 30 deletions SwiftyStoreKit/ProductsInfoController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,43 +27,15 @@ import StoreKit

class ProductsInfoController: NSObject {

// MARK: Private declarations

// As we can have multiple inflight queries and purchases, we store them in a dictionary by product id
private var inflightQueries: [Set<String>: InAppProductQueryRequest] = [:]

private(set) var products: [String: SKProduct] = [:]

private func addProduct(_ product: SKProduct) {
products[product.productIdentifier] = product
}

private func allProductsMatching(_ productIds: Set<String>) -> Set<SKProduct> {

return Set(productIds.flatMap { self.products[$0] })
}

private func requestProducts(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {
func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {

inflightQueries[productIds] = InAppProductQueryRequest.startQuery(productIds) { result in

self.inflightQueries[productIds] = nil
for product in result.retrievedProducts {
self.addProduct(product)
}
completion(result)
}
}

func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {

let products = allProductsMatching(productIds)
guard products.count == productIds.count else {

requestProducts(productIds, completion: completion)
return
}
completion(RetrieveResults(retrievedProducts: products, invalidProductIDs: [], error: nil))
}

}
81 changes: 44 additions & 37 deletions SwiftyStoreKit/SwiftyStoreKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,40 @@ public class SwiftyStoreKit {
self.receiptVerificator = receiptVerificator
}

// MARK: Internal methods

func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {
// MARK: private methods
fileprivate func retrieveProductsInfo(_ productIds: Set<String>, completion: @escaping (RetrieveResults) -> Void) {
return productsInfoController.retrieveProductsInfo(productIds, completion: completion)
}

func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> Void) {

if let product = productsInfoController.products[productId] {
purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
} else {
retrieveProductsInfo(Set([productId])) { result -> Void in
if let product = result.retrievedProducts.first {
self.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
} else if let error = result.error {
completion(.error(error: SKError(_nsError: error as NSError)))
} else if let invalidProductId = result.invalidProductIDs.first {
let userInfo = [ NSLocalizedDescriptionKey: "Invalid product id: \(invalidProductId)" ]
let error = NSError(domain: SKErrorDomain, code: SKError.paymentInvalid.rawValue, userInfo: userInfo)
completion(.error(error: SKError(_nsError: error)))
}

fileprivate func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> Void) {

retrieveProductsInfo(Set([productId])) { result -> Void in
if let product = result.retrievedProducts.first {
self.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
} else if let error = result.error {
completion(.error(error: SKError(_nsError: error as NSError)))
} else if let invalidProductId = result.invalidProductIDs.first {
let userInfo = [ NSLocalizedDescriptionKey: "Invalid product id: \(invalidProductId)" ]
let error = NSError(domain: SKErrorDomain, code: SKError.paymentInvalid.rawValue, userInfo: userInfo)
completion(.error(error: SKError(_nsError: error)))
}
}
}

func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> Void) {
fileprivate func purchase(product: SKProduct, quantity: Int, atomically: Bool, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> Void) {
guard SwiftyStoreKit.canMakePayments else {
let error = NSError(domain: SKErrorDomain, code: SKError.paymentNotAllowed.rawValue, userInfo: nil)
completion(.error(error: SKError(_nsError: error)))
return
}

paymentQueueController.startPayment(Payment(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername) { result in

completion(self.processPurchaseResult(result))
})
}

fileprivate func restorePurchases(atomically: Bool = true, applicationUsername: String = "", completion: @escaping (RestoreResults) -> Void) {

paymentQueueController.restorePurchases(RestorePurchases(atomically: atomically, applicationUsername: applicationUsername) { results in

Expand All @@ -75,30 +83,16 @@ public class SwiftyStoreKit {
})
}

func completeTransactions(atomically: Bool = true, completion: @escaping ([Purchase]) -> Void) {
fileprivate func completeTransactions(atomically: Bool = true, completion: @escaping ([Purchase]) -> Void) {

paymentQueueController.completeTransactions(CompleteTransactions(atomically: atomically, callback: completion))
}

func finishTransaction(_ transaction: PaymentTransaction) {
fileprivate func finishTransaction(_ transaction: PaymentTransaction) {

paymentQueueController.finishTransaction(transaction)
}

// MARK: private methods
private func purchase(product: SKProduct, quantity: Int, atomically: Bool, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> Void) {
guard SwiftyStoreKit.canMakePayments else {
let error = NSError(domain: SKErrorDomain, code: SKError.paymentNotAllowed.rawValue, userInfo: nil)
completion(.error(error: SKError(_nsError: error)))
return
}

paymentQueueController.startPayment(Payment(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername) { result in

completion(self.processPurchaseResult(result))
})
}

private func processPurchaseResult(_ result: TransactionResult) -> PurchaseResult {
switch result {
case .purchased(let purchase):
Expand Down Expand Up @@ -162,10 +156,23 @@ extension SwiftyStoreKit {
* - Parameter applicationUsername: an opaque identifier for the user’s account on your system
* - Parameter completion: handler for result
*/
public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> Void) {
public class func purchaseProduct(_ productId: String, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping (PurchaseResult) -> Void) {

sharedInstance.purchaseProduct(productId, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
}

/**
* Purchase a product
* - Parameter product: product to be purchased
* - Parameter quantity: quantity of the product to be purchased
* - Parameter atomically: whether the product is purchased atomically (e.g. finishTransaction is called immediately)
* - Parameter applicationUsername: an opaque identifier for the user’s account on your system
* - Parameter completion: handler for result
*/
public class func purchaseProduct(_ product: SKProduct, quantity: Int = 1, atomically: Bool = true, applicationUsername: String = "", completion: @escaping ( PurchaseResult) -> Void) {

sharedInstance.purchase(product: product, quantity: quantity, atomically: atomically, applicationUsername: applicationUsername, completion: completion)
}

/**
* Restore purchases
Expand Down

0 comments on commit 8e51120

Please sign in to comment.