Trouble retrieving contacts already on the app from Parse - ios

I'm looking to do something similar to most popular apps (e.g. Snapchat) where users verify their phone numbers, then give the app permission to their Contacts so friends who have already signed up can be displayed.
I'd like two table view sections. One section for contacts NOT on the app, and another section for contacts whose phone numbers matched one in the Parse column, "phoneNumber". The second section is causing this error telling me I'm running multiple queries at once.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'This query has an outstanding network connection. You have to wait until it's done.'
Here is the code I'm trying to run:
let query = PFUser.query()
for person in self.people! { // iterate through phone contacts
let numbers = person.phoneNumbers
let primaryNumber = numbers?.map({$0.value})[0] // pull contact's first phone number (i.e. main number)
dispatch_async(dispatch_get_main_queue()) {
query?.whereKey("phoneNumber", equalTo: primaryNumber!)
query?.getFirstObjectInBackgroundWithBlock({ (object, error) -> Void in
self.contactsOnTheApp.addObject(object!) // create array of users already on the app to then show in table view section
})
}
}
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}

You are defining query outside the loop and then perform a background query inside the loop. You modify the existing query on each loop iteration but as the first background query hasn't completed when you modify the query for the next search you get the error message.
You can move the let query=PFUser.query() inside the for loop but it would be more efficient to create an array of phone numbers and use a containedIn condition on a single query and then loop through the returned results.
Also you don't need the dispatch_async since you are using the background Parse call and you don't need to dispatch the reload on the main queue. In fact, you are executing the reload too early because your queries won't be complete. You need to reload from the query completion block. This will be easier if you have a single query too.

Related

iOS: CloudKit perform(query: ) does nothing - closure not executed

I am in process in adding CloudKit to my app to enable iCloud sync. But I ran into problem with my method, that executes query with perform method on private database.
My method worked fine, I then changed a few related methods (just with check if iCloud is available) and suddenly my perform method does nothing. By nothing I mean that nothing in perform(query: ) closure gets executed. I have breakpoint on the first line and others on the next lines but never manage to hit them.
private static func getAppDetailsFromCloud(completion: #escaping (_ appDetails: [CloudAppDetails]?) -> Void) {
var cloudAppDetails = [CloudAppDetails]()
let privateDatabase = CKContainer.default().privateCloudDatabase
let query = CKQuery(recordType: APPID_Type, predicate: NSPredicate(format: "TRUEPREDICATE"))
privateDatabase.perform(query, inZoneWith: nil) { (records, error) in
if let error = error {
print(error)
completion(nil)
} else {
if let records = records {
for record in records {
let appId = record.object(forKey: APPID_ID_Property) as? Int
let isDeleted = record.object(forKey: APPID_ISDELETED_Property) as? Int
if let appId = appId, let isDeleted = isDeleted {
cloudAppDetails.append(CloudAppDetails(id: appId, isDeleted: isDeleted == 1))
}
}
completion(cloudAppDetails)
return
}
}
completion(nil)
}
}
My problem starts at privateDatabase.perform line, after that no breakpoints are hit and my execution moves to function which called this one getAppDetailsFromCloud. There is no error...
This is my first time implementing CloudKit and I have no idea why nothing happens in the closure above.
Thanks for help.
EDIT: Forgot to mention that this metod used to work fine and I was able to get records from iCloud. I have not made any edits to it and now it does not work as described :/
EDIT 2: When I run the app without debugger attached then everything works flawlessly. I can sync all data between devices as expected. When I try to debug the code, then I once again get no records from iCloud.
In the completion handler shown here, if there's no error and no results are found, execution will fall through and quietly exit. So, there are two possible conditions happening here: the query isn't running or the query isn't finding any results. I'd perform the following investigative steps, in order:
Check your .entitlements file for the key com.apple.dev.icloud-container-environment. If this key isn't present, then builds from xcode will utilize the development environment. If this key is set, then builds from xcode will access the environment pointed to by this key. (Users that installed this app from Testflight or the app store will always use the production environment).
Open the cloudkit dashboard in the web browser and validate that the records you expect are indeed present in the environment indicated by step 1 and the container you expect. If the records aren't there, then you've found your problem.
If the records appear as expected in the dashboard, then place the breakpoint on the .perform line. If the query is not being called when you expected, then you need to look earlier in the call stack... who was expected to call this function?
If the .perform is being called as expected, then add an else to the if let record statement. Put a breakpoint in the else block. If that fires, then the query ran but found no records.
If, after the above steps, you find that the completion handler absolutely isn't executed, this suggests a malformed query. Try running the query by hand using the cloudkit dashboard and observing the results.
The closure executes asynchronously and usually you need to wait few seconds.
Take into account you can't debug many threads in same way as single. Bcs debugger will not hit breakpoint in closure while you staying in your main thread.
2019, I encountered this issue while working on my CloudKit tasks. Thunk's selected answer didn't help me, so I guess I'm gonna share here my magic. I got the idea of removing the breakpoints and print the results instead. And it worked. But I still need to use breakpoints inside the closure. Well, what I had to do is restart the Xcode. You know the drill in iOS development, if something's not right, restart the Xcode, reconnect the device, and whatnot.

Deleting CloudKit Records Swift 4

I am having issues deleting CloudKit records. This is my first time dealing with the API and apparently there are two ways to do this.
Saving records is straight forward and ostensibly so is deleting them, except this doesn't do it:
func deleteRecords() {
let recordID = record.recordID
publicDatabase.delete(withRecordID: recordID) { (recordID, error) in
guard let recordID = recordID else {
print(error!.localizedDescription)
return
}
print("Record \(recordID) was successfully deleted")
}
}
I understand using a ckModifyRecordsOperation is another way to do this but this is a batch operation. I only need to delete one record at a time. Here's my code for that:
func batchDelete() {
let recordIDsToDelete = [CKRecordID]()
let operation = CKModifyRecordsOperation(recordsToSave: nil, recordIDsToDelete: recordIDsToDelete)
operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordIDs, error in
// handle errors here
}
publicDatabase.add(operation)
print("Batch \(recordIDsToDelete) record was successfully deleted")
}
Neither of these separately or together are working for me.
You are correct, there are two ways. The first way you describe is referred to by Apple as a "convenience" function. If you're just deleting a single record, it's probably the quickest option to implement. However, each convenience operation conducts its own trip to the database. If you loop through thousands of records and delete them individually with the convenience function, you're going to use a lot of your cloudKit quota making a series of individual calls.
The second option, the operation, let's you batch the deletes and send them in one operation. Generally, this will be a more efficient use of your cloudkit quotas. But, according to Apple docs, there's no technical difference between the two; the the convenience function is just a wrapper to the operation.
Now, to your specific problem, the operation has two separate completion blocks: perRecordCompletionBlock and modifyRecordsCompletionBlock. As the names imply, the first block is called after each and every record is processed in the operation and that's where errors are surfaced. Make sure you implement perRecordCompletionBlock and check for errors there (and then you'll have to decide if your error handling steps belong in the perRecordCompletionBlock or the modifyRecordsCompletionBlock).
Finally, if the operation (or convenience function) is running and you confirm that the completion blocks fire without errors but the record still doesn't delete, this typically indicates you passed nil rather than a valid record to the deletion.

Having to call fetch twice from CoreData

Both on simulator and my real device, an array of strings is saved upon app termination. When I restart the app and fetchRequest for my persisted data (either from a viewDidLoad or a manual button action), I get an empty array on the first try. It isn't until the second time I fetchRequest that I finally get my data.
The funny thing is that there doesn't seem to be a time discrepancy involved in this issue. I tried setting various timeouts before trying to fetch the second time. It doesn't matter whether I wait 10 seconds to a minute -- or even immediately after the first fetch; the data is only fetched on the second try.
I'm having to use this code to fetch my data:
var results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
while (results.isEmpty) {
results = try self.context.fetch(fetchRequest) as! [NSManagedObject]
}
return results
For my sanity's sake, here's a checklist:
I'm initializing the Core Data Stack using boilerplate code from Apple: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/CoreData/InitializingtheCoreDataStack.html#//apple_ref/doc/uid/TP40001075-CH4-SW1
I'm putting my single DataController instance in a static variable at the top of my class private static let context: NSManagedObjectContext = DataController().managedObjectContext
I'm successfully saving my context and can retrieve the items without any issue in a single session; but upon trying to fetch on the first try in a subsequent session, I get back an empty array (and there lies the issue).
Note** I forgot to mention that I'm building a framework. I am using CoreData with the framework's bundle identifier and using the model contained in the framework, so I want to avoid having to use logic outside of the framework (other than initalizing the framework in the appDelegate).
The Core Data stack should be initialized in applicationDidFinishLaunchingWithOptions located in appDelegate.swift because the psc is added after you're trying to fetch your data.
That boilerplate code from Apple includes:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
/* ... */
do {
try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)
} catch {
fatalError("Error migrating store: \(error)")
}
}
The saved data isn't available until the addPersistentStoreWithType call finishes, and that's happening asynchronously on a different queue. It'll finish at some point but your code above is executing before that happens. What you're seeing isn't surprising-- you're basically looping until the async call finishes.
You need to somehow delay your fetch until the persistent store has been loaded. There are a couple of possibilities:
Do something sort of like what you're already doing. I'd prefer to look at the persistent store coordinator's persistentStores property to see if any stores have been loaded rather than repeatedly trying to fetch.
Post a notification after the persistent store is loaded, and do your fetch when the notification happens.

FEventType.ChildAdded event only fired once

my firebase data structure looks like the following
user
|__{user_id}
|__userMatch
|__{userMatchId}
|__createdAt: <UNIX time in milliseconds>
I'm trying to listen for the child added event under userMatch since a particular given time. Here's my swift code:
func listenForNewUserMatches(since: NSDate) -> UInt? {
NSLog("listenForNewUserMatches since: \(since)")
var handle:UInt?
let userMatchRef = usersRef.childByAppendingPath("\(user.objectId!)/userMatch")
var query = userMatchRef.queryOrderedByChild("createdAt");
query = query.queryStartingAtValue(since.timeIntervalSince1970 * 1000)
handle = query.observeEventType(FEventType.ChildAdded, withBlock: { snapshot in
let userMatchId = snapshot.key
NSLog("New firebase UserMatch created \(userMatchId)")
}, withCancelBlock: { error in
NSLog("Error listening for new userMatches: \(error)")
})
return handle
}
What's happening is that the event call back is called only once. Subsequent data insertion under userMatch didn't trigger the call. Sort of behaves like observeSingleEventOfType
I have the following data inserted into firebase under user/{some-id}/userMatch:
QGgmQnDLUB
createdAt: 1448934387867
bMfJH1bzNs  
createdAt: 1448934354943
Here are the logs:
2015-11-30 17:32:38.632 listenForNewUserMatches since:2015-12-01 01:32:37 +0000
2015-11-30 17:45:55.163 New firebase UserMatch created bMfJH1bzNs
The call back was fired for bMfJH1bzNs but not for QGgmQnDLUB which was added at a later time. It's very consistent: after opening the app, it only fires for the first event. Not sure what I'm doing wrong here.
Update: Actually the behavior is not very consistent. Sometimes the call back is not fired at all, not even once. But since I persist the since time I should use when calling listenForNewUserMatches function. If I kill the app and restart the app, the callback will get fired (listenForNewUserMatches is called upon app start), for the childAdded event before I killed the app. This happens very consistently (callback always called upon kill-restart the app for events that happened prior to killing the app).
Update 2: Don't know why, but if I add queryLimitedToLast to the query, it works all the time now. I mean, by changing userMatchRef.queryOrderedByChild("createdAt") to userMatchRef.queryOrderedByChild("createdAt").queryLimitedToLast(10), it's working now. 10 is just an arbitrary number I chose.
I think the issue comes from the nature of time based data.
You created a query that says: "Get me all the matches that happened after now." This should work when the app is running and new data comes in like bMfJH1bzNs. But older data like QGgmQnDLUB won't show up.
Then when you run again, the since.timeIntervalSince1970 has changed to a later date. Now neither of the objects before will show up in your query.
When you changed your query to use queryLimitedToLast you avoided this issue because you're no longer querying based on time. Now your query says: "Get me the last ten children at this location."
As long as there is data at that location you'll always receive data in the callback.
So you either need to ensure that since.timeIntervalSince1970 is always earlier than the data you expect to come back, or use queryLimitedToLast.

Waiting for geocoding loop to complete

So I understand that geocoding in iOS is an asynchronous method, and right now I'm working on an app that accesses a list of address from Parse (for restaurants) and performs forward geocoding on each address. Ideally I'd like this operation to be performed before a table is populated after a couple days of struggles this is just not happening for me.
My question is how do I get an iteration of forward geocoding to be completed prior to anything else happening in my app?
I've trying looking into grand central dispatch methods and I tried following this tutorial but I have got no luck:
http://www.raywenderlich.com/79150/grand-central-dispatch-tutorial-swift-part-2
Here is my code:
As you will see I'm trying to put found CLLocations in
var storeDict:NSDictionary = [CLLocation:PFObject]()
override func queryForTable() -> PFQuery! {
var query:PFQuery = PFQuery(className: self.parseClassName)
query.whereKey("Food", equalTo: foodName)
var prices = query.findObjects()
var i = 0
println(prices.count)
let geoCoder = CLGeocoder()
for price in prices {
var location:String = price.objectForKey("Address") as String
geoCoder.geocodeAddressString(location, completionHandler:
{(placemarks: [AnyObject]!, error: NSError!) in
if error != nil {
println("Geocode failed with error: \(error.localizedDescription)")
} else if placemarks.count > 0 {
var placemark = placemarks[0] as CLPlacemark
var location = placemark.location
var coordinateLocation:CLLocation = CLLocation(latitude: location.coordinate.latitude, longitude: location.coordinate.longitude) as CLLocation
print(price)
print(coordinateLocation)
self.Restaurant[coordinateLocation] = price as? PFObject
print(i)
i++
}
})
println("check")
}
return query
}
First, you might want to notify the user that they can't do anything in the app until the data has finished being loaded (and geocoded). You could add a semi-transparent view with a spinning wheel over it to prevent the user from interacting with the app.
The table should have absolutely no idea where you are in the process -- whether you've started geocoding, finished geocoding, or how long ago you ran the geocoding. It should have no idea whether you even queried to get the restaurants. All it knows is its datasource, and if that datasource has objects, then it will use it to populate the rows in the table.
Say the datasource is an array of Restaurant objects. If the array is empty, then the table will be empty. You can do KVO on the array, so that whenever the datasource is updated, reloadData will be called on the tableView.
Now you've separated out the table as a separate problem that you've handled. Not onto querying and geocoding.
Currently, you're not querying Parse in background with block, but you're literally halting the program until the query finishes. I understand why you're electing to do that since you're concerned about doing nothing until the query finishes, but it would be a lot better to execute in background.
Once the query finishes, you loop through the resulting objects and geocode one by one. Just a word of caution, Apple does not allow you to geocode a ton of objects at a time -- they will throttle you, so I would limit the query to only return the amount of objects you need. When an object is finished being geocoded, add it to the datasource. This will trigger the reload of the table, and your data will appear.
Say now that you queried 20 objects. Each time the geocoding completes, your tableview will be reloaded. It might be better to wait until the geocoding completes on all 20 objects before calling reload. You can actually add each geocode operation onto a queue with each operation adding their geocoded object to some temporary array, and then add an operation that updates your datasource with the temporary array. At that moment, the table will be updated with the new data. Note that the downside of doing it this way is that some addresses might take longer the geocode than others, so rather than displaying whatever data it was able to geocode thus far, it will wait until everything has been fully geocoded.
Lastly, you could have the query run in background and have the geocoding occur in its completion block. If the view just loaded for the first time, it can show the spinning wheel until the datasource is updated. When the datasource is updated, the spinning wheel is removed.

Resources