1

I am trying to query my _User class for a specific objectId, and download an image from it.

The objectId passed through the userList is the correct one, if checked against the parse.com user table.

The array returned is always empty

Any help would be appreciated.

func imageArrayFromUserList(userList: [PFUser]) -> [UIImage] {
    var arrayToReturn: [UIImage] = []
    for user in userList {
        let objectID = user.objectId

        let query = PFUser.query()
        //let query = PFQuery(className: "_User")
        query!.whereKey("objectId", equalTo: objectID!)
        //query?.limit = 1
        query!.findObjectsInBackgroundWithBlock({ (results: [AnyObject]?, error: NSError?) -> Void in

            if error != nil{// This never prints anything to console
                println(error)
            }

            if let results = results as? [PFObject] {
                for object in results {
                    let userPicture = object["image"] as! PFFile

                    userPicture.getDataInBackgroundWithBlock {
                        (imageData: NSData?, error: NSError?) -> Void in
                        if error == nil {
                            if let imageData = imageData {
                                let image = UIImage(data:imageData)
                                arrayToReturn.append(image!)
                            }
                        }
                    }
                }
            }
        })
    }
    return arrayToReturn
}//end of imageArrayFromUserList method
sweepez
  • 585
  • 1
  • 4
  • 17
  • That implies `userList` is empty so the for loop is never entered. Did you scope outwards with your breakpoints to find out what *is* getting executed? Put breakpoints to ensure the function is even being entered, and then at the top of the for loop, then at the top of the completion handler. – BaseZen Aug 03 '15 at 00:02
  • Also, see that `error` parameter? - don't ignore it. Check if it isn't nil and if it isn't then print it to see what went wrong. – Paulw11 Aug 03 '15 at 00:06
  • @BaseZen userList is not empty, I put at breakpoint after "let objectID = user.objectId" and the value matches the one on parse.com. – sweepez Aug 03 '15 at 00:29
  • @Paulw11 I tried printing an error, nothing happens because "query!.findObjectsInBackgroundWithBlock" does not seem to execute, it jumps it and goes to return line. I have breakpoints everywhere inside findobjects – sweepez Aug 03 '15 at 00:31
  • That means the `findObjectsInBackgroundWithBlock` is definitely called, so your statement is incorrect. Step through every line of a single interation of the `for` loop and explain what happens. Are you saying the *completion handler* is never called? Was a breakpoint ever reached at the top of that? – BaseZen Aug 03 '15 at 00:32
  • I don't think you understand how completion handlers (callbacks) work. The task executes on a separate thread. That's what 'InBackground' means. – BaseZen Aug 03 '15 at 00:33
  • I got nothing, I made userList have exactly one PFUser upon the method being called, on first iteration of the for loop the correct objectId is assigned (matches the one on the User class on parse). I cant tell what happens inside the completion block because breakpoints don't happen. Does the code I have there seem correct to download the image file?, I made a column on the User class called "image" which already has pictures in it, and I am trying to download it for the specific user objectId. – sweepez Aug 03 '15 at 00:50
  • Your code is broadly correct, but you can't return `arrayToReturn` because all of the data fetching will happen in the background and takes some time - so this function will always return an empty array – Paulw11 Aug 03 '15 at 00:59
  • Put an unconditional println() at the very top of the completion handler just to make sure. – BaseZen Aug 03 '15 at 01:00
  • Also, let the code just run. Don't set any breakpoints in the outer function when debugging the callback. Stepping through the outer function code will never break in the callback code, because the callback happens on another thread. I still think you don't grasp the basics of background threads. – BaseZen Aug 03 '15 at 01:04
  • @BaseZen it printed nil – sweepez Aug 03 '15 at 01:05
  • I meant something more helpful, like: `println("Hey, the completion handler has been entered!")` But anyway, so the completion handler is running after all. Your basic assumptions are incorrect. Put print statements that describe everything the completion handler is doing at every line. – BaseZen Aug 03 '15 at 01:09
  • put a println(image) right after let image = UIImage(data:imageData) and I got Optional(, {320, 320}) which is what I was hoping. any suggestions on how to I should store this image, because like @Paulw11 the arrayreturned is empty. Background thread :D – sweepez Aug 03 '15 at 01:13
  • also edited my question to reflect my real problem, the array of images was returned empty, which makes sense since it gets returned immediately, while the background thread was fetching it. I'm gonna try something else and try to solve the problem, thanks. I'll take a look at the tutorial you linked. – sweepez Aug 03 '15 at 01:32

1 Answers1

1

You don't yet understand how completion handlers work. findObjectsInBackgroundWithBlock is designed to return immediately. It queues the finding task on a background thread. When the task completes, the completion handler code is called, probably on the same background thread.

See: How to explain callbacks in plain english? How are they different from calling one function from another function?

So you are debugging incorrectly, by expecting the debugger to hit breakpoints in the completion handler while stepping through the main thread that creates the background task. It doesn't work that way.

Since you're confused about debugging, helpful println()s at every stage, e.g.:

        if error != nil{// This never prints anything to console
            println(error)
        }
        else {
            println("No error!")
        }

        if let results = results as? [PFObject] {
            println("Got \(results.count\) results!")
            for object in results {
                println("Got result: \(object)!")
                let userPicture = object["image"] as! PFFile

                userPicture.getDataInBackgroundWithBlock {
                    (imageData: NSData?, error: NSError?) -> Void in
                    if error == nil {
                        println("Got user picture!")
                        if let imageData = imageData {
                            let image = UIImage(data:imageData)
                            arrayToReturn.append(image!)
                        }
                    }
                    else {
                        println("could not get user picture!")
                    }
                }
            }
        }
        else {
            println("What the hell? Results was not a [PFObject]")
        }

NOW with regard to just designing with callbacks correctly:

Your main function creates a request for the data, then immediately returns. It should be returning Void, not the array.

What is supposed to reflect the new data? Let's say it's a UITableView called myTableView. Then when the array has been filled in, you save it in a stored property of your class, and call myTableView.reloadData() in a dispatch_async call to the main thread. Where the UITableViewDataSource functions use the aforementioned stored array property.

I'm sure this is confusing. You need to read a whole tutorial on integrating data that arrives in the background.

Interestingly, I just did that for another user!

Tutorial in retrieving, mutating and saving array from Parse.com in Swift with UITableView

Community
  • 1
  • 1
BaseZen
  • 8,650
  • 3
  • 35
  • 47
  • I ended up using https://github.com/rs/SDWebImage to get the pictures loaded instead, i was not able to do it the other way. Problem fixed – sweepez Aug 03 '15 at 18:47
  • But concepts not fully learned. :-( I see you accepted the answer. Was it helpful even though you didn't use it? (Is a teacher -->) – BaseZen Aug 03 '15 at 20:18
  • I accepted because you spent time trying to help me figure out the problem, it was helpful to clear up the confusion of running something in the background and not being able to breakpoint inside, and then realizing that the pictures won't load either and that I needed to load them asynchronously. Eventually I used sdwebimage to take care of loading the image async. – sweepez Aug 03 '15 at 22:40