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 {
}
}
}
Related
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()
}
)}
}
I am trying to implement a table view that displays books information in every cell. I get this information from the API. Here is my code:
class ViewController: UIViewController {
#IBOutlet weak var allBooksTable: UITableView!
var bookList:myList? = nil {
didSet {
allBooksTable.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.allBooksTable.dataSource = self
self.allBooksTable.delegate = self
let url = URL(string: "https://ipvefa0rg0.execute-api.us-east-1.amazonaws.com/dev/books?lang=fr&term=self")!
var request = URLRequest(url: url)
request.setValue(
"jFXzWHx7SkK6",
forHTTPHeaderField: "api-key"
)
request.httpMethod = "GET"
let session = URLSession.shared
let task = session.dataTask(with: request) { (data, response, error) in
if let error = error {
print(error.localizedDescription)
} else if let data = data {
do {
let decodedData = try JSONDecoder().decode(myList.self,
from: data)
self.bookList = decodedData
print("user: ", decodedData.list[0].imageLinks.thumbnail)
print("===================================")
} catch let DecodingError.dataCorrupted(context) {
print(context)
} catch let DecodingError.keyNotFound(key, context) {
print("Key '\(key)' not found:", context.debugDescription)
//print("codingPath:", context.codingPath)
} catch let DecodingError.valueNotFound(value, context) {
print("Value '\(value)' not found:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch let DecodingError.typeMismatch(type, context) {
print("Type '\(type)' mismatch:", context.debugDescription)
print("codingPath:", context.codingPath)
} catch {
print("error: ", error)
}
} else {
// Handle unexpected error
}
}
task.resume()
}
}
extension ViewController: UITableViewDataSource , UITableViewDelegate {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 200
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell") as! bookTableViewCell
cell.bookName.text = bookList?.list[indexPath.row].title
if bookList?.list[indexPath.row].imageLinks.smallThumbnail != nil{
//cell.img.load(url: (bookList?.list[indexPath.row].imageLinks.smallThumbnail)!)
print((bookList?.list[indexPath.row].imageLinks.smallThumbnail)!)
cell.img.downloaded(from: (bookList?.list[indexPath.row].imageLinks.smallThumbnail)!)
}
return cell
}
}
extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() { [weak self] in
self?.image = image
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode)
}
}
When I run the app, it only shows my the first book and then crashes with the following error.
2020-10-15 23:12:00.953424+1100 MSA[50129:2517792] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.'
*** First throw call stack:
(
0 CoreFoundation 0x00007fff2043a126 __exceptionPreprocess + 242
1 libobjc.A.dylib 0x00007fff20177f78 objc_exception_throw + 48
2 CoreAutoLayout 0x00007fff58010d41 -[NSISEngine tryToOptimizeReturningMutuallyExclusiveConstraints] + 0
3 CoreAutoLayout 0x00007fff58010fcd -[NSISEngine withBehaviors:performModifications:] + 25
4 UIKitCore 0x00007fff24ac64ad -[UIView(AdditionalLayoutSupport) _recursiveUpdateConstraintsIfNeededCollectingViews:forSecondPass:] + 112
5 UIKitCore 0x00007fff24ac6136 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededCollectingViews:forSecondPass:] + 827
6 UIKitCore 0x00007fff24ac6a08 __100-[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:]_block_invoke + 85
7 UIKitCore 0x00007fff24ac6594 -[UIView(AdditionalLayoutSupport) _updateConstraintsIfNeededWithViewForVariableChangeNotifications:] + 154
8 UIKitCore 0x00007fff24ac7482 -[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeededWithViewForVariableChangeNotifications:] + 393
9 UIKitCore 0x00007fff24ba9ad6 -[UIView _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 275
10 UIKitCore 0x00007fff24bbda37 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2979
11 QuartzCore 0x00007fff27a3dd87 -[CALayer layoutSublayers] + 258
12 QuartzCore 0x00007fff27a44239 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 575
13 UIKitCore 0x00007fff24ba8fe9 -[UIView(Hierarchy) layoutBelowIfNeeded] + 573
14 UIKitCore 0x00007fff24bb0479 +[UIView(Animation) performWithoutAnimation:] + 84
15 UIKitCore 0x00007fff248954d0 -[UITableView _createPreparedCellForGlobalRow:withIndexPath:willDisplay:] + 1300
16 UIKitCore 0x00007fff2485e8bb -[UITableView _updateVisibleCellsNow:] + 2942
17 UIKitCore 0x00007fff2487e6e6 -[UITableView layoutSubviews] + 237
18 UIKitCore 0x00007fff24bbd9ce -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2874
19 QuartzCore 0x00007fff27a3dd87 -[CALayer layoutSublayers] + 258
20 QuartzCore 0x00007fff27a44239 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 575
21 QuartzCore 0x00007fff27a4ff91 _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 65
22 QuartzCore 0x00007fff27990078 _ZN2CA7Context18commit_transactionEPNS_11TransactionEdPd + 496
23 QuartzCore 0x00007fff279c6e13 _ZN2CA11Transaction6commitEv + 783
24 QuartzCore 0x00007fff279c7616 _ZN2CA11Transaction14release_threadEPv + 210
25 libsystem_pthread.dylib 0x00007fff5dcda054 _pthread_tsd_cleanup + 551
26 libsystem_pthread.dylib 0x00007fff5dcdc512 _pthread_exit + 70
27 libsystem_pthread.dylib 0x00007fff5dcd9ddd _pthread_wqthread_exit + 77
28 libsystem_pthread.dylib 0x00007fff5dcd8afc _pthread_wqthread + 481
29 libsystem_pthread.dylib 0x00007fff5dcd7b77 start_wqthread + 15
)
libc++abi.dylib: terminating with uncaught exception of type NSException
Could you please help me to understand what's going on here? I have looked over for similar threads, but most of the solutions are specific to the question.
if a UIElement getting update in background thread thats why applications getting crashed so whenever you want to update UI you need to update that UI Element in main thread, and your session data task is running in background thread and there you set the value bookList but when bookList is set there is an property observer which is reload the tableview in background thread so there is two solutions.
inside your api calling functions you need to set the property in main thread like this:-
DispatchQueue.main.async {
self.bookList = decodedData
}
inside your property observer you need to do like that
var bookList:myList? = nil {
didSet {
DispatchQueue.main.async {
allBooksTable.reloadData()
}
}
}
Enjoy:-
The issue is that you set bookList from a background thread (since URLSession.dataTask calls its completion on a background thread) and in the didSet of bookList, you update the UI. You should dispatch the UI update to the main thread.
var bookList:myList? = nil {
didSet {
DispatchQueue.main.async {
allBooksTable.reloadData()
}
}
}
You can set the bookList on main thread like below...
DispatchQueue.main.async {
self.bookList = decodedData
}
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)
}
}
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
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