0

My game is free to download and has one IAP to unlock the full game. It was crashing immediately after purchase for a minority of users and the crash was fixed by adding these two lines at the beginning of my function:

let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
  *my original code*
}

Here is the thread where it got solved: In App Purchase causes occasional crash

But although the issue no longer occurs for new users, the app still crashes for people who were affected by the bug initially, even after they updated to the fixed version. I assume there is a corrupt setting somewhere on their device from the initial crash, because I had one user who downloaded the game on another device of his and he was able to restore the IAP and play the game just fine.

Here is a crash report:

Incident Identifier: B7B61633-1BE4-4AB2-99ED-A207B2E88525
CrashReporter Key:   2b01761b32c1d23c1adf755f83cc58464c9e7e77
Hardware Model:      iPhone5,2
Process:             MyApp [551]
Path:                /private/var/mobile/Containers/Bundle/Application/43D176E1-395E-4BF5-A0D5-3602068AADA6/MyApp.app/MyApp
Identifier:          com.MyName.MyApp
Version:             5 (1.1)
Code Type:           ARM (Native)
Parent Process:      launchd [1]

Date/Time:           2016-03-02 02:10:42.42 +0000
Launch Time:         2016-03-02 02:10:27.27 +0000
OS Version:          iOS 9.2.1 (13D15)
Report Version:      105

Exception Type:  EXC_BREAKPOINT (SIGTRAP)
Exception Codes: 0x0000000000000001, 0x00000000e7ffdefe
Triggered by Thread:  0

Breadcrumb Trail: (reverse chronological seconds)
14     GC Framework: startAuthenticationForExistingPrimaryPlayer


Global Trace Buffer (reverse chronological seconds):
13.238455    CFNetwork                  0x0000000023da3e45 TCP Conn 0x16ede9d0 SSL Handshake DONE
13.532519    CFNetwork                  0x0000000023da3d7f TCP Conn 0x16ede9d0 starting SSL negotiation
13.534444    CFNetwork                  0x0000000023e231a5 TCP Conn 0x16ede9d0 complete. fd: 11, err: 0
13.537454    CFNetwork                  0x0000000023e242a7 TCP Conn 0x16ede9d0 event 1. err: 0
13.643210    CFNetwork                  0x0000000023e24325 TCP Conn 0x16ede9d0 started
13.648224    CFNetwork                  0x0000000023da3e45 TCP Conn 0x16ed89f0 SSL Handshake DONE
13.982238    CFNetwork                  0x0000000023da3d7f TCP Conn 0x16ed89f0 starting SSL negotiation
13.982896    CFNetwork                  0x0000000023e231a5 TCP Conn 0x16ed89f0 complete. fd: 6, err: 0
13.984447    CFNetwork                  0x0000000023e242a7 TCP Conn 0x16ed89f0 event 1. err: 0

Thread 0 name:
Thread 0 Crashed:
0   MyApp                       0x002028ac _TFFC11MyApp9IAPHelper12paymentQueueFS0_FTCSo14SKPaymentQueue19updatedTransactionsGSaCSo20SKPaymentTransaction__T_U_FT_T_ + 7504 (IAPHelper.swift:0)
1   libdispatch.dylib               0x23447dd6 _dispatch_call_block_and_release + 10 (init.c:760)
2   libdispatch.dylib               0x234514e6 _dispatch_after_timer_callback + 66 (queue.c:3293)
3   libdispatch.dylib               0x23447dc2 _dispatch_client_callout + 22 (init.c:819)
4   libdispatch.dylib               0x2345a6d2 _dispatch_source_latch_and_call + 2042 (inline_internal.h:1063)
5   libdispatch.dylib               0x23449d16 _dispatch_source_invoke + 738 (source.c:755)
6   libdispatch.dylib               0x2344c1fe _dispatch_main_queue_callback_4CF + 394 (inline_internal.h:1043)
7   CoreFoundation                  0x2386cfc4 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 8 (CFRunLoop.c:1613)
8   CoreFoundation                  0x2386b4be __CFRunLoopRun + 1590 (CFRunLoop.c:2718)
9   CoreFoundation                  0x237bdbb8 CFRunLoopRunSpecific + 516 (CFRunLoop.c:2814)
10  CoreFoundation                  0x237bd9ac CFRunLoopRunInMode + 108 (CFRunLoop.c:2844)
11  GraphicsServices                0x24a37af8 GSEventRunModal + 160 (GSEvent.c:2245)
12  UIKit                           0x27aa9fb4 UIApplicationMain + 144 (UIApplication.m:3681)
13  MyApp                       0x001898f4 main + 180 (AppDelegate.swift:12)
14  libdyld.dylib                   0x23470872 start + 2 (start_glue.s:64)

Thread 1 name:
Thread 1:
0   libsystem_kernel.dylib          0x23543320 kevent_qos + 24
1   libdispatch.dylib               0x2345794e _dispatch_mgr_invoke + 254 (source.c:2542)
2   libdispatch.dylib               0x23449a2e _dispatch_mgr_thread + 38 (source.c:2573)

Thread 2:
0   libsystem_kernel.dylib          0x2354288c __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x235e0e18 _pthread_wqthread + 1036 (pthread.c:1999)
2   libsystem_pthread.dylib         0x235e09fc start_wqthread + 8 (pthread_asm.s:147)

Thread 3:
0   libsystem_kernel.dylib          0x2354288c __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x235e0e18 _pthread_wqthread + 1036 (pthread.c:1999)
2   libsystem_pthread.dylib         0x235e09fc start_wqthread + 8 (pthread_asm.s:147)

Thread 4:
0   libsystem_kernel.dylib          0x2354288c __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x235e0e18 _pthread_wqthread + 1036 (pthread.c:1999)
2   libsystem_pthread.dylib         0x235e09fc start_wqthread + 8 (pthread_asm.s:147)

Thread 5 name:
Thread 5:
0   libsystem_kernel.dylib          0x2352dbf8 mach_msg_trap + 20 (syscall_sw.h:105)
1   libsystem_kernel.dylib          0x2352d9f8 mach_msg + 40 (mach_msg.c:103)
2   CoreFoundation                  0x2386cf1c __CFRunLoopServiceMachPort + 136 (CFRunLoop.c:2345)
3   CoreFoundation                  0x2386b2a2 __CFRunLoopRun + 1050 (CFRunLoop.c:2607)
4   CoreFoundation                  0x237bdbb8 CFRunLoopRunSpecific + 516 (CFRunLoop.c:2814)
5   CoreFoundation                  0x237bd9ac CFRunLoopRunInMode + 108 (CFRunLoop.c:2844)
6   CFNetwork                       0x23e049e6 +[NSURLConnection(Loader) _resourceLoadLoop:] + 486 (NSURLConnection.mm:325)
7   Foundation                      0x240c632c __NSThread__start__ + 1144 (NSThread.m:1134)
8   libsystem_pthread.dylib         0x235e2c7e _pthread_body + 138 (pthread.c:656)
9   libsystem_pthread.dylib         0x235e2bf2 _pthread_start + 110 (pthread.c:692)
10  libsystem_pthread.dylib         0x235e0a08 thread_start + 8 (pthread_asm.s:162)

Thread 6 name:
Thread 6:
0   libsystem_kernel.dylib          0x23541f14 __select + 20
1   CoreFoundation                  0x238723c0 __CFSocketManager + 572 (CFSocket.c:2128)
2   libsystem_pthread.dylib         0x235e2c7e _pthread_body + 138 (pthread.c:656)
3   libsystem_pthread.dylib         0x235e2bf2 _pthread_start + 110 (pthread.c:692)
4   libsystem_pthread.dylib         0x235e0a08 thread_start + 8 (pthread_asm.s:162)

Thread 7:
0   libsystem_kernel.dylib          0x2354288c __workq_kernreturn + 8
1   libsystem_pthread.dylib         0x235e0e18 _pthread_wqthread + 1036 (pthread.c:1999)
2   libsystem_pthread.dylib         0x235e09fc start_wqthread + 8 (pthread_asm.s:147)

Thread 0 crashed with ARM Thread State (32-bit):
r0: 0x00000000    r1: 0x00000000      r2: 0x39c940b0      r3: 0x00000000
r4: 0x00000000    r5: 0x00631376      r6: 0x00000000      r7: 0x0040dcf4
r8: 0x0064e984    r9: 0x00000000     r10: 0x00000001     r11: 0x16d54600
ip: 0xf64d8965    sp: 0x0040db34      lr: 0x002011f0      pc: 0x002028ac
cpsr: 0x60000010

Binary Images:
<not included for reasons of brevity>

And here is the function referenced as causing the crash:

extension IAPHelper: SKPaymentTransactionObserver {

  public func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
    let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(0.1 * Double(NSEC_PER_SEC)))
    dispatch_after(delayTime, dispatch_get_main_queue()) {

    for transaction in transactions {
      switch (transaction.transactionState) {
      case .Purchased:
        print("case .Purchased:")
        self.completeTransaction(transaction)
        break
      case .Failed:
        print("case .Failed:")
        self.failedTransaction(transaction)
        break
      case .Restored:
        print("case .Restored:")
        self.restoreTransaction(transaction)
        break
      case .Deferred:
        print("case .Deferred:")
        break
      case .Purchasing:
        print("case .Purchasing:")
        break
      }
    }
  }
}

Does anybody have any idea how to solve this issue? As I cannot reproduce it and have no idea where to start. They have tried deleting the app from their device and downloading the updated version, but it makes no difference.

EDIT:

I'd like to make clear, it crashed for about 10% of people who tried to buy the IAP in version 1.0. Now it doesn't crash for anybody when they try to buy the IAP in version 1.1. This question is about those 10% from version 1.0 who still cannot load the game properly, because when SKPayment tries to check whether they've previously bought the IAP it crashes every time.

EDIT 2:

Here are the methods called from paymentQueue:

private func completeTransaction(transaction: SKPaymentTransaction) {
  provideContentForProductIdentifier(transaction.payment.productIdentifier)
  SKPaymentQueue.defaultQueue().finishTransaction(transaction)
}

private func restoreTransaction(transaction: SKPaymentTransaction) {
  let productIdentifier = transaction.originalTransaction!.payment.productIdentifier
  provideContentForProductIdentifier(productIdentifier)
  SKPaymentQueue.defaultQueue().finishTransaction(transaction)
}

private func failedTransaction(transaction: SKPaymentTransaction) {
  NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperStopSpinnerNotification, object: nil)
  if transaction.error!.code != SKErrorPaymentCancelled {
     NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperTransactionFailedNotification, object: nil)
  }
  SKPaymentQueue.defaultQueue().finishTransaction(transaction)
}

EDIT 3:

I just received a crash report from a user whose device crashed when making an IAP, which suggests the initial problem has indeed not been fixed. :-( The number of these crashes has reduced dramatically in the updated version, but it looks as though the underlying cause is still there. I will make a new question about this providing more detail as this page has now become a bit of a mess!

Community
  • 1
  • 1
Eatton
  • 455
  • 4
  • 20
  • Have you tried reproducing on the exact same device with the exact same OS? Have you tried using a tool like Crashlytics and a much better logging system like CocoaLumberjack? – nhgrif Mar 05 '16 at 14:22
  • You didn't fix the crash, you tried to work around it. What is the code in the method you're calling? – Wain Mar 05 '16 at 14:36
  • @nhgrif It has crashed on many different devices with different OS. I haven't used Crashlytics and haven't heard of CocoaLumberjack - how might they help? – Eatton Mar 05 '16 at 14:51
  • @wain Which method are you referring to? Also, are you saying there's still an underlying problem? – Eatton Mar 05 '16 at 14:52
  • It's still crashing... What indicates the problem was ever solved...? – nhgrif Mar 05 '16 at 16:40
  • @nhgrif It crashed for about 10% of people who tried to buy the IAP in version 1.0. Now it doesn't crash for anybody when they try to buy the IAP in version 1.1. Surely that means the problem has been solved. This question is about those 10% from version 1.0 who still cannot load the game properly, because when SKPayment tries to check whether they've previously bought the IAP it crashes every time. – Eatton Mar 05 '16 at 18:26

1 Answers1

0

When a user makes a purchase, the purchase is actually made through Apple's software, and Apple records that a purchase was made, the user paid his money, and the app needs to deliver something - when you call finishTransaction, that's when Apple assumes that the deliver has been made.

If the user made a purchase, and your app crashed between the user making the purchase and you delivering, then Apple still remembers that the user made a payment with nothing delivered. So whenever you start listening to the payment queue, you will get a notification about this sale and you need to handle it correctly at this point. This may come unexpected to your app, if you haven't seen the user make a purchase.

gnasher729
  • 51,477
  • 5
  • 75
  • 98
  • Thanks for the explanation. So when my users tap 'Restore' and restoreTransaction is called, do you think I need to handle this particular issue there? I have added restoreTransaction and the other methods called from the payment queue to the bottom of my original question. – Eatton Mar 05 '16 at 23:28
  • Also, how does this explain that an affected user can log into his iTunes account on another device, download the game, and successfully tap 'Restore'. Yet on the device on which the initial crash occured it always crashes when tapping Restore? Any ideas? – Eatton Mar 06 '16 at 07:58