Slow data return Parse iOS swift 3 - ios

I am using Parse version "1.14.4" iOS 10.3.2 and swift 3.
The query is slow whether local (he objects returned are pinned) or remote.
Thanks
let placeObject = PFObject(className:"PlaceObject")
let point = PFGeoPoint(latitude:self.PointGlobal.latitude, longitude:self.PointGlobal.longitude)
placeObject["location"] = point
let query = PFQuery(className:"CLocationObject")
// Interested in locations near user.
query.whereKey("location", nearGeoPoint:point)
// Limit what could be a lot of points.
query.limit = 200
let localQuery = (query.copy() as! PFQuery).fromLocalDatastore()
localQuery.findObjectsInBackground{
(objects: [PFObject]?, error: Error?) -> Void in
self.dataReturnedLocally = true
.....
if self.dataReturnedLocally{
print("local query with no error there was data already")
}
else {
print("getting data remotely")
query.findObjectsInBackground{
(objects: [PFObject]?, error: Error?) -> Void in
if error == nil {
if let objects = objects {

geo based queries are the slowest types of queries with MongoDB, unfortunately. Also, there are not automatically indexes based on location, making these extra slow, especially for large collections. So, your only real solution is to add indexes to your database to index the location, optimized for the location queries you'll need to make. Though keep in mind too many of these affects write speed.
Depending on your use case, it may be better to use withinMiles instead of nearGeoPoint. This will return fewer results, but will not take as long to run, either.

All queries in the LDS are slow at the moment as they are not indexed. The LDS stored an objectId / JSON representation of the data and all filtering is done in memory.

Related

How to query for samples from health kit that have an HKDevice

I want to query samples from HealthKit but in order to prevent inaccurate or manipulated data I don't want samples that were written to health by other apps. Does anyone have any idea what predicate I can use to filter out data from all apps or to only allow data from devices? Thanks in advance.
Edit: I've realized that apps can save data to health with an HKDevice included. So filtering out samples that don't have devices won't work.
If what you want to do is exclude manually entered data, see this answer: Ignore manual entries from Apple Health app as Data Source
Samples that were added to HealthKit by the user via Health will have the HKMetadataKeyWasUserEntered key.
You can filter out the results that are not stored by apple in your query instead of iterating all the results.
first, you need to get all the sources for your desired type.
let query = HKSourceQuery(sampleType: type,
samplePredicate: predicate) {
query, sources, error in
// error handling ...
// create a list of your desired sources
let desiredSources = sources?.filter {
!$0.bundleIdentifier.starts(with: "com.apple.health")
}
// now use that list as a predicate for your query
let sourcePredicate = HKQuery.predicateForObjects(from: desiredSources!)
// use this predicate to query for data
}
you can also combine other predicates using NSCompoundPredicate
I'm still open to suggestions and alternate solutions but here is my work-around since I was unable to figure out how to use a predicate to get the job done.
let datePredicate = HKQuery.predicateForSamples(withStart:Date(), end: nil, options: [])
let sampleQuery = HKAnchoredObjectQuery(type: sampleType,
predicate: predicate,
anchor: nil,
limit: Int(HKObjectQueryNoLimit)) { query,
samples,
deletedObjects,
anchor,
error in
if let error = error {
print("Error performing sample query: \(error.localizedDescription)")
return
}
guard let samples = samples as? [HKQuantitySample] else { return }
// this line filters out all samples that do not have a device
let samplesFromDevices = samples.filter {
$0.device != nil && $0.sourceRevision.source.bundleIdentifier.hasPrefix("com.apple.health")
}
doStuffWithMySamples(samplesFromDevices)
}
As you can see, I just filter the data once it comes through rather than doing it before-hand.
Edit: Seems like the sources listed in health are separated into apps and actual devices. Not 100% sure how they do this but it seems like the sources under the device section all have a bundle identifier prefixed with com.apple.health. Hopefully this works.

Swift Parse how to make app works offline?

I'm working on local parse with swift 3.0
I'm doing querys to get results from server. but if there's no connection it wont show last results we got because losing connection.
so what i want to do is to save query results to view it if there is no connection
this is the query:
var query = PFUser.query()
query = PFQuery(className: "_User")
// query?.fromLocalDatastore()
query!.whereKey("objectId", equalTo: PFUser.current()!.objectId!)
query!.findObjectsInBackground {
(objects , error) -> Void in
if error == nil {
for object in objects! {
self.usernamelbl.text = object["username"] as! String
if let userp = PFUser.current()?["photo"] as? PFFile {
userp.getDataInBackground {
(imageData, error) -> Void in
if error == nil {
self.profilepic.image = UIImage(data: imageData!)!
}
}
}
}
Now how can i save the results and view them offline also if app closed?
Any help will be appreciated
It's possible to set the caching policy of specific PFQuery calls. To save a copy to disk, and rely on that before making another network hit, you set the kPFCachePolicyCacheElseNetwork policy.
However according to this Parse question, there is apparently a pretty strict limit on the size these caches are allowed to be. I'm not sure if those still apply in the open source version of Parse, but if you want to save more information to disk, it might be appropriate to use a more dedicated data persistence framework, like Core Data, SQLite, or Realm (Full disclosure: I work for Realm. :) )
For the purposes of image files, I'd recommend you manually manage the caching of those on disk, instead of storing it in Parse's cache (Due to the size constraints). There are some great image caching libraries out there (Like PINCache) that make it very easy.

Core Data inserting and saving slow

I'm parsing data from a JSON file that has approximately 20000 objects. I've been running the time profiler to figure out where my bottlenecks are and speed up the parse and I've managed to reduce the parse time by 45%, however according to the time profiler 78% of my time is being taken by the context.save() and much of the heavy portions throughout the parse are sourcing from where I call NSEntityDescription.insertNewObjectForEntityForName.
Does anyone have any idea if theres any way to speed this up? I'm currently batching my saves every 5000 objects. I tried groupings of 100,1000,2000,5000,10000 and I found that 5000 was the most optimal on the device I'm running. I've read through the Core Data Programming Guide but have found most of the advice it gives is to optimizing fetching on large numbers of data and not parsing or inserting.
The answer could very well be, Core Data has its limitations, but I wanted to know if anyone has found ways to further optimize inserting thousands of objects.
UPDATE
As requested some sample code on how I handle parsing
class func parseCategories(data: NSDictionary, context: NSManagedObjectContext, completion: ((success: Bool) -> Void)) {
let totalCategories = data.allValues.count
var categoriesParsed = 0
for (index, category) in data.allValues.enumerate() {
let privateContext = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.PrivateQueueConcurrencyType)
privateContext.persistentStoreCoordinator = (UIApplication.sharedApplication().delegate as! AppDelegate).persistentStoreCoordinator!
privateContext.mergePolicy = NSMergeByPropertyStoreTrumpMergePolicy
//Do the parsing for this iteration on a separate background thread
privateContext.performBlock({ () -> Void in
guard let categoryData = category.valueForKey("category") as? NSArray else{
print("Fatal Error: could not parse the category data into an NSArray. This should never happen")
completion(success: false)
return
}
let newCategory: Categories?
do {
let newCategory = NSEntityDescription.insertNewObjectForEntityForName("Categories", inManagedObjectContext: privateContext) as! Categories
newCategory.name = category.valueForKey("name") as? String ?? ""
newCategory.sortOrder = category.valueForKey("sortOrder") as? NSNumber ?? -1
SubCategory.parseSubcategories(category.valueForKey("subcategories") as! NSArray, parentCategory: newCategory, context: privateContext)
} catch {
print("Could not create the Category object as expected \(error)")
completion(success: false)
}
do {
print("Num Objects Inserted: \(privateContext.insertedObjects.count)") //Num is between 3-5k
try privateContext.save()
} catch {
completion(success: false)
return
}
categoriesParsed+=1
if categoriesParsed == totalCategories{
completion(success: true)
}
})
}
}
In the above code, I look through the top level data objects which I call a "Category", I spin off background threads for each object to parse concurrently. There are only 3 of this top level object, so it doesn't get too thread heavy.
Each Category has SubCategories, and several other levels of child objects which yield several thousand objects each getting inserted.
My core data stack is configured with one sqlite database the standard way that is configured when you create an app with CoreData
One reason is that you're saving the managed object context in each single iteration, which is expensive and not needed. Save it after the last item has been inserted.

Querying from Parse

Basically I am trying to display 3 users, I am querying from the _User class the following: username, profilePicture & Name.
After that I would like to query the last photo they posted from the Posts class.
Here is how I have setup my code :
let userQuery = PFQuery(className: "_User")
userQuery.limit = 3
userQuery.addDescendingOrder("createdAt")
userQuery.findObjectsInBackgroundWithBlock ({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.profilePicArray.removeAll(keepCapacity: false)
self.usernameArray.removeAll(keepCapacity: false)
self.fullnameArray.removeAll(keepCapacity: false)
self.uuidArray.removeAll(keepCapacity: false)
for object in objects! {
self.profilePicArray.append(object.valueForKey("profilePicture") as! PFFile)
self.usernameArray.append(object.valueForKey("username") as! String)
self.fullnameArray.append(object.valueForKey("firstname") as! String)
self.uuidArray.append(object.valueForKey("uuid") as! String)
}
let imageQuery = PFQuery(className: "Posts")
imageQuery.whereKey("username", containedIn: self.usernameArray)
imageQuery.findObjectsInBackgroundWithBlock({ (objects:[PFObject]?, error:NSError?) -> Void in
if error == nil {
self.lastPicArray.removeAll(keepCapacity: false)
for object in objects! {
self.lastPicArray.append(object.valueForKey("image") as! PFFile)
}
self.collectionView.reloadData()
} else {
print(error!.localizedDescription)
}
})
} else {
print(error!.localizedDescription)
}
})
}
But when I run it, it isn't showing the correct image for the user showing...I cannot get my head round it, it's been driving me mad for several hours now!!
The background thread isn't a problem for fetching your images, but your image query is not set to return images in any specific order, nor does it seem to limit how many photos will come back for each user (unless you have it set up so that the Post object can only hold one post per user). You could handle this in a few different ways, but the easiest way might be to sort your object array in some predictable order, using the username as the sort key, and then when you get your images back you can sort them in the same order based on their username property before putting them into the array that drives the collectionView.
One other note - you'll want to put your .reloadData() call back on the main thread or it'll happen at an unpredictable time.
It might cause by data transferring in different time cost since you use findObjectsInBackgroundWithBlock. Threads running in background are hard to control. So the order of images in imageQuery objects is different with usernameArray. Or findObjectsInBackgroundWithBlock doesn't guarantee that data will be fetched in same order with keys set. Not 100% sure about this.
But try putting the image fetching procedure inside the first for object in objects! loop. It might be able to solve your problem.
For sorting answer from creeperspeak, you should be able to get username back from imageQuery class just like object.valueForKey("username") in your first loop. Then match them with self.usernameArray.

Does the Parse query download the same object multiple times?

Im beginning to scratch the surface of Swift, Parse and IOS, and I had a question regarding how parse performs its findObjectsInBackgroundWithBlock method
In the little snippet below, can someone tell me, if my app will continuously keep downloading 100 objects?
query.whereKey("location", nearGeoPoint: mygeopoint, withinMiles: 20)
query.limit = 100
query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in
if error != nil {
print(error)
}else {
for o in objects! {
// do some stuff
}
}
}
As a follow up question: lets say I wanted to maintain a reference of objects seen so far, so I don't have to download them again, thereby getting only new objects, how do I do that?
As a follow up to the follow up question: lets say if there are no new objects from the original query and I wanted to execute a new query like
query.whereKey("city", containsString: "San Francisco")
(continue reading) to reflect the logic that, if there are no new objects within 20 miles around me, send me objects that match city = San Francisco : How would i do that?
I have been also reading about PromiseKit - is that something that would be applicable in a scenario like this?

Resources