iOS - Swift - sync and async - ios

Please help me to solve this problem - after a lot of (not so efficent...) search I can't do this alone.
I have the following methods:
showLoadingAnimation()
to show the loading animation while background tasks are running
hideLoadingAnimation()
to hide the loading animation as soon as all the background tasks are finished
getUserFacebookData()
to get Facebook-user data
uploadUserFacebookDataToServer()
to upload the Facebook-user data to the server (and perform tasks with them).
What I want to perform:
Show up the loading animation: showLoadingAnimation()
Get the user data from Facebook: getFacebookData()
Wait until these data are being downloaded
As soon as the Facebook-user data are being download, upload these data to the server: uploadUserFacebookDataToServer()
Wait untile these data are being uploaded
Hide the loading animation: hideLoadingAnimation()
Now my problem is, that I don't know how to solve this problem. I know, that I should use sync and/or async tasks, GCD... But I don't know how, and I can't find a proper guide to it.
Could someone explain it to me through these functions?
Thanks!
UPDATE:
Thank you, Zhi-Wei Cai, that was the kind of answer what I was hoping for.
Now it seems to work, the calling order is OK,
but now the problem is the same as the beginning:
uploadUserFacebookDataToServer()
doesn't wait until
getUserFacebookData
downloads the user data from Facebook, that's why it won't be able to work with the necessary data given back from
getUserFacebookData
Any idea? Is there anything to do with dispatch?
UPDATE 2:
As you requested, here are the fuctions. I hope, with these information you can help me to solve this problem and to understand this whole process.
func getFacebookUserData(completionHandler: () -> Void)
{
println("getFacebookUserData")
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: nil)
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
if ((error) != nil)
{
// Process error
println("Error: \(error)")
}
else
{
let userID : NSString = result.valueForKey("id") as NSString!
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(userID, forKey: "settings_facebookID")
self.facebookID_fromSettings = userID
}
})
and
func getObjectIDfromFacebookID(completionHandler: () -> Void)
{
println("getObjectIDfromFacebookID")
var query = PFQuery(className:"users")
query.whereKey("facebookID", equalTo:facebookID_fromSettings)
println("getObjectIDfromFacebookID: facebookID: " + facebookID_fromSettings)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if (objects!.count == 0) {
// New user, registering
println("New user, registering")
}
else
{
//User is already regsitered, reading out objectID
println("User is already regsitered, reading out objectID")
}
if let objects = objects as? [PFObject] {
for object in objects {
println("objectID: " + object.objectId)
var objectID: String = object.objectId
println(objectID)
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(objectID, forKey: "settings_objectID")
}
}
}
}
completionHandler()
}
So the first function gets the facebookID from the FB-server, but this process takes time, it won't give a result immediately. The second function should work with this data, that's why it should "wait" until the first gives back the requested data.
I can solve this problem by building these 2 fuctions together in one, but that's "not elegant", and I also would like to use this (sync/async dispatch) method in other parts of the project,
Thanks for trying to help me!

You can use completion handlers:
func showLoadingAnimation() {
self.getUserFacebookData({ () -> Void in
self.uploadUserFacebookDataToServer({ () -> Void in
self.hideLoadingAnimation()
})
})
}
func getUserFacebookData(completionHandler: () -> Void) {
println("getUserFacebookData")
completionHandler()
}
func uploadUserFacebookDataToServer(completionHandler: () -> Void) {
println("uploadUserFacebookDataToServer")
completionHandler()
}
func hideLoadingAnimation() {
println("hideLoadingAnimation")
}
Once showLoadingAnimation() is called, the rest will be done asynchronously.
Reference: https://developer.apple.com/library/ios/featuredarticles/Short_Practical_Guide_Blocks/

[EDIT] Use dispatch_group_t
check following code
Step 1: showLoadingAnimation()
Step 2: dispatch_group_t faceBookService = dispatch_group_create();
Step 3:dispatch_group_async_enter(faceBookService,queue,^{
getUserFacebookData()
// The below given line should be inside completion handler or after the above task has finished
dispatch_group_leave(faceBookService);
});
Step 4:dispatch_group_async_enter(faceBookService,queue,^{
uploadUserFacebookDataToServer()
// The below given line should be inside completion handler or after the above task has finished
dispatch_group_leave(faceBookService);
});
Step 5:dispatch_group_notify(faceBookService,dispatch_get_main_queue(),^{
//Called after finishing both tasks.
hideLoadingAnimation()
});

Related

How to safely handle multiple writes in firebase which must all happen

I want to handle a friend request in my app written in Swift using Firebase. In my database, this means that the user sending the request needs to add the other user to their "sentRequests" dictionary, and the user receiving the request needs to add the user sending the requests to their "receivedRequests" dictionary. The problem is, if the user sending the request has a faulty connection and only does the first part, then it might cause issues. Either both writes should happen or none. What can I do to fix this? I included my code below for reference, but honestly if someone just sends me a good tutorial or answer here that would be just has helpful as correctly rewriting my code.
static func sendRequestFromCurrentUser(toUser userThatRequestWasSentTo : User, succeeded : #escaping (Bool)->Void ){
let ref = Database.database().reference().child("users").child(User.current.uid).child("sentRequests").child(userThatRequestWasSentTo.uid)
ref.setValue(userThatRequestWasSentTo.toDictionary(), withCompletionBlock: {(error, ref) in
if error == nil{
let currentUserRef = Database.database().reference().child("users").child(userThatRequestWasSentTo.uid).child("receivedRequests").child(User.current.uid)
currentUserRef.setValue(User.current.toDictionary(), withCompletionBlock: {(error, ref) in
if error == nil{
succeeded(true)
}
else{
succeeded(false)
}
})
}
else{
succeeded(false)
}
})
}
So I stole this from the Firebase blog and got it to match my code. The answer is fairly intuitive, I just hadn't considered it. Basically you just create a reference to the top level of your database and specify the paths you want to write to in the dictionary (so not by creating specific references with child()), and then just call updateChildValues().
static func sendRequestFromCurrentUser(toUser userThatRequestWasSentTo : User, succeeded : #escaping (Bool)->Void ){
let ref = Database.database().reference()
// Create the data we want to update
var updatedUserData : [String : Any] = [:]
updatedUserData["users/\(User.current.uid)/sentRequests/\(userThatRequestWasSentTo.uid)"] = userThatRequestWasSentTo.toDictionary()
updatedUserData["users/\(userThatRequestWasSentTo.uid)/receivedRequests/\(User.current.uid)"] = User.current.toDictionary()
// Do a deep-path update
ref.updateChildValues(updatedUserData, withCompletionBlock: { (error, ref) in
if let error = error {
print("Error updating data: \(error.localizedDescription)")
succeeded(false)
}
else{
succeeded(true)
}
})
}

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)
}
}

How to check block is still running in background while uploading large audios on server from ios app

I'm using afnetworking multipart data to upload audio file to server. I have made one function for that and upload progress is in background so that user can use app while upload is running. The function code is :
class func postAudioURL(serverlink:String,methodname:String,param:NSDictionary,userName:String,password:String,filepath:String ,CompletionHandler:(success:Bool,response:NSDictionary) -> ())
{
print(serverlink + methodname + " and Param \(param)")
let notallowchar : NSCharacterSet = NSCharacterSet(charactersInString: "01234").invertedSet
let dateStr:String = "\(NSDate())"
let resultStr:String = (dateStr.componentsSeparatedByCharactersInSet(notallowchar) as NSArray).componentsJoinedByString("")
let fileFormatedname = "RecordedAudio" + resultStr + ".wav"
let audiodata : NSData = NSData(contentsOfFile: filepath)!
let manager = AFHTTPRequestOperationManager(baseURL: NSURL(string: serverlink))
manager.requestSerializer.setAuthorizationHeaderFieldWithUsername(AUTH_USERNAME, password: AUTH_PWD)
manager.responseSerializer = AFJSONResponseSerializer(readingOptions: NSJSONReadingOptions.AllowFragments)
manager.POST(methodname, parameters: param, constructingBodyWithBlock: { (formdata:AFMultipartFormData!) -> Void in
formdata.appendPartWithFileData(audiodata, name: "AudioFile", fileName: fileFormatedname, mimeType: "Audio/wav")
}, success: { (operation: AFHTTPRequestOperation!,
responseObject: AnyObject!) -> Void in
print("Response : " + responseObject.description)
CompletionHandler(success: true, response: responseObject as! NSDictionary)
}, failure: { (operation: AFHTTPRequestOperation!,
error: NSError!) -> Void in
print("Error: " + error.localizedDescription + "error code : \(error.code)")
var statuscode:String = ""
if(operation != nil) {
print("Response string error : \(operation.responseString) response code : \(operation.response.statusCode)")
statuscode = String("\(operation.response.statusCode)")
}
let errDict:NSDictionary = ["message":"\(error?.localizedDescription)","StatusCode":statuscode]
CompletionHandler(success: false,response: errDict)
})
}
This code is working fine without any issue. I have write here to specify how I upload audio using completion block.
I have managed one local database to know that file is pending to upload, or sent or failed based on this upload function response. Most of the time I will get response either success or fail and I will update database accordingly.
The problem is that for some audio I can't get response either success or fail by any random issue or killing app. So that file stat becomes pending for all time. I need a solution for that like can I check there is some function or completion block is still running or not? By that I can update like if not running than I will update all pending state to fail and re-upload.
The problem is in rare case but still I need solution as it is considered as a bug in my app. Any help will be appreciated. Thanks in advance.
=== EDIT
I have used background task expiration so that if app is going background than still audio upload can run, and ends that background task while success or fail.
My this function call is like
func uploadAudioServiceCall(metadata : String , myFileUrl : String) {
self.bgTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({ () -> Void in
UIApplication.sharedApplication().endBackgroundTask(self.bgTask)
})
let webParam : [String:String] = ["data" : metadata]
WebService.postAudioURL(MAINLINK, methodname: UPLOAD_METHOD, param: webParam, userName: AUTH_USERNAME, password: AUTH_PWD, filepath: myFileUrl) { (success, response) -> () in
if success == true {
//"Response":"Success"
// update state pending to sent
let allPending = //get pending count from database
if allPending.count == 0 {
UIApplication.sharedApplication().endBackgroundTask(self.bgTask)
}
} else {
// update state pending to fail
let allPending = //get pending count from database
if allPending.count == 0 {
UIApplication.sharedApplication().endBackgroundTask(self.bgTask)
}
}
}
}
func applicationWillResignActive(application: UIApplication) {
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
}
almofire uploading will stop in this case, so there is no change to execute .success or .failure closures. you can set your 'pending' state as failed there.

how to correctly make cloud code request with parse?

I am attempting to make all my user sessions with Parse exclusive, meaning if a user is already logged in on a certain device in a certain location, if another device logs in with the same credentials, I want the previous session(s) to be terminated, with a message of an alert view of course. Sort of like the old AOL Instant Messaging format. I figured the code for this action should be written in the login logic, so I wrote this within my login "succession" code :
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil || error == nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self)
PFCloud.callFunctionInBackground("currentUser", withParameters: ["PFUser":"currentUser"])
//..... Get other currentUser session tokens and destroy them
}
} else {
Thats probably not the correct cloud code call, but you get the point. When the user logs in once again on another device, I want to grab the other sessions and terminate them. Does anyone know the correct way to go about making this request in swift?
I speak swift with a stutter, but I think I can answer adequately in almost-swift. The key idea is to start the success segue only after the cloud says it's okay. Here's what I think you want:
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if (user != nil) {
// don't do the segue until we know it's unique login
// pass no params to the cloud in swift (not sure if [] is the way to say that)
PFCloud.callFunctionInBackground("isLoginRedundant", withParameters: []) {
(response: AnyObject?, error: NSError?) -> Void in
let dictionary = response as! [String:Bool]
var isRedundant : Bool
isRedundant = dictionary["isRedundant"]!
if (isRedundant) {
// I think you can adequately undo everything about the login by logging out
PFUser.logOutInBackgroundWithBlock() { (error: NSError?) -> Void in
// update the UI to say, login rejected because you're logged in elsewhere
// maybe do a segue here?
}
} else {
// good login and non-redundant, do the segue
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
} else {
// login failed for typical reasons, update the UI
}
}
Please don't take me too seriously on swift syntax. The idea is to nest the segue in the completion handlers to know that you need to do it before starting it. Also, please note that the explicit placement on the main_queue within the completion handler is unnecessary. The SDK runs those blocks on the main.
A simple check to determine if a user's session is redundant (not unique) looks like this...
Parse.Cloud.define("isLoginRedundant", function(request, response) {
var sessionQuery = new Parse.Query(Parse.Session);
sessionQuery.equalTo("user", request.user);
sessionQuery.find().then(function(sessions) {
response.success( { isRedundant: sessions.length>1 } );
}, function(error) {
response.error(error);
});
});

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)
}
}
}
}
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.
The two requests are executing at the same time, so there is no attendees to get pictures from when the second request executes, if the getAttendees completion closure is going to be called multiple times then you can do something like this:
let group = dispatch_group_create()
for key in keys {
dispatch_group_enter(group)
self.getAttendee(key as String, completion:{ (result, attendee) in
if(result == true){
attendees.addEntriesFromDictionary(attendee)
self.getAttendeesPictures(attendee, completion: { (result, image) in
if result == true {
attendeesImages.append(image!)
}
dispatch_group_leave(group)
})
} else {
dispatch_group_leave(group)
}
})
}
dispatch_group_notify(group, dispatch_get_main_queue()) {}
If the result of the first request is the complete set of attendees you don't even need to use GCD, just call getAttendeesPictures inside the completion closure.
This code doesn't exactly uses the same variables and methods of the original code, it only gives the idea.
Hope it helps!
While there is definitely solution with using GCD and stuff around it, synchronization in general is pain and the more your code gets complicated, the more problems it will start showing - but I think there is one-for-all solution to that: Bolts framework from Facebook (both for android na iOS)
Bolts Framework usage
So what is so magical about it? Well, it lets you create "Tasks", and then chain them. The method in particular that you are interested in is taskForCompletionOfAllTasks: , which is made for parallel processing, just what you need. I wrote a little example for you which you can adjust to your needs:
func fetchAllInformation() -> BFTask {
// First, create all tasks (if you need more, than just create more, it is as easy as that
var task1 = BFTaskCompletionSource()
var task2 = BFTaskCompletionSource()
var tasks = [task1, task2]
// What you do, is you set result / error to tasks and the propagate in the chain upwards (it is either result, or error)
// You run task 1 in background
API.instance.fetchFirstDetailsInBackgroundWithBlock {
(object: AnyObject!, error: NSError!) -> Void in
// On error or on success, you assign result to task (whatever you want)
if error == nil {
task1.setResult(object)
} else {
task1.setError(error)
}
}
// You run task 2 in background
API.instance.fetchSecondDetailsInBackgroundWithBlock {
(object: AnyObject!, error: NSError!) -> Void in
// On error or on success, you assign result to task (whatever you want)
if error == nil {
task2.setResult(object)
} else {
task2.setError(error)
}
}
// Now you return new task, which will continue ONLY if all the tasks ended
return BFTask(forCompletionOfAllTasks: tasks)
}
Once you have main method done, you can use bolts chaining magic:
func processFullObject() {
// Once you have main method done, you can use bolts chaining magic
self.fetchAllInformation().continueWithBlock { (task : BFTask!) -> AnyObject! in
// All the information fetched, do something with result and probably with information along the way
self.updateObject()
}
}
The Bolts framework documentation / README covers basically everything there is to know about it and it is quite extensive, so I would suggest you to go through it - it is very easy to use once you get the basics. I personally use it for exactly this, and it is a blast. This answer will hopefully provide you with different solution and approach, possibly a cleaner one.
There is something wrong with this conceptually. It sounds like you want to wait until both of these functions complete before doing something else, but what you haven't explained is that getAttendeesPictures depends on the outcome of getAttendees. That means what you really want to do it execute one asynchronous block, then execute a second asynchronous block with the output of the first, and then execute your final completion block when both are finished.
GCD is not particularly suited for this; you're better of using NSOperationQueue with NSBlockOperations. There are two distinct advantages to this over GCD:
NSOperation uses familiar object-oriented syntax compared to GCD's c-type functions, so it's pretty easy to write and understand.
Operations in the queue can have explicit dependencies on one another, so you can make it clear that e.g. operation B will only be executed after operation A is complete.
There is a great writeup of this by NSHipster which I'd recommend you go read. It's talked about mostly in the abstract, but what you want to do is use NSBlockOperation to create two block operations, one for executing getAttendees and one for executing getAttendeesPictures, and then make it explicit that the second block depends on the first before adding them both to a queue. They will then both execute and you can use a completion block on the second operation to do something once both have completed.
Dave Roberts is right in his response though: an immediate problem with the code is that you don't use the output of the getAttendees function to actually create any attendees. Perhaps this part of the code is missing, but from what I can see the name and objectID are just printed out. If you want to pass something useful into the getAttendeesPictures function you will need to fix this part first.
This is off the top of my head. The idea is to read and handle new asyc data only when all of the nested blocks complete.
We leverage a while loop to handle waiting for a signal to read the next set of data.
The outside while loop continues as long as done equals false. And nothing is really going on, other than consuming cpu cycles while it waits. The if inside the loop will only be trigged (set to true) when all of the attendees have been read.
Meanwhile inside the loop we work through nested blocks, reading in the attendee and then when that completes, read their picture, and when that completes read the firebase data. Finally once we have all data from the prior blocks we stuff the data into an object which is then added to the dictionary. At that time it is determined if we are finished reading attendees and if so, bail completely. If not, we read the next attendee.
(this is conceptual)
done = false
readyToReadNextAttendee = true
while ( done == false )
{
if (readyToReadNextAttendee == true ) {
readyToReadNextAttendee = false
readAttendee
readPicture
readFirebase {
putDataIntoObject
addObjectToDictionary
if finishedReadingAttendees {
done = true
} else {
readyToReadNextAttendee = true
}
}
}
}
If you have the option of reading in all of the attendees first, you could iterate over and array as well, not reading the next index until readyToReadNextAttendee = true
One Idea i have used is to place an if statement check inside the query statement call back and place the query statement call back in a for loop (so you can loop through all of your queries), so the if statement should check if this the last call back expected, then you should execute a return statement or a deferred.resolve statement, the following is a concept code.
var list=fooKeys //list of keys (requests) i want to fetch form firebase
var array=[] // This is the array that will hold the result of all requests
for(i=xyz;loop breaking condition; i++){
Ref = new Firebase("https://yourlink.firebaseio.com/foo/" + fooKeys[i]);
Ref.once("value", function (data) {
array.push(data.val());
if(loop breaking condition == true){
//This mean that we looped over all items
return array; //or deferred.resolve(array);
}
})
}
Putting this code in a function and call it asynchronously will give you the ability to wait for the whole results before proceed in doing other stuff.
Hope you (and the others) find this beneficial.

Resources