Aleksandar • Vacić

iOS bits and pieces

A single bug in my StoreKit code that lost me 90% of IAP sales

In Banca, my very successful currency conversion app for iPhone, I added ability to buy various themes, through in-app purchase. I followed the guide, implemented all the bits and pieces of the whole workflow. Apart from one important part.

My theme store is modal view controller and in its init method I got this:

1
2
3
4
5
6
7
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
  self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
  if (self) {
      if ([SKPaymentQueue canMakePayments]) {
          // set observer, to follow transactions processing
          [[SKPaymentQueue defaultQueue] addTransactionObserver:self];
      }

The trouble was, I forgot to add this:

1
2
3
4
5
- (void)dealloc {
  if ([SKPaymentQueue canMakePayments]) {
      [[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
  }
}

Where it’s crashing

This is the symbolicated crash report:

1
2
3
4
5
6
7
2 libsystem_c.dylib 0x33e257ec _sigtramp + 48
3 StoreKit 0x32836792 __NotifyObserverAboutChanges + 38
4 CoreFoundation 0x374b6afa CFArrayApplyFunction + 38
5 StoreKit 0x32836762 -[SKPaymentQueue _notifyObserversAboutChanges:] + 118
6 StoreKit 0x32836452 -[SKPaymentQueue _addLocalTransactionForPayment:] + 250
7 StoreKit 0x3283594c -[SKPaymentQueue addPayment:] + 444
8 Banca 0x000f1612 -[ThemeStoreViewController buy:] (ThemeStoreViewController.m:477)

It crashes on that second line, with __NotifyObserverAboutChanges.

Why it crashes

If you look at the crash report, it crashes when customer attempts to buy the product. However, I did test it (of course) and have managed to buy a theme with no issues. This is where TestFlightapp.com checkpoints came in handy. Along with crash log above, I also have the checkpoint events I have sprinkled throughout my code. These are anonymous usage tracking calls, which helps me figure out how are people using my app.

In this case, it also helped me find the bug because - if you have set enough checkpoints - it gives you step-by-step scenario for repeating the issue.

This is the checkpoints list where the bug appears:

  • 00:00:16 Passed checkpoint: (MAIN_VIEW)

  • 00:00:18 Passed checkpoint: (APP_STORE_PRODUCTS_GETTING)

  • 00:00:18 Passed checkpoint: (THEME_STORE)

  • 00:00:19 Passed checkpoint: (APP_STORE_PRODUCTS_RECEIVED)

  • 00:00:27 Passed checkpoint: (MAIN_VIEW)

  • 00:00:31 Passed checkpoint: (THEME_STORE)

  • 00:00:31 Passed checkpoint: (APP_STORE_PRODUCTS_GETTING)

  • 00:00:32 Passed checkpoint: (APP_STORE_PRODUCTS_RECEIVED)

  • 00:00:37 Crashed (Signal)

As you can see, if you open the store, then close, then open again and then attempt buying, the app crashes. Why?

Well, see the code at the start of the post - I’m adding my view controller as SKPaymentQueue transaction observer, but I was not removing it (the missing dealloc). So when you do this twice in a row in one session, StoreKit will attempt to deliver payment processing notification to current store VC and to the old one, which is released. Hence the SIGSEGV crash.

When looking at the number of store opening checkpoints versus the completed sales, I estimate I lost about 90% of sales over the last few days that the app is live.

I asked Apple for expedited review of the Banca 3.0.1, so hopefully they will do it.