Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finish all asynchronous requests before loading data?

I have run into an issue where I have multiple asynchronous requests occuring which grab images and information from the Facebook API and my Firebase database. I want to perform all my asynchronous requests, then store all that data that I grabbed from the Facebook API/Firebase database into one entire object which I can quickly load. I have set up completion handlers for every asynchronous request which I thought forces the program to "wait" until the request is complete and then have the program continue, but that doesn't seem to work for me. Below is my attempt:

func setupEvents(completion: (result: Bool, Event: Event) -> Void){
    // Get a reference to Events
    eventsReference = Firebase(url:"<DB Name>")
    eventAttendeesRef = Firebase(url:"<DB Name>")

    //Read the data at our posts reference
    println("Event References: \(eventsReference)")
    eventsReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in

        let eventName = snapshot.value["eventName"] as? String
        let eventLocation = snapshot.value["eventLocation"] as? String
        let eventCreator = snapshot.value["eventCreator"] as? String

        var attendees: NSMutableDictionary = [:]
        var attendeesImages = [UIImage]()
        let attendee: NSMutableDictionary = [:]

        let group = dispatch_group_create()

        //Get attendees first
        dispatch_group_enter(group)
        self.getAttendees(snapshot.key as String, completion:{ (result, name, objectID) -> Void in
            if(result == true){
                println("Finished grabbing \(name!) \(objectID!)")
                attendees.addEntriesFromDictionary(attendee as [NSObject : AnyObject])
            }
            else {
                println("False")
            }
            dispatch_group_leave(group)
        })

        //Get attendees photos
        dispatch_group_enter(group)
        self.getAttendeesPictures(attendee, completion: { (result, image) -> Void in
            if result == true {
                println("Finished getting attendee photos. Now to store into Event object.")
                attendeesImages.append(image!)
            }
            else{
                println("false")
            }
            dispatch_group_leave(group)
        })

        dispatch_group_notify(group, dispatch_get_main_queue()) {
            println("both requests done")
            //Maintain array snapshot keys
            self.eventIDs.append(snapshot.key)

            if snapshot != nil {
                let event = Event(eventName: eventName, eventLocation:eventLocation, eventPhoto:eventPhoto, fromDate:fromDate, fromTime:fromTime, toDate:toDate, toTime:toTime, attendees: attendees, attendeesImages:attendeesImages, attendeesImagesTest: attendeesImagesTest, privacy:privacy, eventCreator: eventCreator, eventCreatorID: eventCreatorID)
                println("Event: \(event)")
                completion(result: true, Event: event)
            }
        }

        }) { (error) -> Void in
            println(error.description)
    }
}

I know I have my completion handlers set correctly as I have tested in my program. However, what I want is that only after both the getAttendees and getAttendeesPictures function completes, I then want to store all the information I grabbed the snapshot, getAttendees, and getAttendeesPictures function and store them into an event object. Any ideas on how to accomplish this? I've tried to look into dispatch_groups to help me handle this via this link: Checking for multiple asynchronous responses from Alamofire and Swift but my program seems to only execute the getAttendees function but not the getAttendeesPictures function. Below are also the getAttendees and getAttendeesPictures functions:

func getAttendees(child: String, completion: (result: Bool, name: String?, objectID: String?) -> Void){
    //Get event attendees of particular event
    var attendeesReference = self.eventAttendeesRef.childByAppendingPath(child)
    println("Loading event attendees")
    //Get all event attendees
    attendeesReference.observeEventType(FEventType.ChildAdded, withBlock: { (snapshot) -> Void in
        let name = snapshot.value.objectForKey("name") as? String
        let objectID = snapshot.value.objectForKey("objectID") as? String
        println("Name: \(name) Object ID: \(objectID)")
        completion(result: true, name: name, objectID: objectID)
        }) { (error) -> Void in
            println(error.description)
    }

 func getAttendeesPictures(attendees: NSMutableDictionary, completion: (result: Bool, image: UIImage?)-> Void){
    println("Attendees Count: \(attendees.count)")
    for (key, value) in attendees{
        let url = NSURL(string: "https://graph.facebook.com/\(key)/picture?type=large")
        println("URL: \(url)")
        let urlRequest = NSURLRequest(URL: url!)
        //Asynchronous request to display image
        NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
            if error != nil{
                println("Error: \(error)")
            }
            // Display the image
            let image = UIImage(data: data)
            if(image != nil){
                completion(result: true, image: image)
            }
        }
    }
}
like image 329
user1871869 Avatar asked Jul 07 '15 06:07

user1871869


1 Answers

For users seeking answer to question in title then use of dispatch_group and GCD outlined here: i.e embedding one group inside the notification method of another dispatch_group is valid. Another way to go at a higher level would be NSOperations and dependencies which would also give further control such as canceling operations.

Outline:

func doStuffonObjectsProcessAndComplete(arrayOfObjectsToProcess: Array) -> Void){

    let firstGroup = dispatch_group_create()

    for object in arrayOfObjectsToProcess {

        dispatch_group_enter(firstGroup)

        doStuffToObject(object, completion:{ (success) in
            if(success){
                // doing stuff success
            }
            else {
                // doing stuff fail
            }
            // regardless, we leave the group letting GCD know we finished this bit of work
            dispatch_group_leave(firstGroup)
        })
    }

    // called once all code blocks entered into group have left
    dispatch_group_notify(firstGroup, dispatch_get_main_queue()) {

        let processGroup = dispatch_group_create()

        for object in arrayOfObjectsToProcess {

            dispatch_group_enter(processGroup)

            processObject(object, completion:{ (success) in
                if(success){
                    // processing stuff success
                }
                else {
                    // processing stuff fail
                }
                // regardless, we leave the group letting GCD know we finished this bit of work
                dispatch_group_leave(processGroup)
            })
        }

        dispatch_group_notify(processGroup, dispatch_get_main_queue()) {
            print("All Done and Processed, so load data now")
        }
    }
}

The remainder of this answer is specific to this codebase.

There seem to be a few problems here: The getAttendees function takes an event child and returns an objectID and Name which are both Strings? Shouldn't this method return an array of attendees? If not, then what is the objectID that is returned?

Once an array of attendees is returned, then you can process them in a group to get the pictures.

The getAttendeesPictures eventually returns UIImages from Facebook. It's probably best to cache these out to the disk and pass path ref - keeping all these fetched images around is bad for memory, and depending on size and number, may quickly lead to problems.

Some examples:

func getAttendees(child: String, completion: (result: Bool, attendees: Array?) -> Void){

    let newArrayOfAttendees = []()

    // Get event attendees of particular event

    // process attendees and package into an Array (or Dictionary)

    // completion
    completion(true, attendees: newArrayOfAttendees)
}

func getAttendeesPictures(attendees: Array, completion: (result: Bool, attendees: Array)-> Void){

    println("Attendees Count: \(attendees.count)")

    let picturesGroup = dispatch_group_create()

    for attendee in attendees{

       // for each attendee enter group
       dispatch_group_enter(picturesGroup)

       let key = attendee.objectID

       let url = NSURL(string: "https://graph.facebook.com/\(key)/picture?type=large")

        let urlRequest = NSURLRequest(URL: url!)

        //Asynchronous request to display image
        NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue()) { (response:NSURLResponse!, data:NSData!, error:NSError!) -> Void in
            if error != nil{
                println("Error: \(error)")
            }

            // Display the image
            let image = UIImage(data: data)
            if(image != nil){
               attendee.image = image
            }

            dispatch_group_leave(picturesGroup)
        }
    }

    dispatch_group_notify(picturesGroup, dispatch_get_main_queue()) {
         completion(true, attendees: attendees)
    }
}

func setupEvents(completion: (result: Bool, Event: Event) -> Void){

    // get event info and then for each event...

    getAttendees(child:snapshot.key, completion: { (result, attendeesReturned) in
        if result {
            self.getAttendeesPictures(attendees: attendeesReturned,         completion: { (result, attendees) in

              // do something with completed array and attendees


            }
        }
        else {

        }
    })

}

The above code is just an outline, but hopefully points you in the right direction.

like image 172
Dave Roberts Avatar answered Oct 22 '22 09:10

Dave Roberts