Can't update Array within a function - ios

Hey I'm working with Swift 2 and I'm trying to make a method that returns an array of strings of IDs downloaded from a database through a query. My problem is that within the function I cannot update my Array, meaning that I can access the downloaded information from the server but I cannot append it to my array for some reason. Or better, I can, but it doesn't really do anything. My array seems to stay empty.
func ATMsAroundMe(myLocation : PFGeoPoint) -> [String]{
var results = [String]()
let query = PFQuery(className: "ATMs")
query.whereKey("location", nearGeoPoint: myLocation, withinMiles: 5)
query.limit = 10
query.findObjectsInBackgroundWithBlock { (atms: [PFObject]?, error: NSError?) -> Void in
if (error == nil) {
for atm in atms! {
print(atm.objectId) //Works!
results.append(atm.objectId!) //Doesn't work
}
} else {
// Log details of the failure
}
}
print(results) //Prints "[]"
return results
}
So yeah if you have any suggestion or any idea on what am I doing wrong it'd be really helpful and appreciated if you could let me know.
Thanks.

The reason why results is not updated is because it is updated in another block scope. So updated values persists only in that block scope. To get the updated result you would need to use closures or __block in variable declaration in Objective-c.
which is quite nicely explained here here in BLOCKS VS CLOSURES

The issue here is that the call-
query.findObjectsInBackgroundWithBlock
This is an asynchronous call, and thus, your method simply returns as it does not wait for the results that will be returned by this asynchronous call. Thus you will need to think of an asynchronous API ATMsAroundMe in the form-
func ATMsAroundMe(myLocation : PFGeoPoint, completionHandler:(Bool,[String]?) ->Void){
let query = PFQuery(className: "ATMs")
query.whereKey("location", nearGeoPoint: myLocation, withinMiles: 5)
query.limit = 10
query.findObjectsInBackgroundWithBlock { (atms: [PFObject]?, error: NSError?) -> Void in
if (error == nil) {
for atm in atms! {
print(atm.objectId) //Works!
var results = [String]()
results.append(atm.objectId!)
completionHandler(true, results)
}
} else {
// Report the failure
completionHandler(false, nil)
}
}
}
You can now call this API like-
ATMsAroundMe(myLocation){(success :Bool, results:[String]?) in
if(success){
if let results = results {
//Process results
}
}
}
Synchronous solution:
func ATMsAroundMe(myLocation : PFGeoPoint) -> [String]{
var results = [String]()
let query = PFQuery(className: "ATMs")
query.whereKey("location", nearGeoPoint: myLocation, withinMiles: 5)
query.limit = 10
//Declare a semaphore to help us wait until the background task is completed.
let sem = dispatch_semaphore_create(0);
query.findObjectsInBackgroundWithBlock { (atms: [PFObject]?, error: NSError?) -> Void in
if (error == nil) {
for atm in atms! {
print(atm.objectId) //Works!
results.append(atm.objectId!)
dispatch_semaphore_signal(sem);
}
} else {
// Log details of the failure
dispatch_semaphore_signal(sem);
}
}
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
print(results) //Should print your results
return results
}
Note Be care full in calling this synchronous API from your main thread, it can potentially stall the main thread until the call returns like any other synchronous calls.

Related

How to implement Parse query properly to get ordered data

I have below code to build a query out of two queries.
Issue is result is not sorted by updatedAt.
What could be the issue?
let ownerQuery = PFQuery(className: "requests")
ownerQuery.whereKey("owner", equalTo: forUser)
ownerQuery.whereKey("stage", equalTo: "initiated")
let requestQuery = PFQuery(className: "requests")
requestQuery.whereKey("requested_by", equalTo: requestedBy)
requestQuery.whereKey("stage", equalTo: "accepted")
// fetch all request of current user as requestor or lender/seller
let query = PFQuery.orQuery(withSubqueries: [ownerQuery, requestQuery])
query.includeKey("requested_by")
query.includeKey("owner")
query.order(byDescending: "updatedAt")
issue turned out to be code where i was processing the retrieved data. Parse returned sorted result.
I have below code where ordering gets affected. it seems to be because of getDetails() returning in async.
So i have added array sort before returning. I retrieve 20 rows at a time so sorting every array should not be costly i guess.
I had also posted on GitHub. Awaiting confirmation.
query.findObjectsInBackground { (results, error) in
if error == nil {
for result in results! {
group.enter()
let stage = result["stage"] as! String
let requestedBy = result["requested_by"] as! PFUser
DBHelper.getDetails(result["requested_id"] as! String, callbackFunction: { (requestedItem) in
let owner = result["owner"] as! PFUser
let request = Request()
request.requestId = result.objectId
request.item = requestedItem
request.owner = owner
request.lastUpdatedDate = result.updatedAt
requestsFound.append(request)
group.leave()
})
}
group.notify(queue: DispatchQueue.global(qos: .background), execute: {
DispatchQueue.main.async {
requestsFound.sort {(request1:Request, request2:Request) -> Bool in
request1.lastUpdatedDate! < request2.lastUpdatedDate!
}
callbackFunction(requestsFound,nil)
}
})
} else {
// encountered error from Parse
DispatchQueue.main.async {
callbackFunction(requestsFound,error! as NSError) // to represent error at backend server
}
}
} //end of findObjectsInBackground
Try this one:
ownerQuery.order(byAscending: "updatedAt")

Only show friends posts on Parse

new programmer trying to learn Swift and I'm trying to set my app up to only show the current users post on a parse photosharing database. The first method here in theory should add the current user to the "followingWho" array.
override func viewDidLoad() {
super.viewDidLoad()
let userQuery = PFUser.query()
userQuery?.whereKey("username", equalTo: PFUser.currentUser()!.username!)
userQuery?.findObjectsInBackgroundWithBlock ({
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
//no error
if let objects = objects {
for object in objects {
let followingWho = object["followingWho"] as! NSArray!
self.loadData(followingWho)
}
}
} else {
//error
NSLog("Error")
}
})
self.tableView.reloadData()
}
then my second method here should display the posts and filter out all but the current users
func loadData(followingWho: NSArray) {
let query = PFQuery(className: "Posts")
query.whereKey("addedBy", containedIn: followingWho as! [PFObject])
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock {
(posts: [PFObject]?, error: NSError?) -> Void in
if (error == nil) {
//No error
//let posts = posts as! [PFObject]
if let posts = posts {
for post in posts {
self.images.append(post["Image"] as! PFFile)
self.imageCaptions.append(post["Caption"] as! String)
self.imageDates.append(post["date"] as! String)
self.imageUsers.append(post["addedby"] as! String)
}
self.tableView.reloadData()
}
} else {
//error
NSLog("Error")
}
}
}
But nothing seems to be working. The code compiles and runs but it seems to only pull up a blank screen loading none of the posts.
This could be the problem, although if it is not the problem, it is certainly a problem: you are trying to update the UI from a background thread. findObjectsInBackgroundWithBlock calls its completion block in the background, and updating UI from the background is a big no-no. Instead, call your UI updates on the main thread:
dispatch_async(dispatch_get_main_queue(), {
//Do your UI updates here
})

Wait for async function return value in swift

I want to retrieve the user score from Parse and assign it to a variable. This function returns 0 before the query finishes. I have found a similar answer at Retrieve object from parse.com and wait with return until data is retrieved. However, I expect the function have a return value and what argument should I use for the completionhandler when calling this function. Any help would be appreciated, thanks!
Here is my code
func loadCurrentUserData() -> Int {
let query = PFQuery(className: "userScore")
let userId = PFUser.currentUser()!
var currentUserScore: Int = 0
query.whereKey("user", equalTo: userId)
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let scoreReceived = objects![0]["score"] as! Int
currentUserScore = scoreReceived
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.userScore.text = "\(scoreReceived)"
})
} else {
print("Error: \(error!) \(error!.userInfo)")
}
}
return currentUserScore
}
The way you have set this function will not work as the query method is asynchronous. This can be fixed in two ways:
1) Use the PFQuery synchronous category:
http://parse.com/docs/ios/api/Categories/PFQuery(Synchronous).html
The only disadvantage to this approach is that the method will become blocking so make sure to call it from a background thread.
2) Restructure the function to use a completion block instead of a return value..i.e:
func loadCurrentUserData(completion: (score: Int!, error: NSError?) ->()) {
let query = PFQuery(className: "userScore")
let userId = PFUser.currentUser()!
var currentUserScore: Int = 0
query.whereKey("user", equalTo: userId)
query.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
let scoreReceived = objects![0]["score"] as! Int
currentUserScore = scoreReceived
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.userScore.text = "\(scoreReceived)"
})
completion(score: currentUserScore, error: nil);
} else {
print("Error: \(error!) \(error!.userInfo)")
}
completion(score: currentUserScore, error: error);
}
}

How to get Top-50 query with Parse.com

I don't know How to get Top-50 query with Parse.com,
so Can you tell me that?
This code is get all query,but I'd like to get top-50 query.
But I don't how to get Top-50 or latest-50 query.
func loadData() {
comments.removeAllObjects()
var query:PFQuery = PFQuery(className: "Comment")
query.orderByDescending("createdAt")
query.limit = 1000
query.findObjectsInBackgroundWithBlock{(objects: [AnyObject]!, error: NSError!) -> Void in
if (error != nil){
//error
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// Task
for object in objects {
if object["commentId"] as? String == nil {
self.comments.addObject(object)
if self.comments.count == 50 {
break
}
}
}
dispatch_async(dispatch_get_main_queue()) {
// UI
self.tableView.reloadData()
self.stopIndicator()
}
}
}
}
Language is Swift.
Please answer me.
To query for the latest 50 records on Parse you need to limit the query for 50 objects (Default limit is 100).
func loadData() {
self.comments.removeAllObjects()
var query:PFQuery = PFQuery(className: "Comment")
query.orderByDescending("createdAt")
query.limit = 50 //<--- change this line
query.findObjectsInBackgroundWithBlock{(objects: [AnyObject]!, error: NSError!) -> Void in
if (error != nil){
//error
}
// Add the received objects to your array
self.comments.addObjectsFromArray(objects)
// No need to perform any task in background thread, so update the UI
self.tableView.reloadData()
self.stopIndicator()
}
}

reloadData when loop is done

I have a loadItem functions which is suppose to load items from a parse server. This include a loop where i'm at the end is saving the data to an array:
itemArray?.addObject(arrayDic)
When this is saved i would like to reloadData of the collectionView. therefor i've inserted it into a dispatch_async block, but it still seems like it is being run before the data is saved into the itemArray array since the itemArray.count is 0 and i've checked inside the loop that the data is saved into the array. What am i doing wrong?
func loadItems() {
var query = PFQuery(className:"Items")
query.orderByAscending("createdAt")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
var imageRelation: PFRelation = object.relationForKey("pictures")
var query = imageRelation.query()
query.orderByDescending("createdAt")
query.findObjectsInBackgroundWithBlock {
(imageObjects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
var theArray = imageObjects as NSArray
var arrayDic = NSMutableDictionary()
let imageFile = theArray.objectAtIndex(0).objectForKey("image") as PFFile
imageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if !(error != nil) {
let image = UIImage(data:imageData)
arrayDic.setValue(object.objectForKey("price"), forKey: "price")
arrayDic.setValue(image, forKey: "image")
itemArray?.addObject(arrayDic)
}
}
}
}
}
dispatch_async(dispatch_get_main_queue()) {
println(itemArray?.count)
self.collectionView.reloadData()
}
} else {
// Log details of the failure
NSLog("Error: %# %#", error, error.userInfo!)
}
}
}
I would not advise trying to make the requests synchronous (as that can significantly slow down the process, unless you scheduled them to run concurrently on some concurrent background queue). I would suggest, instead, a pattern that allows you to keep the asynchronous requests, but notifies you of their completion. One such pattern is the dispatch group, in which you:
create a dispatch group:
let group = dispatch_group_create()
for every iteration of your loop, call dispatch_group_enter:
dispatch_group_enter(group)
win the completion block of the asynchronous method, call dispatch_group_leave:
dispatch_group_leave(group)
specify what you want to do when the dispatch group is complete:
dispatch_group_notify(group, dispatch_get_main_queue()) {
println(self.itemArray?.count)
self.collectionView.reloadData()
}
When all of the calls to dispatch_group_enter are offset by the final dispatch_group_leave call, the notification block will be called for you.
So, looking at the code as you loop through the objects, it might look something like:
let group = dispatch_group_create()
for object in objects {
dispatch_group_enter(group)
// do some stuff
query.findObjectsInBackgroundWithBlock {
(imageObjects: [PFObject]!, error: NSError!) -> Void in
if error == nil {
// do some more stuff
imageFile.getDataInBackgroundWithBlock {
(imageData: NSData!, error: NSError!) -> Void in
if !(error != nil) {
// do even some more stuff
}
dispatch_group_leave(group)
}
} else {
dispatch_group_leave(group)
}
}
}
dispatch_group_notify(group, dispatch_get_main_queue()) {
println(self.itemArray?.count)
self.collectionView.reloadData()
}
Their are other functionally equivalent patterns (e.g. custom concurrent dispatch queue with the final reloadData dispatched with a barrier; make these individual requests NSOperation objects that you add to NSOperationQueue and create separate completion operation that is dependent upon those other operations; etc.), but the dispatch group seems to entail the least refactoring of this code. Regardless, hopefully this illustrates the basic idea: Make the final reload only be triggered when the other asynchronous requests are done.

Resources