Does the Parse query download the same object multiple times? - ios

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?

Related

Slow data return Parse iOS swift 3

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.

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.

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.

Parse iOS SDK 1.8.5 causes try throw & PFObject conditional cast errors

After updating to the latest Parse SDK 1.8.5, I am receiving two errors surrounding the findObjectsInBackgroundWithBlock function. Two errors return on the same line:
if let objects = query.findObjects() as? [PFObject]
I have tried changing it to as [PFObject]? with no luck. The errors are as follows:
Call can throw, but it is not marked with 'try' and the error is not handled
AND Conditional cast from '[PFObject]' to '[PFObject]' always succeeds
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
if let objects = query.findObjects() as? [PFObject] {
for object in objects {
object.deleteInBackgroundWithBlock{ (success, error) -> Void in
if (success) {
print("Worked")
print(objects.count)
self.viewDidAppear(true)
} else {
}
}
I have researched similar problems, and tried to fix it to query.findObjectsInBackgroundWithBlock { (objects, error) -> Void in but this has done nothing either.
I am not sure how to fix either of these errors, and am very confused as to why they suddenly popped up. All of my functions were running perfectly, and have been for weeks, and suddenly these errors have popped up.
changing it from:
if let objects = query.findObjects() as? [PFObject]
to
if let objects = objects as [PFObject]?
seemed to fix the problem, but I am not sure if it is now going to run properly.
For other lines with the same error, XCode suggested changing it to:
self.rooms = (results as [PFObject]?)!
and that took away the error. I am pretty new to Swift coding, so I am not sure exactly what has happened, if anyone has any insight?
Using findObjects() is much more difficult with the new update. This is because with Xcode 7 and Swift 2, Swift became better at handling errors and Parse took advantage of these new methods to allow findObjects() to throw an error. It would be your job to handle the error and provide other code to run in case of such an error. In addition to these new requirements, findObjects() runs synchronously and will slow down your app. You should be using findObjectsInBackground() which runs asynchronously. This will solve both of your problems. If you need hep implementing this function, there is a lot of documentation and question/answers online.
Removing as [PFObject]? from if let objects = objects as [PFObject]? should solve your conditional cast error.

Repeat code for whole array

I am using some Facebook IDs in my app, and I have an array of serveral ID's, the array can be 10 numbers but can also be 500 numbers..
Right now the numbers are displayed in a tableview, and I want all the results there too, so they need to be in an array.
let profileUrl = NSURL(string:"http://www.facebook.com/" + newArray[0])!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
// Will happen when task completes
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
dispatch_async(dispatch_get_main_queue(),
{ () -> Void in
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
//print(websiteArray[1])
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
print(secondArray[0])
})
}
}
this code takes the first number of the array, goes to facebook.com/[the actual number], and then downloads the data and splits the data into pieces, so that the data that I want it in the secondArray[0]. I want to do this for every number of the array, take the result data and put it back into an array. I have no idea how to do this because you don't know how much numbers there are gonna be etc, does someone has a good solution for this?
Any help would be appreciated, really!
Thanks
You have several problems here, and you should take them one at at a time to build up to your solution.
First, forget the table for the moment. Don't worry at all about how you're going to display these results. Just focus on getting the results in a simple form, and then you'll go back and convert that simple form into something easy to display, and then you'll display it.
So first, we want this in a simple form. That's a little bit complicated because it's all asynchronous. But that's not too hard to fix.
func fetchTitle(identifier: String, completion: (title: String) -> Void) {
let profileUrl = NSURL(string:"http://www.facebook.com/" + identifier)!
let task = NSURLSession.sharedSession().dataTaskWithURL(profileUrl) {
(data, response, error) -> Void in
if let urlContent = data {
let webContent = NSString(data: urlContent, encoding: NSUTF8StringEncoding)
let websiteArray = webContent!.componentsSeparatedByString("pageTitle\">")
let secondArray = websiteArray[1].componentsSeparatedByString("</title>")
let title = secondArray[0]
completion(title: title)
}
}
task.resume()
}
Now this is still pretty bad code because it doesn't handle errors at all, but it's a starting point, and the most important parts are here. A function that takes a string, and when it's done fetching things, calls some completion handler.
(Regarding error handling, note how many places this code would crash if it were returned surprising data. Maybe the data you get isn't a proper string. Maybe it's not formatted like you think it is. Every time you use ! or subscript an array, you run the risk of crashing. Try to minimize those.)
So you might then wrap it up in something like:
var titles = [String]()
let identifiers = ["1","2","3"]
let queue = dispatch_queue_create("titles", DISPATCH_QUEUE_SERIAL)
dispatch_apply(identifiers.count, queue) { index in
let identifier = identifiers[index]
fetchTitle(identifier) { title in
dispatch_async(queue) {
titles.append(title)
}
}
}
This is just code to get you on the right track and start studying the right things. It certainly would need work to be production quality (particularly to handle errors).
Once you have something that returns your titles correctly, you should be able to write a program that does nothing but take a list of identifiers and prints out the list of titles. Then you can add code to integrate that list into your tableview. Keep the parts separate. The titles are the Model. The table is the View. Read up on the Model-View-Controller paradigm, and you'll be in good shape.
To repeat code for whole array put your code in a loop and run that loop from 0 to array.count-1
You don't need to know how many items there will be an array. You can just get the count at run time array.count here array is your array.
I hope this is what you wanted to know, your question doesn't make much sense though.

Resources