Parse - Asynchronous Block Call - ios

I have the following code that checks if username is taken or not in Parse database. The only problem is that the code is executing and execution continues to next code and then returns the result of this block.
This is leading to a problem with a check I have afterwards on true/false value.
How can I execute the same code below and wait for it to get a result. and based on that, proceed?
let query = PFQuery(className:"User")
query.whereKey("appUsername", equalTo:username)
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
print("test username: " + username);
if error == nil {
// The find succeeded.
usernameTaken = true;
} else {
// Log details of the failure
usernameTaken = false;
}
}

You know, the PFQuery class, and the whole Parse library, has a detailed documentation. It should have taken you no more than 5 seconds to find that next to the findObjectsInBackgroundWithBlock, there's a findObjects: method that will return the data directly:
https://parse.com/docs/ios/api/Classes/PFQuery.html#//api/name/findObjects
It is however generally a very bad idea to use synchronous requests, especially from the main thread, as this will cause your UI to freeze while the request is made (which is usually quick, but can take a lot longer if the network connection is slow or unreliable). You would be a lot better of extending the completion block paradigm to your own code to propagate the request.

First of all check this: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID94 to better understand how it's works
You can use some completion block in function that include your code like this:
func result(completion block: (result: Bool) -> Void) {
let query = PFQuery(className:"User")
query.whereKey("appUsername", equalTo:username)
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
print("test username: " + username);
if error == nil {
// retun true with completion block
block(result: true)
} else {
// return false with completion block
block(result: false)
}
}
}
And you can use it like this:
...
result(completion: {(result: Bool) in
// Here code that use result from your query
...
})
...
Or you can call some your function from findObjectsInBackgroundWithBlock that will use result from query:
func someFunc(result: Bool) {
// Here code that use result from your query
...
}
...
let query = PFQuery(className:"User")
query.whereKey("appUsername", equalTo:username)
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
print("test username: " + username);
if error == nil {
self.someFunc(true)
} else {
self.someFunc(false)
}
}

Ok so I fixed the execution but placing the check of the boolean variable usernameTaken in the if condition. Things worked after that.

Related

Running Parse code after another has finished execution

I'm trying to query a class of objects with specific ID's to delete. When I run it however, I get an error for "This query has an outstanding network connection. You have to wait until it's done". I assume this is because I'm trying to delete an object while I'm still accessing it.
I've provided the deletion code in the closure because I'm assuming the closure statements only execute after the function call finishes, yet it's still giving me the error. I've also tried using DispatchGroups because it does seem like a concurrency issue, but I'm not too familiar with their usage yet. Here's my code:
let idList = [...] // Some list of ID's I would like to remove
let query = PFQuery(className: "Pictures")
for id in idList {
query.getObjectInBackground(withId: id) { (object: PFObject?, error:
Error?) in
if error == nil {
img?.deleteInBackground() { (success, error: Error?) ...
}
}
I'm expecting each object associated with an ID in my original IdList to be deleted from the Parse backend. However, it seems that getObjectInbackground() and deleteInBackground() are clashing. If anyone could provide some advice, that would be wonderful!
You can try
var idList = [...]
func delete(_ id:Int) {
query.getObjectInBackground(withId: id) { (object: PFObject?, error: Error?) in
if error == nil {
img?.deleteInBackground() { (success, error: Error?) ...
}
idList = Array(idList.dropFirst())
if !idList.isEmpty {
delete(idList.first!)
}
}
}
Initially call it like
delete(idList.first!)

Parse query results aren't being added (appended) to a local array in iOS Swift

Could anyone tell me why my startingPoints array is still at 0 elements? I know that I am getting objects returned during the query, because that print statement prints out each query result, however it seems like those objects are not getting appended to my local array. I've included the code snippet below...
func buildStartSpots() -> Void {
let queryStartingPoints = PFQuery(className: "CarpoolSpots")
queryStartingPoints.whereKey("spotCityIndex", equalTo: self.startingCity)
queryStartingPoints.findObjectsInBackgroundWithBlock{(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
print("starting point: \(object)")
self.startingPoints.append(object)
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
print("starting points")
dump(self.startingPoints)
}
While I have no experience in Parse, the block is asynchronously executed and likely non-blocking as dictated by the method name of the API call. Therefore, it is not guaranteed that the data would be available at the time you call dump, since the background thread might still be doing its work.
The only place that the data is guaranteed to be available at is the completion block you supplied to the API call. So you might need some ways to notify changes to others, e.g. post an NSNotification or use event stream constructs from third party libraries (e.g. ReactiveCocoa, RxSwift).
When you try to access the array, you need to use it within the closure:
func buildStartSpots() -> Void {
let queryStartingPoints = PFQuery(className: "CarpoolSpots")
queryStartingPoints.whereKey("spotCityIndex", equalTo: self.startingCity)
queryStartingPoints.findObjectsInBackgroundWithBlock{(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for object in objects! {
print("starting point: \(object)")
**self.startingPoints.append(object)**
}
//use it here
startingPoints xxx
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
}
}
print("starting points")
dump(self.startingPoints)
}
I am able to get the application functioning as intended and will close this answer out.
It seems as though that the startingPoints array is not empty, and the values I need can be accessed from a different function within that same class.
The code snippet I am using to access my locally stored query results array is here:
for object in self.startingPoints {
let startingLat = object["spotLatitude"] as! Double
let startingLong = object["spotLongitude"] as! Double
let carpoolSpotAnnotation = CarpoolSpot(name: object.valueForKey("spotTitle") as! String, subTitle: object.valueForKey("spotSubtitle") as! String, coordinate: CLLocationCoordinate2D(latitude: startingLat, longitude: startingLong))
self.mapView.addAnnotation(carpoolSpotAnnotation)
The code snippet above is located within my didUpdateLocations implementation of the locationManager function, and with this code, I am able to access the query results I need.

Why does the "objects: [PFObject]?" parameter of Parse's findObjectsInBackgroundWithBlock function return nil?

This is for an existing class on Parse called "HellsKitchen" and I have tried others, but always receive nil on objects.
let query = PFQuery(className: "HellsKitchen")
query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
print("got em: \(objects)")
} else {
print("error: \(error!.userInfo)")
}
}
All of the other posts about this refer to the block closure when they had objects as [AnyObject]? but it has since changed sometime in late 2015 and I no longer need to cast it as PFObject as all the answers say.
I have tried adding App Transport Security Settings > Allow Arbitrary Loads = YES to my Info.plist to no avail.
I get no error from the block either. It passes into the if statement because error == nil and prints "got em: nil".
How can I get my objects?

Completion Handler - Parse + Swift

I'm trying to generate an array of PFObjects called 'areaList'. I've been researching this quite a bit and understand that I could benefit from using a completion handler to handle the asynchronous nature of the loaded results. My ask, specifically, is to get some guidance on what I'm doing wrong as well as potential tips on how to achieve the result "better".
Here is my query function with completion handler:
func loadAreasNew(completion: (result: Bool) -> ()) -> [Area] {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
for area in areas! {
let areaToAdd = area as! Area
areaList.append(areaToAdd)
// print(areaList) // this prints the list each time
// print(areaToAdd) // this prints the converted Area in the iteration
// print(area) // this prints the PFObject in the iteration
if areaList.count == areas!.count {
completion(result: true)
} else {
completion(result: false)
}
}
} else {
print("There was an error")
}
}
return areaList
}
Here is how I'm attempting to call it in viewDidLoad:
loadAreasNew { (result) -> () in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
I assigned this variable before viewDidLoad:
var areaList = [Area]()
In the console, I get the following:
Didn't Work
Didn't Work
Didn't Work
Didn't Work
[]
Representing the 5 items that I know are there in Parse...
This is an interesting question. First off, PFQuery basically has a built in completion handler, which is quiet nice! As you probably know, all of the code within the areaQuery.findObjectsInBackgroundWithBlock {...} triggers AFTER the server response. A completion most often serves the purpose of creating a block, with the ability of asynchronously returning data and errors.
Best practice would (IMO) to just call the code that you want to use with the results from your PFQuery right after your area appending loop (which I'm gonna take out because I'm picky like that), like so:
func loadAreasNew() {
var areaList = self.areaList
let areaQuery = PFQuery(className: "Area")
areaQuery.findObjectsInBackgroundWithBlock {
(areas: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let areasFormatted = areas! As [Areas]
areasList += areasFormatted
//Something like this
self.codeINeedAreasFor(areasList)
}
} else {
print(error)
}
}
}
HOWEVER! If you really feel the need to use some completion handlers, check out this other answer for more info on how to use them. But keep in mind all tools have a time and a place...
There are a few issues here.
Your completion handler doesn't require you to define the name for your completion handler's parameters, so you could easily use completion: (Bool) -> ()
Further in your function, you're returning areaList. This should be put through the completion handler like this onComplete(areaList) and change your completion handler parameter to expect your area list.
Then, when you call your function, it could look more like this :
loadAreasNew { result in
if (result == true) {
print(self.areaList)
} else {
print("Didn't Work")
}
}
Here is my concern:
1) Don't pass in a local variable and make the function return it, it's meaningless and danger.
You may want to initiate an empty array and make your fetch, then "return" it.
2) The fetch request is processed in background, you will have no idea when it will have finished. If you return the array immediately it will always be an empty array.
Put the "return" in your completion too.
3) Parse already has a distance checking method, you don't have to do it manually. aPARSEQUERRY.where(key:,nearGeoPoint:,inKilometers:)
I will rewrite the function as:
func loadNewAreas(completion:([Area],err?)->()){
let areaQuery = PFQuery(className: "Area")
areaQuery.where("location",nearGeoPoint:MYCURRENTLOCATION,inKilometers:50)
areaQuery.findObjectInBackgroundWithBlock(){objects,err
if objects.count == 0
{
completion([],err)
}
let areas = Area.areasFromPFObjects(objects)
completion(areas,err)
}
}

Parse with Swift: handle network error manually

I have been trying to modify the following code to manually handle network errors (e.g. disconnection) when retrieving users (_User table) from Parse. As soon as the error returned from the PFQuery is not nil, I should return the completionHandler with the errorMsg, however, the following code doesn't seem to return. Instead the Parse SDK error message keeps printing:
var status: Bool = false
var errorMsg: String? = nil
var query = PFQuery(className: ParseClient.ClassNames.User) //default limit - 100
query.whereKey("email", containsString: "#").findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
status = true
didComplete(success: status, students: objects as! [PFUser], error: nil)
return
} else {
dispatch_async(dispatch_get_main_queue()) {
println("error in fetching all users from Parse")
errorMsg = "There are network problems in fetching user data, please try again later."
didComplete(success: status, students: nil, error: errorMsg)
return
}
}
}
Parse network error message:
2015-09-01 21:28:59.881 UserGame[3564:354410] [Error]: Network connection failed. Making attempt 3 after sleeping for 5.993194 seconds.
Your second return is inside your dispatch_async block, and I think you meant to put it outside.
That said, I'm pretty sure you can't stop Parse from retrying, at least not from within this block. You could also try:
query.cancel()
This "cancels the current network request (if any)."

Resources