Crash when running collectionGroup query in Cloud Firestore (Swift) - ios

I run a mobile app project using Swift, SwiftUI and Cloud Firestore where I need to find users based on their different settings/preferences. I have solved this by using a collectionGroup query. But sometimes (maybe 1 out of 10 times) the query crashes without any (for me) understandable error message. The composite indexes have been created using the http links provided from XCode.
This is the function I use:
func getUsersFromActivityPrefs(genders:[String], activities:[Int],skillScore_min:Int, skillScore_max:Int,completion:#escaping ([String]) -> ()) {
var matchUsers = [String]()
var count = 0
let db = Firestore.firestore()
for gender in genders {
for activity in activities {
let dbRef = db.collectionGroup("activity_preferences")
.whereField("gender", isEqualTo: gender)
.whereField("activityid", isEqualTo: activity)
.whereField("status", isEqualTo: true)
.whereField("skill_score", isGreaterThanOrEqualTo: skillScore_min)
.whereField("skill_score", isLessThanOrEqualTo: skillScore_max)
.limit(to: 100)
dbRef.getDocuments {( snap, err) in
count+=1
if err != nil {
print(err!.localizedDescription)
}
for i in snap!.documentChanges{
let uid = i.document.get("uid") as? String ?? ""
if uid != "" && !matchUsers.contains(uid) {
matchUsers.append(uid)
if matchUsers.count == 100 {
count = genders.count * activities.count
completion(matchUsers) //escaping completion handler
return
}
}
}
if count == genders.count * activities.count {
completion(matchUsers)
return
}
}
}
}
}
I have attached the trace log and the crash message from XCode. Im using the latest version of Firebase SDK and deployment target is iOS14.
This is the trace log I get:
thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x4f)
frame #0: 0x00007fff4b80dd66 AttributeGraphAG::Graph::input_value_ref_slow(AG::data::ptr<AG::Node>, AG::AttributeID, unsigned int, AGSwiftMetadata const*, bool*, long) + 322 frame #1: 0x00007fff4b81f1a5 AttributeGraphAGGraphGetValue + 203
frame #2: 0x00007fff55e7ffab SwiftUISwiftUI.DynamicBody.phase.getter : SwiftUI._GraphInputs.Phase + 27 frame #3: 0x00007fff55e80176 SwiftUISwiftUI.DynamicBody.updateValue() -> () + 294
frame #4: 0x00007fff55b9583a SwiftUIpartial apply forwarder for implicit closure #2 (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in implicit closure #1 (A1.Type) -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 () -> (Swift.UnsafeMutableRawPointer, __C.AGAttribute) -> () in closure #1 (Swift.UnsafePointer<A1>) -> AttributeGraph.Attribute<A> in AttributeGraph.Attribute.init<A where A == A1.Value, A1: AttributeGraph.StatefulRule>(A1) -> AttributeGraph.Attribute<A> + 26 frame #5: 0x00007fff4b808d03 AttributeGraphAG::Graph::UpdateStack::update() + 505
frame #6: 0x00007fff4b809199 AttributeGraphAG::Graph::update_attribute(AG::data::ptr<AG::Node>, bool) + 335 frame #7: 0x00007fff4b80d8e8 AttributeGraphAG::Graph::value_ref(AG::AttributeID, AGSwiftMetadata const*, bool*) + 130
frame #8: 0x00007fff4b81f1f3 AttributeGraphAGGraphGetValue + 281 frame #9: 0x00007fff561aeeb7 SwiftUISwiftUI.GraphHost.updatePreferences() -> Swift.Bool + 39
frame #10: 0x00007fff55c9a8cf SwiftUISwiftUI.ViewGraph.updateOutputs(at: SwiftUI.Time) -> () + 95 frame #11: 0x00007fff5611310c SwiftUIclosure #1 () -> () in (extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 1308
frame #12: 0x00007fff56112327 SwiftUI(extension in SwiftUI):SwiftUI.ViewRendererHost.render(interval: Swift.Double, updateDisplayList: Swift.Bool) -> () + 343 frame #13: 0x00007fff55ba07de SwiftUIclosure #1 () -> () in SwiftUI._UIHostingView.requestImmediateUpdate() -> () + 62
frame #14: 0x00007fff562739ae SwiftUIreabstraction thunk helper from #escaping #callee_guaranteed () -> () to #escaping #callee_unowned #convention(block) () -> () + 14 frame #15: 0x0000000112ebd8ac libdispatch.dylib_dispatch_call_block_and_release + 12
frame #16: 0x0000000112ebea88 libdispatch.dylib_dispatch_client_callout + 8 frame #17: 0x0000000112eccf23 libdispatch.dylib_dispatch_main_queue_callback_4CF + 1152
frame #18: 0x00007fff203a8276 CoreFoundation__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9 frame #19: 0x00007fff203a2b06 CoreFoundation__CFRunLoopRun + 2685
frame #20: 0x00007fff203a1b9e CoreFoundationCFRunLoopRunSpecific + 567 frame #21: 0x00007fff2b773db3 GraphicsServicesGSEventRunModal + 139
frame #22: 0x00007fff24660af3 UIKitCore-[UIApplication _run] + 912 frame #23: 0x00007fff24665a04 UIKitCoreUIApplicationMain + 101
frame #24: 0x000000010db84a5b Sparringmain at AppDelegate.swift:14:7 frame #25: 0x00007fff20257415 libdyld.dylibstart + 1
Attachments:
Crash in XCode 1
Composite index 2

func getUsersFromActivityPrefs(genders: [String], activities: [Int], skillScore_min: Int, skillScore_max: Int, completion: #escaping ([String]) -> Void) {
var matchUsers = [String]()
var count = 0
let db = Firestore.firestore()
let dispatch = DispatchGroup() // instantiate dispatch group outside loop
for gender in genders {
for activity in activities {
dispatch.enter() // enter group on each iteration
let dbRef = db.collectionGroup("activity_preferences")
.whereField("gender", isEqualTo: gender)
.whereField("activityid", isEqualTo: activity)
.whereField("status", isEqualTo: true)
.whereField("skill_score", isGreaterThanOrEqualTo: skillScore_min)
.whereField("skill_score", isLessThanOrEqualTo: skillScore_max)
.limit(to: 100)
dbRef.getDocuments {( snap, err) in
if let snap = snap {
count += 1
for doc in snap.documents {
if let uid = doc.get("uid") as? String,
!matchUsers.contains(uid) {
matchUsers.append(uid)
}
}
} else if let err = err {
print(err)
}
dispatch.leave() // always leave no matter what the db returned
}
}
}
/*
this is the group's completion handler and it's only
called once after all groups have entered and left
*/
dispatch.notify(queue: .main) {
completion(matchUsers)
}
}

Related

Firebase fetching causes crash for some users

When I try to fetch user data from Firebase a crash occurs for some users, I can't reproduce this crash myself but I do have the following crash log:
0 Ski Tracker 0x77bf0 closure #1 in HistoryPresenter.downloadHistory(completionHandler:) + 4340857840 (HistoryPresenter.swift:4340857840)
1 Ski Tracker 0x86c8 closure #1 in FetchFromDatabase.fetchUserHistoryFromDatabase(uid:completionHandler:) + 4340401864 (<compiler-generated>:4340401864)
2 Ski Tracker 0x8604 thunk for #escaping #callee_guaranteed (#guaranteed FIRDataSnapshot) -> () + 4340401668 (<compiler-generated>:4340401668)
3 FirebaseDatabase 0x1df28 __92-[FIRDatabaseQuery observeSingleEventOfType:andPreviousSiblingKeyWithBlock:withCancelBlock:]_block_invoke + 120
4 FirebaseDatabase 0xbf94 __43-[FChildEventRegistration fireEvent:queue:]_block_invoke.11 + 80
5 libdispatch.dylib 0x24b4 _dispatch_call_block_and_release + 32
6 libdispatch.dylib 0x3fdc _dispatch_client_callout + 20
7 libdispatch.dylib 0x127f4 _dispatch_main_queue_drain + 928
8 libdispatch.dylib 0x12444 _dispatch_main_queue_callback_4CF + 44
9 CoreFoundation 0x9a6f8 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16
10 CoreFoundation 0x7c058 __CFRunLoopRun + 2036
11 CoreFoundation 0x80ed4 CFRunLoopRunSpecific + 612
12 GraphicsServices 0x1368 GSEventRunModal + 164
13 UIKitCore 0x3a23d0 -[UIApplication _run] + 888
14 UIKitCore 0x3a2034 UIApplicationMain + 340
15 libswiftUIKit.dylib 0x35308 UIApplicationMain(_:_:_:_:) + 104
16 Ski Tracker 0x7160 main + 4340396384 (FriendView.swift:4340396384)
17 ??? 0x1f6938960 (Missing)
If I understand the crash log correctly the code which is causing the crash is within the fetchUserHistoryFromDatabase function:
func fetchUserHistoryFromDatabase(uid : String, completionHandler: #escaping([String : Any]?) -> Void ) {
ref?.child("users").child(uid).child("runData").observeSingleEvent(of: .value, with: { snapshot in
guard let result = snapshot.value as? [String:Any] else {
print("Error no rundata")
completionHandler(nil)
return
}
completionHandler(result)
})
}
This function is called from downloadHistory where potential nil values are handled:
private func downloadHistory(completionHandler: #escaping () -> Void) {
if let id = Auth.auth().currentUser?.uid {
FetchFromDatabase().fetchUserHistoryFromDatabase(uid : id, completionHandler: { [weak self] dict in
if dict != nil {
for run in dict! {
self?.determineTimeStamp(run : run)
}
if !(self!.tempDict.isEmpty) {
let sortedDict = self?.tempDict.keys.sorted(by: { $0 > $1 } )
self?.convertDictToArray(sortedDict: sortedDict!)
}
}
completionHandler()
}
)}
}
Any help here is greatly appreciated.
Remove the force unwrapping from your code. Every ! is an invitation for a crash.
private func downloadHistory(completionHandler: #escaping () -> Void) {
if let id = Auth.auth().currentUser?.uid {
FetchFromDatabase().fetchUserHistoryFromDatabase(uid : id, completionHandler: { [weak self] dict in
guard let self = self else {
completion()
return
}
if let dict = dict {
for run in dict {
self.determineTimeStamp(run : run)
}
if !self.tempDict.isEmpty {
let sortedDict = self.tempDict.keys.sorted(by: { $0 > $1 } )
self.convertDictToArray(sortedDict: sortedDict)
}
}
completionHandler()
}
)}
}
I notice a self! there dangerous, because a user could leave the calling context of the function and since the closure has a capture list of weak self, it should return nil but you are forcing it
try this
private func downloadHistory(completionHandler: #escaping () -> Void) {
if let id = Auth.auth().currentUser?.uid {
FetchFromDatabase().fetchUserHistoryFromDatabase(uid : id, completionHandler: { [weak self] dict in
guard let self = self else { completionHandler()
return }
if let safeDict = dict {
for run in dict {
self.determineTimeStamp(run : run)
}
if (self.tempDict.isEmpty) {
let sortedDict = self.tempDict.keys.sorted(by: { $0 > $1 } )
self.convertDictToArray(sortedDict: sortedDict)
}
}
completionHandler()
}
)}
}

StoreKit crashes after second request and buy product

I'm having trouble with In-App Purchases. The app is crashing in the following case:
Opening the view from NavController
Requesting the 2 SKProducts
Showing price of products on the labels
Closing View by going back in NavController
Opening the view from NavController again
Requesting the 2 SKProducts
Showing price of products on the labels
Buying one product with SKPaymentQueue.default().add(payment)
Crash with EXC_BAD_ACCESS
I've checked that the payment isn't nil. If I buy the product on the first open everything is working as it should. Any tips why it crashes only in this case?
Here is the code:
override func viewDidLoad() {
super.viewDidLoad()
SKPaymentQueue.default().add(self)
if SKPaymentQueue.canMakePayments(){
print("Enabled, Loading...")
let productId: NSSet = NSSet(objects: unlockPremiumId, disableAdsId)
let request: SKProductsRequest = SKProductsRequest(productIdentifiers: productId as! Set<String>)
request.delegate = self
request.start()
} else {
print("Please Enable IAP")
}
}
var SKProductList = [SKProduct]()
var currentSKProduct = SKProduct()
func productsRequest(_ request: SKProductsRequest, didReceive response: SKProductsResponse) {
let myProducts = response.products
for product in myProducts {
if product.productIdentifier == unlockPremiumId {
if let productPrice = priceStringForProduct(item: product) {
premiumFeaturesBtn.setTitle(selectedLanguage.IAPPurchase + productPrice, for: .normal)
}
}
if product.productIdentifier == disableAdsId {
if let productPrice = priceStringForProduct(item: product) {
disableAdsBtn.setTitle(selectedLanguage.IAPPurchase + productPrice, for: .normal)
}
}
SKProductList.append(product)
}
disableAdsBtn.isEnabled = true
premiumFeaturesBtn.isEnabled = true
restoreBtn.isEnabled = true
}
#IBAction func disableAdsTapped(_ sender: UIButton) {
disableAdsBtn.isEnabled = false
for product in SKProductList {
let ProdId = product.productIdentifier
if ProdId == disableAdsId {
buyProduct(skpayment:SKPayment(product: product))
}
}
}
func buyProduct(skpayment:SKPayment) {
SKPaymentQueue.default().add(product)
}
Here is the Stack Trace, sorry for the formatting:
thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_ACCESS (code=1, address=0x18)
frame #0: 0x00000001108c394b libobjc.A.dylibobjc_msgSend + 11
frame #1: 0x000000011011b4cc StoreKit__NotifyObserverAboutChanges + 66
frame #2: 0x000000011289b268 CoreFoundationCFArrayApplyFunction + 72
frame #3: 0x000000011011b473 StoreKit-[SKPaymentQueue _notifyObserversAboutChanges:sendUpdatedDownloads:] + 148
frame #4: 0x0000000110119951 StoreKit`-[SKPaymentQueue addPayment:] + 313
frame #5: 0x000000010f57c2a8 AnatomyQuizzSettingsInAppPurchasesTableViewController.buyProduct(product=0x00006040000086c0, self=0x00007fbcbb090e00) at SettingsInAppPurchasesTableViewController.swift:114
frame #6: 0x000000010f57b9ac AnatomyQuizzSettingsInAppPurchasesTableViewController.disableAdsTapped(sender=0x00007fbcba473700, self=0x00007fbcbb090e00) at SettingsInAppPurchasesTableViewController.swift:88
frame #7: 0x000000010f57bc3c AnatomyQuizz#objc SettingsInAppPurchasesTableViewController.disableAdsTapped(_:) at SettingsInAppPurchasesTableViewController.swift:0
frame #8: 0x0000000114a57972 UIKit-[UIApplication sendAction:to:from:forEvent:] + 83
frame #9: 0x0000000114bd6c3c UIKit-[UIControl sendAction:to:forEvent:] + 67
frame #10: 0x0000000114bd6f59 UIKit-[UIControl _sendActionsForEvents:withEvent:] + 450
frame #11: 0x0000000114bd5e86 UIKit-[UIControl touchesEnded:withEvent:] + 618
frame #12: 0x0000000115047bad UIKit_UIGestureEnvironmentSortAndSendDelayedTouches + 5560
frame #13: 0x0000000115041a4d UIKit_UIGestureEnvironmentUpdate + 1506
frame #14: 0x000000011504141f UIKit-[UIGestureEnvironment _deliverEvent:toGestureRecognizers:usingBlock:] + 484
frame #15: 0x00000001150404cb UIKit-[UIGestureEnvironment _updateGesturesForEvent:window:] + 288
frame #16: 0x0000000114acef14 UIKit-[UIWindow sendEvent:] + 4102
frame #17: 0x0000000114a72365 UIKit-[UIApplication sendEvent:] + 352
frame #18: 0x00000001153bea1d UIKitdispatchPreprocessedEventFromEventQueue + 2809
frame #19: 0x00000001153c1672 UIKit__handleEventQueueInternal + 5957
frame #20: 0x00000001128f0101 CoreFoundation__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION + 17
frame #21: 0x000000011298ff71 CoreFoundation__CFRunLoopDoSource0 + 81
frame #22: 0x00000001128d4a19 CoreFoundation__CFRunLoopDoSources0 + 185
frame #23: 0x00000001128d3fff CoreFoundation__CFRunLoopRun + 1279
frame #24: 0x00000001128d3889 CoreFoundationCFRunLoopRunSpecific + 409
frame #25: 0x00000001192c99c6 GraphicsServicesGSEventRunModal + 62
frame #26: 0x0000000114a565d6 UIKitUIApplicationMain + 159
frame #27: 0x000000010f6ac0f7 AnatomyQuizzmain at AppDelegate.swift:15
frame #28: 0x0000000117907d81 libdyld.dylibstart + 1
frame #29: 0x0000000117907d81 libdyld.dylib`start + 1

unrecognized selector sent to instance when using Swift fetch

I'm using Xcode Version 8.3.3 with Swift 3. My phone is running iOS 10.3.2.
This issue is happening while I'm out using the app.
I have an intermittent error occurring in my phone app. Once data has been uploaded to a web service, the following code is called to update the object in Core Data. I'm not seeing why the object is causing an error:
func uploadedMarker(_ oldRouteId: Int16, stepId: Double, newRouteId: Int16) {
do {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "HoldMarkers")
let routePredicate = NSPredicate(format: "routeId = %i", oldRouteId)
let stepPredicate = NSPredicate(format: "stepId = %f", stepId)
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [routePredicate, stepPredicate])
request.returnsObjectsAsFaults = false
let markers = try self.context.fetch(request) as! [HoldMarkers]
if markers.count > 0 {
let marker = markers.first!
marker.uploaded = true
marker.routeId = newRouteId
do {
try marker.validateForUpdate()
self.appDelegate.saveContext()
}
catch {
let nserror = error as NSError
NSLog("uploadedMarker - Core Data Error - Updating HoldMarkers :\(nserror), \(nserror.userInfo)")
}
}
}
catch {
let nserror = error as NSError
NSLog("uploadedMarker - Core Data Error - Fetching HoldMarkers :\(nserror), \(nserror.userInfo)")
}
}
Context is defined as:
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
Like I said, this is an intermittent error:
-[MKPolyline _genericValueForKey:withIndex:flags:]: unrecognized selector sent to instance 0x17049a860
1 CoreFoundation __exceptionPreprocess + 124
2 libobjc.A.dylib objc_exception_throw + 52
3 CoreFoundation -[NSObject(NSObject) doesNotRecognizeSelector:] + 136
4 CoreFoundation ___forwarding___ + 912
5 CoreFoundation __forwarding_prep_0___ + 88
6 CoreData _PF_Handler_Public_GetProperty + 244
7 Foundation -[NSFunctionExpression expressionValueWithObject:context:] + 736
8 Foundation -[NSComparisonPredicate evaluateWithObject:substitutionVariables:] + 224
9 Foundation -[NSCompoundPredicateOperator evaluatePredicates:withObject:substitutionVariables:] + 440
10 Foundation -[NSCompoundPredicate evaluateWithObject:substitutionVariables:] + 272
11 CoreData -[NSManagedObjectContext executeFetchRequest:error:] + 2796
12 Parks-Tracker RouteViewController.uploadedMarker(Int16, stepId : Double, newRouteId : Int16) -> () (RouteViewController.swift:1371)
13 Parks-Tracker #objc RouteViewController.uploadedMarker(Int16, stepId : Double, newRouteId : Int16) -> () (RouteViewController.swift:0)
14 Parks-Tracker RouteViewController.(updateRoute(Double, longitude : Double, timestamp : Date, routeId : Int16, stepId : Double, segment : Int16, complete : Bool) -> ()).(closure #2) (RouteViewController.swift:1114)
15 Parks-Tracker thunk for #callee_owned (#owned NSDictionary, #owned NSError?, #unowned Int16, #unowned Double) -> () (RouteViewController.swift:0)
16 Parks-Tracker thunk for #callee_unowned #convention(block) (#unowned NSDictionary, #unowned NSError?, #unowned Int16, #unowned Double) -> () (RouteViewController.swift:0)
17 Parks-Tracker RouteViewController.(callRouteUpdate(Double, longitude : Double, complete : Bool, routeId : Int16, stepId : Double, segment : Int16, conditionDescription : String, markerImage : NSData?, routeName : String, destinationId : Int16, completion : (NSDictionary, NSError?, Int16, Double) -> ()) -> ()).(closure #1) (RouteViewController.swift:1262)
18 Parks-Tracker thunk for #callee_owned (#owned Data?, #owned URLResponse?, #owned Error?) -> () (LoginViewController.swift:0)

dispatch_group_leave error while adding new child

I am trying to add a new child to my node in firebase using this code:
#IBAction func likeButtonOnTouch(_ sender: Any) {
if ViewController.usersUid.count > 0 {
self.update()
}
}
func update() {
let child1: String = (FIRAuth.auth()?.currentUser?.uid)!
let ref = FIRDatabase.database().reference().child(child1).child("following")
let data1: [String:String] = [ViewController.usersUid[self.currentUser]: "true"]
ref.setValue(data1)
}
But when I press the button, my app crashes with this error:
thread 1 exc_bad_instruction (code=1 subcode=0x100ef5c78)
on this line:
ref.setValue(data1)
I have no idea what this means and how to fix it. The most interesting thing that the value has successfully added to my DB (!!!), but app crashes. Please give me an advice.
P.S. crash report:
* thread #1: tid = 0xb503, 0x0000000100f11c78 libdispatch.dylib`dispatch_group_leave + 76, queue = 'com.apple.main-thread', stop reason = EXC_BREAKPOINT (code=1, subcode=0x100f11c78)
frame #0: 0x0000000100f11c78 libdispatch.dylib`dispatch_group_leave + 76
frame #1: 0x00000001000a36f8 Elite Club`thunk + 68 at ViewController.swift:0
frame #2: 0x00000001001bf8a4 Elite Club`__43-[FValueEventRegistration fireEvent:queue:]_block_invoke.57((null)=<unavailable>) + 88 at FValueEventRegistration.m:60 [opt]
frame #3: 0x0000000100f0d258 libdispatch.dylib`_dispatch_call_block_and_release + 24
frame #4: 0x0000000100f0d218 libdispatch.dylib`_dispatch_client_callout + 16
frame #5: 0x0000000100f12280 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1200
frame #6: 0x000000019376e810 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 12
frame #7: 0x000000019376c3fc CoreFoundation`__CFRunLoopRun + 1660
frame #8: 0x000000019369a2b8 CoreFoundation`CFRunLoopRunSpecific + 444
frame #9: 0x000000019514e198 GraphicsServices`GSEventRunModal + 180
frame #10: 0x00000001996e17fc UIKit`-[UIApplication _run] + 684
frame #11: 0x00000001996dc534 UIKit`UIApplicationMain + 208
frame #12: 0x00000001000c04b8 Elite Club`main + 140 at AppDelegate.swift:15
frame #13: 0x000000019267d5b8 libdyld.dylib`start + 4
if ViewController.usersUid[self.currentUser] does not exist please crash
Could you Try this way :
let data1: [String:String] = [(ViewController.usersUid[self.currentUser] ?? "") : "true"]
ref.setValue(data1) { (error, ref) -> Void in
}
more cleaner way :
if let currentuser = ViewController.usersUid[self.currentUser] {
let data1: [String:String] = [currentuser : "true"]
ref.setValue(data1) { (error, ref) -> Void in
}
}
After a day of working, I've figured out. First, you should sign out, and then sign in one more time. Here is my final code:
func update() {
do {
let ref = try FIRAuth.auth()?.signOut()
}
catch {}
let preferences = UserDefaults.standard
FIRAuth.auth()?.signIn(withEmail: preferences.object(forKey: "userName") as! String, password: preferences.object(forKey: "userPassword") as! String) { (user, error) in
if error == nil {
LoggingIn.userUID = (user?.uid)!
print(user?.uid ?? "not found")
print("You have successfully logged in")
let child1: String = (FIRAuth.auth()?.currentUser?.uid)!
let ref = FIRDatabase.database().reference().child(child1).child("following")
let data1: [String:String] = [ViewController.usersUid[self.currentUser] : "true"]
ref.setValue(data1) { (error, ref) -> Void in
}
} else {
}
}
}

React Native / Swift - In App Purchases code crashing randomly in sandbox and testflight

I am trying to include in app purchases in a react native app. I have tried react-native-in-app-utils and creating a native module in Swift. Both work but both seem very unstable. Random crashes with no errors in the Xcode console. Much more stable on test-flight but still crashes and seems to have no rhyme or reason. The bulk of the crashes happen on purchasing, but also happens a lot when retrieving purchases from Apple or restoring purchases. Here is my swift code for which I have an Objective C bridge to react-native:
// Create Product List
struct ProductList {
static let monthlySub : String = "monthlysubscription"
static let threeMonthlySub : String = "threemonthlysubscription"
static let sixMonthlySub : String = "sixmonthlysubscription"
static let yearlySub : String = "yearlysubscription"
static let products = [monthlySub, threeMonthlySub, sixMonthlySub, yearlySub]
}
#objc(StoreManager)
class StoreManager: NSObject {
var loadedProducts: Dictionary<String, SKProduct> = [:]
// callback holders defined as optionals
var purchaseCallbackHolder: RCTResponseSenderBlock? = nil
var productsCallbackHolder: RCTResponseSenderBlock? = nil
var restoreCallbackHolder: RCTResponseSenderBlock? = nil
override init() {
super.init()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
#objc func getProductList(name: String, callback successCallback: RCTResponseSenderBlock){
// check payments allowed
if SKPaymentQueue.canMakePayments() {
// products from static struct
let products = NSSet(array: ProductList.products);
// When request completes, calls the SKProductsRequestDelegate
let request = SKProductsRequest(productIdentifiers: products as! Set<String>);
productsCallbackHolder = successCallback
request.delegate = self;
request.start();
}
}
#objc func purchaseProduct(productIdentifier: String, callback successCallback: RCTResponseSenderBlock) {
let product = loadedProducts[productIdentifier as String]!
let payment = SKPayment(product: product)
// add callback to holder
purchaseCallbackHolder = successCallback
// Triggers SKPaymentTransactionObserver
SKPaymentQueue.defaultQueue().addPayment(payment)
}
#objc func restorePurchases(name:String, callback successCallback: RCTResponseSenderBlock){
restoreCallbackHolder = successCallback
print("Restoring Purchases")
SKPaymentQueue.defaultQueue().restoreCompletedTransactions()
}
#objc func validatePurchases(name:String, callback successCallback: RCTResponseSenderBlock) -> Void {
let receiptUrl = NSBundle.mainBundle().appStoreReceiptURL
let receipt: NSData = NSData(contentsOfURL:receiptUrl!)!
let receiptdata: NSString = receipt.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
// Pass Base64 encoded string back to JS
successCallback([receiptdata])
}
func updateWithProducts(products: [SKProduct]) {
var productIdentifiers: Dictionary<String, NSNumber> = [:]
for product in products {
loadedProducts[product.productIdentifier] = product
productIdentifiers[product.productIdentifier] = product.price
}
productsCallbackHolder?([productIdentifiers])
}
}
extension StoreManager: SKProductsRequestDelegate {
func productsRequest(request: SKProductsRequest, didReceiveResponse response: SKProductsResponse) {
// products retrieved from App store
let appProducts = response.products
if appProducts.count != 0 {
for product in appProducts{
print(product.productIdentifier)
}
print(appProducts)
updateWithProducts(appProducts)
}
else {
// return error/info to react native
print("no products received from store")
}
}
}
extension StoreManager: SKPaymentTransactionObserver {
func paymentQueue(queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) {
print("Received Payment Transaction Response from Apple");
for transaction:AnyObject in transactions {
// check object is a transaction first
if let trans:SKPaymentTransaction = transaction as? SKPaymentTransaction{
switch trans.transactionState {
case .Purchased:
print("Product Purchased");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Failed:
print("Purchased Failed");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
break;
case .Restored:
print("Purchases Restored");
SKPaymentQueue.defaultQueue().finishTransaction(transaction as! SKPaymentTransaction)
default:
break;
}
}
// invoke any callback waiting to be called
purchaseCallbackHolder?([])
purchaseCallbackHolder = nil
restoreCallbackHolder?([])
restoreCallbackHolder = nil
}
}
}
Any help would be much appreciated!
Update: I have a stack trace of the crash below
AppName[1788:581487] *** -[StoreManager respondsToSelector:]: message sent to deallocated instance 0x14d43f80
(lldb) bt
* thread #4: tid = 0x8df6f, 0x25750ffe CoreFoundation`___forwarding___ + 530, queue = 'com.facebook.react.StoreManagerQueue', stop reason = EXC_BREAKPOINT (code=EXC_ARM_BREAKPOINT, subcode=0xdefe)
* frame #0: 0x25750ffe CoreFoundation`___forwarding___ + 530
frame #1: 0x2567b298 CoreFoundation`_CF_forwarding_prep_0 + 24
frame #2: 0x2f103f1e StoreKit`__NotifyObserverAboutChanges + 66
frame #3: 0x2565bc08 CoreFoundation`CFArrayApplyFunction + 36
frame #4: 0x2f103ecc StoreKit`-[SKPaymentQueue _notifyObserversAboutChanges:sendUpdatedDownloads:] + 128
frame #5: 0x2f1028e8 StoreKit`-[SKPaymentQueue addPayment:] + 320
frame #6: 0x00106438 AppName`StoreManager.purchaseProduct(productIdentifier="iosmonthlysubscription799", successCallback=0x00106694 AppName`partial apply forwarder for reabstraction thunk helper from #callee_unowned #convention(block) (#unowned Swift.ImplicitlyUnwrappedOptional<__ObjC.NSArray>) -> (#unowned ()) to #callee_owned (#owned Swift.ImplicitlyUnwrappedOptional<Swift.Array<Swift.AnyObject>>) -> (#unowned ()) with unmangled suffix ".40" at inAppPayments.swift, self=0x14d0a990) -> ()) -> () + 1168 at inAppPayments.swift:54
frame #7: 0x00106634 AppName`#objc StoreManager.purchaseProduct(String, callback : ([AnyObject]!) -> ()) -> () + 240 at inAppPayments.swift:0
frame #8: 0x25752664 CoreFoundation`__invoking___ + 68
frame #9: 0x256778bc CoreFoundation`-[NSInvocation invoke] + 292
frame #10: 0x2567b356 CoreFoundation`-[NSInvocation invokeWithTarget:] + 50
frame #11: 0x001672d6 AppName`-[RCTModuleMethod invokeWithBridge:module:arguments:](self=0x14fdba90, _cmd="invokeWithBridge:module:arguments:", bridge=0x14faa1a0, module=0x14d0a990, arguments=#"2 elements") + 1636 at RCTModuleMethod.m:489
frame #12: 0x001abaea AppName`-[RCTBatchedBridge _handleRequestNumber:moduleID:methodID:params:](self=0x14faa1a0, _cmd="_handleRequestNumber:moduleID:methodID:params:", i=6, moduleID=77, methodID=3, params=#"2 elements") + 926 at RCTBatchedBridge.m:987
frame #13: 0x001aae44 AppName`__33-[RCTBatchedBridge handleBuffer:]_block_invoke.452(.block_descriptor=<unavailable>) + 1028 at RCTBatchedBridge.m:915
frame #14: 0x00b9bba6 libdispatch.dylib`_dispatch_call_block_and_release + 10
frame #15: 0x00ba64aa libdispatch.dylib`_dispatch_queue_drain + 2014
frame #16: 0x00b9ede2 libdispatch.dylib`_dispatch_queue_invoke + 282
frame #17: 0x00ba799e libdispatch.dylib`_dispatch_root_queue_drain + 426
frame #18: 0x00ba77f0 libdispatch.dylib`_dispatch_worker_thread3 + 100
frame #19: 0x2547ee0c libsystem_pthread.dylib`_pthread_wqthread + 1024
frame #20: 0x2547e9fc libsystem_pthread.dylib`start_wqthread + 8
Seems to be crashing because my callback references are optionals? This is Swift which is called by an Objective C bridge. I believe Objective C has no concept of optionals.
var purchaseCallbackHolder: RCTResponseSenderBlock? = nil
Is there a better way to do this? Thanks again
The transaction observer needs to be removed in a deinit method:
override init() {
super.init()
SKPaymentQueue.defaultQueue().addTransactionObserver(self)
}
deinit {
if SKPaymentQueue.canMakePayments() {
SKPaymentQueue.defaultQueue().removeTransactionObserver(self)
}
}
I looked up the errors I was getting in my stack trace and one of them:
StoreKit__NotifyObserverAboutChanges
Pointed me to this article:
A single bug in my StoreKit code that lost me 90% of IAP sales

Resources