1

My app supports in-app purchases and auto-renewing subscriptions, so it is important that the SKPaymentTransactionObserver is properly notified whenever the renewal notification is published by the system.

I prefer to keep my StoreKit code separate from the AppDelegate, so I created a singleton for it, with a reference in the AppDelegate.

class AppDelegate: UIResponder, UIApplicationDelegate {

    let storeKitManager = DFStoreKitManager.shared

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {

        storeKitManager.register()

        //...

        return true
    }

    func applicationWillTerminate(_ application: UIApplication) {

        storeKitManager.deregister()
    }

}

I add the shared instance as an observer for the SKPaymentQueue.

class DFStoreKitManager: NSObject {

    static let shared = DFStoreKitManager()
    private override init() { //This prevents other classes from using the default '()' initializer for this class.
        super.init()
    }

    func register() {
        SKPaymentQueue.default().add(self)
    }

    func deregister() {
        SKPaymentQueue.default().remove(self)
    }

    //...
}

extension DFStoreKitManager: SKPaymentTransactionObserver {

    func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
        for tx in transactions {

            switch tx.transactionState {
            case .purchasing:   print("SKPaymentTransaction purchasing")
            case .deferred:     print("SKPaymentTransaction deferred")
            case .failed:       print("SKPaymentTransaction failed")

                //...
                queue.finishTransaction(tx)

            case .restored:     print("SKPaymentTransaction restored")

                //...
                queue.finishTransaction(tx)

            case .purchased:    print("SKPaymentTransaction purchased")

                //...
                queue.finishTransaction(tx)
            }
        }
    }

    func paymentQueue(_ queue: SKPaymentQueue, removedTransactions transactions: [SKPaymentTransaction]) {
        print("Removed \(transactions.count) StoreKit transactions")
    }

    func paymentQueue(_ queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: Error) {
        print("SKPaymentTransaction failed to restore through SKPaymentQueue: \(error.localizedDescription)")
    }
}

My logs and analytics show that renewals are consistently being handled through the observer queue, but it seems like the number is significantly lower than the actual number of renewals shown in iTunes Connect.

Starting with iOS 9, NSNotificationCenter will automatically stop sending notifications to observers that have been deallocated (source).

So I'm wondering, is it possible that my singleton is being deallocated by the system, so that the SKPaymentTransactionObersver is also removed?

Or is this singleton (and it's observer status) guaranteed to be retained until the application terminates?

pkamb
  • 26,648
  • 20
  • 124
  • 157
blwinters
  • 1,634
  • 16
  • 34
  • Add a `deinit` and see if it is ever called. But I doubt it is. – rmaddy May 08 '17 at 15:38
  • @rmaddy Good idea, I'll try that. – blwinters May 08 '17 at 15:39
  • the SKPaymentQueue source says specifically: // Observers are not retained. The transactions array will only be synchronized with the server while the queue has observers. This may require that the user authenticate. – Dewey Aug 21 '17 at 15:16
  • @Dewey If I understand correctly, that is stating that the SKPaymentQueue doesn't retain its observers. My question was mostly focused on the retention of my singleton by the AppDelegate, so that it can remain as an observer of the queue. – blwinters Aug 21 '17 at 15:24

2 Answers2

0

The SKPaymentQueue / SKPaymentTransactionObserver docs, specifically, state:

// Observers are not retained. The transactions array will only be synchronized with the server while the queue has observers. This may require that the user authenticate.

open func add(_ observer: SKPaymentTransactionObserver)
open func remove(_ observer: SKPaymentTransactionObserver)

pkamb
  • 26,648
  • 20
  • 124
  • 157
0

My question was mostly focused on the retention of my singleton by the AppDelegate, so that it can remain as an observer of the queue.

Your App Delegate isn't really retaining the already-retained Singleton, so you can safely remove that property.

static let shared = DFStoreKitManager()

The above line is what declares and retains the singleton. Your App Delegate does not need to additionally retain it. Refer to it as DFSoreKitManager.shared anywhere you need the Singleton.

Singleton with properties in Swift 3

You create simple singletons using a static type property, which is guaranteed to be lazily initialized only once, even when accessed across multiple threads simultaneously:

class Singleton {
    static let sharedInstance = Singleton()
}

https://developer.apple.com/documentation/swift/cocoa_design_patterns/managing_a_shared_resource_using_a_singleton

pkamb
  • 26,648
  • 20
  • 124
  • 157