Swift Array is Empty After Parse Queries - Completion Handler? - swift

I don't understand why the arrays become empty after the query with block. I did some research and it's most likely because I need a completion handler, but I can't figure out how to implement it in this case. Can I just add an activity indicator until the method is done?
var usernamesFollowing = [""]
var useridsFollowing = [""]
func refresh(completion: (Bool)){
//find all users following the current user
var query = PFQuery(className: "Followers")
query.whereKey("following", equalTo: PFUser.currentUser()!.objectId!)
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if error == nil {
//remove all from arrays
self.usernamesFollowing.removeAll(keepCapacity: true)
self.useridsFollowing.removeAll(keepCapacity: true)
//get all userIds of following current user and add to useridsFollowing array
if let objects = objects {
for userId in objects {
var followerId = userId["follower"] as! String
self.useridsFollowing.append(followerId)
//get usernames from followerId and add to usernamesFollowing array
var query = PFUser.query()
query!.whereKey("objectId", equalTo: followerId)
query!.findObjectsInBackgroundWithBlock({ (objects2, error) -> Void in
if let objects2 = objects2 {
for username in objects2 {
var followerUsername = username["username"] as! String
self.usernamesFollowing.append(followerUsername)
}
}
//WORKS. usernamesFollowing array is now full.
println(self.usernamesFollowing)
})
//BROKEN. usernamesFollowing array is now empty outside of block.
println(self.usernamesFollowing)
}
}
}
//WORKS. useridsFollowing is now full.
println(self.useridsFollowing)
})
//BROKEN. usernamesFollowing is now empty outside of block.
println(self.usernamesFollowing)
}

To elaborate on Larme's point - asynchronous methods return immediately, and dispatch the work into another queue. To put this in context, consider your two println statements:
println(self.usernamesFollowing) //1. inside async fetch closure
println(self.usernamesFollowing) //2. outside async fetch closure
The asynchronous method will take your closure and dispatch it on to a different queue. After doing so, it returns immediately, and continues to execute your code, which goes to your 2nd println statement right away. In this situation, your second println statement will actually print before your first.
If possible, do all your data manipulations within the block. It'll save you a lot of work. If you must offload the objects outside of the block, consider using NSOperations, which is perfectly equipped to deal with that type of scenario.

Related

Why is My Code running this way and is There a better way to solve this issue

// In the code below I am trying to return an array from data in firestore, the array always returned empty when I put the handler outside the for loop so I had to use an if statement inside the for loop to get the array containing the data. after using the print statement you see in the code i found out that the compiler is going over the entire function before entering the for loop, (print("5") & (print("6") are the first to run and when I put the handler outside the for it will also be triggered and return an empty array
**
func getMyGames(joinedGamesIDs: [String], handler: #escaping(_ games: [GameViewModal]) -> ()) {
var games = [GameViewModal]()
if !joinedGamesIDs.isEmpty{
for id in joinedGamesIDs {
db.collection("games").document(id).getDocument { (document, error) in
if let document = document, document.exists {
if let game = self.getGameViewModal(document: document){
games.append(game)
print("1")
print(games.count)
}
print("2")
print(games.count)
}
print("3")
print(games.count)
if games.count == (joinedGamesIDs.count){
handler(games)
}
print("4")
print(games.count)
}
}
print("5")
print(games.count)
}
print("6")
print(games.count)
}
**
I've embedded my explanations in the code commentary for easier reading. But the problem you have is that you aren't coordinating these async tasks (the getting of each document). You must coordinate them so when the last one finishes, you can "return" the array from the function. This function doesn't technically "return" anything (except Void) but the completion handler, in a way, "returns" the array which is why I put it in quotes. These semantic details matter and it helps to understand everything better.
func getMyGames(joinedGamesIDs: [String], handler: #escaping (_ games: [GameViewModel]) -> ()) {
guard !joinedGamesIDs.isEmpty else {
// If there is nothing to do, always consider
// calling the handler anyway, with an empty
// array, so the caller isn't left hanging.
return handler([])
}
// Set up a Dispatch Group to coordinate the multiple
// async tasks. Instatiate outside of the loop.
let group = DispatchGroup()
var games: [GameViewModel] = []
for id in joinedGamesIDs {
// Enter the group on each iteration of async work
// to be performed.
group.enter()
db.collection("games").document(id).getDocument { (document, error) in
if let doc = document,
doc.exists,
let game = self.getGameViewModal(document: doc) {
games.append(game)
} else if let error = error {
// Always print errors when in development.
print(error)
}
// No matter what happens inside the iteration,
// whether there was a success in getting the
// document or a failure, always leave the group.
group.leave()
}
}
// Once all of the calls to enter the group are equalled
// by the calls to leave the group, this block is called,
// which is the group's own completion handler. Here is
// where you ultimately call the function's handler and
// return the array.
group.notify(queue: .main) {
handler(games)
}
}

Closures for waiting data from CloudKit

I have a CloudKit database with some data. By pressing a button my app should check for existence of some data in the Database. The problem is that all processes end before my app get the results of its search. I found this useful Answer, where it is said to use Closures.
I tried to follow the same structure but Swift asks me for parameters and I get lost very quick here.
Does someone can please help me? Thanks for any help
func reloadTable() {
self.timePickerView.reloadAllComponents()
}
func getDataFromCloud(completionHandler: #escaping (_ records: [CKRecord]) -> Void) {
print("I begin asking process")
var listOfDates: [CKRecord] = []
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Riservazioni", predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 20
queryOperation.recordFetchedBlock = { record in
listOfDates.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil {
print("error")
print(error!.localizedDescription)
} else {
print("NO error")
self.Array = listOfDates
completionHandler(listOfDates)
}
}
}
var Array = [CKRecord]()
func generateHourArray() {
print("generate array")
for hour in disponibleHours {
let instance = CKRecord(recordType: orderNumber+hour)
if Array.contains(instance) {
disponibleHours.remove(at: disponibleHours.index(of: hour)!)
}
}
}
func loadData() {
timePickerView.reloadAllComponents()
timePickerView.isHidden = false
}
#IBAction func checkDisponibility(_ sender: Any) {
if self.timePickerView.isHidden == true {
getDataFromCloud{ (records) in
print("gotData")
self.generateHourArray()
self.loadData()
}
print(Array)
}
}
Im struggling to understand your code and where the CloudKit elements fit in to it, so Im going to try and give a generic answer which will hopefully still help you.
Lets start with the function we are going to call to get our CloudKit data, lets say we are fetching a list of people.
func getPeople() {
}
This is simple enough so far, so now lets add the CloudKit code.
func getPeople() {
var listOfPeople: [CKRecord] = [] // A place to store the items as we get them
let query = CKQuery(recordType: "Person", predicate: NSPredicate(value: true))
let queryOperation = CKQueryOperation(query: query)
queryOperation.resultsLimit = 20
// As we get each record, lets store them in the array
queryOperation.recordFetchedBlock = { record in
listOfPeople.append(record)
}
// Have another closure for when the download is complete
queryOperation.queryCompletionBlock = { cursor, error in
if error != nil {
print(error!.localizedDescription)
} else {
// We are done, we will come back to this
}
}
}
Now we have our list of people, but we want to return this once CloudKit is done. As you rightly said, we want to use a closure for this. Lets add one to the function definition.
func getPeople(completionHandler: #escaping (_ records: [CKRecord]) -> Void) {
...
}
This above adds a completion hander closure. The parameters that we are going to pass to the caller are the records, so we add that into the definition. We dont expect anyone to respond to our completion handler, so we expect a return value of Void. You may want a boolean value here as a success message, but this is entirely project dependent.
Now lets tie the whole thing together. On the line I said we would come back to, you can now replace the comment with:
completionHandler(listOfPeople)
This will then send the list of people to the caller as soon as CloudKit is finished. Ive shown an example below of someone calling this function.
getPeople { (records) in
// This code wont run until cloudkit is finished fetching the data!
}
Something to bare in mind, is which thread the CloudKit API runs on. If it runs on a background thread, then the callback will also be on the background thread - so make sure you don't do any UI changes in the completion handler (or move it to the main thread).
There are lots of improvements you could make to this code, and adapt it to your own project, but it should give you a start. Right off the bat, Id image you will want to change the completion handler parameters to a Bool to show whether the data is present or not.
Let me know if you notice any mistakes, or need a little more help.

Difference between generate and append when querying and adding data to array?

var objectarray = [PFObject]()
func populateTable() {
query.findObjectsInBackgroundWithBlock { (objects, error) in
self.objectarray.removeAll(keepCapacity: true)
self.searchTableView.reloadData()
if error == nil {
Above is the query I am doing and the below 2 codes are what I can use to use the query to populate a array.
if let objects = objects as [PFObject]! {
self.objectarray = Array(objects.generate())
}
Is there any difference with running this code above to populate my array or running the code below?
for object in objects! {
self.objectarray.append(object)
}
Doing either works to load onto my tableView. Also another question regarding Parse. After doing the above, the user doesn't download PFFiles from the background until I run
getDataInBackgroundWithBlock
right? I want to know if it'd be beneficial to save smaller versions of images onto the server.

The method does not enter for loop Parse Swift

I use parse to query current user's friend list and the friend request user and when user press each cell of the friend request, The app will add that friend back and delete the selected friend request so I query friend list and friend request and use "addedArray" as friend requests and "duplicate" as array of current user's friend list and use for loop to find the duplicate of friend list and friend request and delete that friend from addedArray so the current user will se the latest friend requests
Here's my code in swift
func queryAdded(){
let query = PFQuery(className: "Request")
let user = PFUser.currentUser()?.relationForKey("Friends")
let query2 = user?.query()
query.whereKey("To", equalTo: PFUser.currentUser()!)
query.findObjectsInBackgroundWithBlock {
(objects, error) -> Void in
if error == nil{
for object in objects! {
print("query")
let username = object.valueForKey("FromUsername") as! String
self.userCellAdded = username
self.addedArray.append(username)
print(username)
print(self.addedArray.count)
}
print("READY")
print(self.addedArray.count)
self.tableView.reloadData()
}
else{
/* dispatch_async(dispatch_get_main_queue()){
//reload the table view
query.cachePolicy = PFCachePolicy.NetworkElseCache
}*/
print("errorrrr")
}
}
query2!.findObjectsInBackgroundWithBlock{(objects,error) -> Void in
if error == nil {
for object in (objects)!{
if let username = object["username"] as? String {
self.duplicate.append(username)
print("duplicate")
print(username)
print("size")
print(self.duplicate.count)
}
}
}
}
for self.iIndex = 0 ; self.iIndex < self.addedArray.count ; ++self.iIndex {
for self.jIndex = 0 ; self.jIndex < self.duplicate.count ; ++self.jIndex {
print("in for loop")
if self.addedArray[self.iIndex] == self.duplicate[self.jIndex] {
self.addedArray.removeAtIndex(self.iIndex)
self.tableView.reloadData()
print("find")
}
}
}
}
The problem is The method queryAdded() does not run for loop for me and I don't understand why
The duplicate array and the addedArray have value and size but still it didn't go inside the for loop
Your problem is that your for loop is depending on the results of two asynchronous operations. What happens is that your app starts these two background queries and then immediately starts the for loop. Since there is no data yet from the queries, the for loop has no data to work on.
You can either solve this by creating a "pyramid hell" by nesting your operations (bad), or you can use a framework to achieve the same as Promises would provide for JavaScript (good).
Since you're using Parse, you have such a framework already; namely the Bolts Framework. You could then perform these operations sequentially using tasks (BFTask).
Example from the Bolts readme:
var query = PFQuery(className:"Student")
query.orderByDescending("gpa")
findAsync(query).continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var valedictorian = students.objectAtIndex(0) as PFObject
valedictorian["valedictorian"] = true
return self.saveAsync(valedictorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
var valedictorian = task.result() as PFObject
return self.findAsync(query)
}.continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
let students = task.result() as NSArray
var salutatorian = students.objectAtIndex(1) as PFObject
salutatorian["salutatorian"] = true
return self.saveAsync(salutatorian)
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
// Everything is done!
return nil
}
You could then first prepare both your queries and then start the chain of tasks:
query1.findObjectsInBackground().continueWithSuccessBlock {
(task: BFTask!) -> BFTask in
var objects = task.result() as NSArray
for object in objects {
//collect your usernames
}
return query2.findObjectsInBackground()
}.continueWithSuccessBlock {
(task: BFTask!) -> AnyObject! in
var objects = task.result() as NSArray
for object in objects {
// collect your usernames from relation
}
// Call a function containing the for loop that is currently not running
return nil
}
The for loop is run
duplicate array and the addedArray have value and size - No they don't
findObjectsInBackgroundWithBlock runs the query in ... the background.
Therefore your program does the following:
start the first query
start the second query
run the for loop
the queries finish at some arbitrary point in time.
In particular when the program reaches point 3 the arrays do not contain anything, they are empty arrays, therefore the for-loop executes perfectly fine as it is supposed to be: it does nothing since there is nothing to loop over.
Solution:
Move the for loop into a function that you call after the first query and the second query finish.

findObjectsInBackgroundWithBlock not entering the "Block"

My code is something like following, and what I don't understood is why the code after Void in never runs? I did tried to debug but it appears Block never gets executed.
By the way, query will return empty.
let query = PFQuery(className: "LastId")
query.whereKey("UserId", equalTo: opUserIdList[i])
query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
if let objects = objects {
for object in objects {
object.setValue(self.opLastIdChangedToList[i], forKey: "lastId")
object.saveInBackground()
}
} else {
let newLine = PFObject(className: "LastId")
newLine["lastId"] = self.opLastIdChangedToList[i]
newLine["userIdself."] = self.opUserIdList[i]
newLine.saveInBackground()
}
})
//rest of the code
This is because the block of code after Void in is the closure. To say in simple that is a pointer to a function containing the code (code block) that is executed later, after the completion of the parent function in which it was called.
Try reading the documentation to better understand how to work with it: https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/uid/TP40014097-CH11-ID94