我有一個只有一個IAP的應用程序,允許用戶解鎖整個遊戲。對於少數用戶,他們成功購買了IAP,但是當他們點擊繼續時,遊戲就會崩潰。當重新啓動應用並嘗試加載他們保存的遊戲時,該應用再次崩潰。刪除並重新安裝應用程序也沒有區別。在應用程序購買成功會導致部分用戶崩潰
這裏是崩潰報告:
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.BlahBlah.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.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
其中它崩潰的方法是:
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:
self.completeTransaction(transaction)
break
case .Failed:
self.failedTransaction(transaction)
break
case .Restored:
self.restoreTransaction(transaction)
break
case .Deferred:
break
case .Purchasing:
break
}
}
}
}
我用,將其從一個雷Wenderlich教程採取的IAP的IAPHelper.swift類:http://www.raywenderlich.com/105365/in-app-purchases-tutorial-getting-started
爲了提供儘可能多的信息,我將在此類下面列出整個&也相關代碼從該選項解鎖遊戲中的UnlockGameViewController提出:
// IAPHelper.swift
import StoreKit
// ** NSNotifications - for sending messages to handle in UnlockGameVC ** //
/// Notification that is generated when a product is purchased.
public let IAPHelperProductPurchasedNotification = "IAPHelperProductPurchasedNotification"
/// Notification that is generated when a transaction fails.
public let IAPHelperTransactionFailedNotification = "IAPHelperTransactionFailedNotification"
/// Notification that is generated when cannot retrieve IAPs from iTunes.
public let IAPHelperConnectionErrorNotification = "IAPHelperConnectionErrorNotification"
/// Notification that is generated when we need to stop the spinner.
public let IAPHelperStopSpinnerNotification = "IAPHelperStopSpinnerNotification"
/// Product identifiers are unique strings registered on the app store.
public typealias ProductIdentifier = String
/// Completion handler called when products are fetched.
public typealias RequestProductsCompletionHandler = (success: Bool, products: [SKProduct]) ->()
/// A Helper class for In-App-Purchases, it can fetch products, tell you if a product has been purchased,
/// purchase products, and restore purchases. Uses NSUserDefaults to cache if a product has been purchased.
public class IAPHelper : NSObject {
/// MARK: - User facing API
/// Initialize the helper. Pass in the set of ProductIdentifiers supported by the app.
public init(productIdentifiers: Set<ProductIdentifier>) {
self.productIdentifiers = productIdentifiers
for productIdentifier in productIdentifiers {
let purchased = NSUserDefaults.standardUserDefaults().boolForKey(productIdentifier)
if purchased {
purchasedProductIdentifiers.insert(productIdentifier)
print("Previously purchased: \(productIdentifier)")
} else {
print("Not purchased: \(productIdentifier)")
}
}
super.init()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
/// Gets the list of SKProducts from the Apple server and calls the handler with the list of products.
public func requestProductsWithCompletionHandler(handler: RequestProductsCompletionHandler) {
completionHandler = handler
productsRequest = SKProductsRequest(productIdentifiers: productIdentifiers)
productsRequest?.delegate = self
productsRequest?.start()
}
/// Initiates purchase of a product.
public func purchaseProduct(product: SKProduct) {
print("Buying \(product.productIdentifier)...")
let payment = SKPayment(product: product)
SKPaymentQueue.defaultQueue().addPayment(payment)
}
/// Given the product identifier, returns true if that product has been purchased.
public func isProductPurchased(productIdentifier: ProductIdentifier) -> Bool {
return purchasedProductIdentifiers.contains(productIdentifier)
}
/// If the state of whether purchases have been made is lost (e.g. the
/// user deletes and reinstalls the app) this will recover the purchases.
public func restoreCompletedTransactions() {
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
print("Restoring...")
}
public func paymentQueueRestoreCompletedTransactionsFinished(queue: SKPaymentQueue) {
print("Restore queue finished.")
NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperStopSpinnerNotification, object: nil)
}
public func paymentQueue(queue: SKPaymentQueue, restoreCompletedTransactionsFailedWithError error: NSError) {
print("Restore queue failed.")
NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperConnectionErrorNotification, object: nil)
}
public class func canMakePayments() -> Bool {
return SKPaymentQueue.canMakePayments()
}
/// MARK: - Private Properties
// Used to keep track of the possible products and which ones have been purchased.
private let productIdentifiers: Set<ProductIdentifier>
private var purchasedProductIdentifiers = Set<ProductIdentifier>()
// Used by SKProductsRequestDelegate
private var productsRequest: SKProductsRequest?
private var completionHandler: RequestProductsCompletionHandler?
}
// MARK: - SKProductsRequestDelegate
extension IAPHelper: SKProductsRequestDelegate {
public func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
print("Loaded list of products...")
let products = response.products
completionHandler?(success: true, products: products)
clearRequest()
// debug printing
for p in products {
print("Found product: \(p.productIdentifier) \(p.localizedTitle) \(p.price.floatValue)")
}
}
public func request(request: SKRequest, didFailWithError error: NSError) {
print("Failed to load list of products.")
print("Error: \(error)")
NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperConnectionErrorNotification, object: nil)
clearRequest()
}
private func clearRequest() {
productsRequest = nil
completionHandler = nil
}
}
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
}
}
}
}
private func completeTransaction(transaction: SKPaymentTransaction) {
print("completeTransaction...")
provideContentForProductIdentifier(transaction.payment.productIdentifier)
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
}
private func restoreTransaction(transaction: SKPaymentTransaction) {
let productIdentifier = transaction.originalTransaction!.payment.productIdentifier
print("restoreTransaction... \(productIdentifier)")
provideContentForProductIdentifier(productIdentifier)
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
}
// Helper: Saves the fact that the product has been purchased and posts a notification.
private func provideContentForProductIdentifier(productIdentifier: String) {
purchasedProductIdentifiers.insert(productIdentifier)
NSUserDefaults.standardUserDefaults().setBool(true, forKey: productIdentifier)
NSUserDefaults.standardUserDefaults().synchronize()
NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperProductPurchasedNotification, object: productIdentifier)
}
private func failedTransaction(transaction: SKPaymentTransaction) {
print("failedTransaction...")
NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperStopSpinnerNotification, object: nil)
if transaction.error!.code != SKErrorPaymentCancelled {
print("Transaction error: \(transaction.error!.localizedDescription)")
NSNotificationCenter.defaultCenter().postNotificationName(IAPHelperTransactionFailedNotification, object: nil)
}
SKPaymentQueue.defaultQueue().finishTransaction(transaction)
}
}
通知方法從UnlockGameViewController:
// MARK: - NSNotification methods
// When a product is purchased, this notification fires
func productPurchased(notification: NSNotification) {
let productIdentifier = notification.object as! String
for (index, product) in products.enumerate() {
if product.productIdentifier == productIdentifier {
// Only one IAP so we can assume this is the Unlock Full Game IAP
activitySpinnerStop()
agent.gameUnlocked = true // sets the bool that the game has now been unlocked
if openedFromMain == true {
showAlertWith(Localization("GameUnlockedAlertTitle"), message: Localization("GameUnlockedAlertMessage"))
noThanksButton.setTitle("Return to Main Menu", forState: UIControlState.Normal)
} else {
showAlertWith(Localization("GameUnlockedAlertTitle"), message: Localization("GameUnlockedAlertMessage2"))
}
}
}
}
// When a transaction fails, this notification fires
func transactionFailed(notification: NSNotification) {
activitySpinnerStop()
showAlertWith(Localization("TransactionFailedAlertTitle"), message: Localization("TransactionFailedAlertMessage"))
}
// When we cannot connect to iTunes to retrieve the IAPs, this notification fires
func cannotConnect(notification: NSNotification) {
activitySpinnerStop()
showAlertWith(Localization("NoConnectionAlertTitle"), message: Localization("NoConnectionAlertMessage"))
}
有兩個問題真的:1)爲什麼它擺在首位崩潰? 2)爲什麼他們不能刪除應用程序,重新安裝和恢復?特別是當付款通過時。
說實話,我的用戶如果能夠刪除,重新安裝和恢復沒有問題,就可以承受初始崩潰。一位用戶報告說,在他的iPhone上墜毀後,他在iPad上下載了遊戲,並能夠恢復交易。所以這讓我相信,也許這是一個使用NSUserDefaults保存在崩潰設備上的數據損壞的問題?類似於這個問題的東西: iOS - strange crash on in App purchase restore function
這是我的第一個應用程序,我真的很少有調試經驗。如果解決方案本身不存在,那麼有關如何調試此問題的指導將非常受歡迎。我難以理解如何在我的設備上重現問題,這似乎只發生在實時應用程序上進行IAP時發生的問題。
你爲什麼要使用dispatch_after?這種事情總是讓我感到刺激 – Paulw11
我已經有這個問題一段時間了,並在此之前詢問過它。這被認爲是答案,公平起見,它大大減少了崩潰次數:http://stackoverflow.com/questions/34289204/in-app-purchase-causes-occasional-crash – Eatton
難道是Game Center認證掛着你的整個應用程序? 「Breadcrumb Trail:(reverse chronological seconds) 14 GC Framework:startAuthenticationForExistingPrimaryPlayer」 – smoothBlue