Error while running multiple SFSpeechRecognitionTask in background - ios

As per requirement of the App I am developing, I have to pass multiple audio files to SFSpeechRecognizer and get the transcription in return.
I did it in two ways
First Method - Using Recursion (Running correctly, you can skip it if you want)
I have first completed this task by getting transcription one by one. i.e when the SFSpeechRecognitionTask gets completed, the result gets saved and the process runs again through a recursive call.
class Transcription
{
let url = [URL(fileURLWithPath: "sad")]
var fileCount = 3
let totalFiles = 4;
func getTranscriptionRecursive()
{
getTranscriptionOfAudioFile(atURL: url[fileCount], fileCount: fileCount, totalFiles: totalFiles) { (result) in
if(self.fileCount <= self.totalFiles)
{
self.fileCount = self.fileCount+1
self.getTranscriptionRecursive()
}
}
}
func getTranscriptionOfAudioFile(atURL url: URL, fileCount: Int, totalFiles: Int, completion: #escaping ((SFSpeechRecognitionResult?)->Void))
{
let request = SFSpeechURLRecognitionRequest(url: url)
request.shouldReportPartialResults = false
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
if (recognizer?.isAvailable)! {
recognizer?.recognitionTask(with: request) { result, error in
//error handling
completion(result)
}
}
}
}
This method worked great but it takes way too much time as each SFSpeechRecognizer request takes time to complete.
Second Method - Using Loop and Background Thread
(This one is having the issue)
I tried to create multiple requests and execute them in background at once.
For that I created a For Loop till the count of audio files, and in that loop I called the function to create SFSpeechRecognizer Request and task.
for index in 0..<urls.count
{
DispatchQueue.global(qos: .background).async {
self.getTranscriptionOfAudio(atURL: self.urls[index]) { (result, myError, message) in
//error handling
//process Results
}
}
}
where as the function to get speech recognition results is
func getTranscriptionOfAudio(atURL audioURL: URL?, completion: #escaping ((SFSpeechRecognitionResult? , Error?, String?)->Void))
{
let request = SFSpeechURLRecognitionRequest(url: audioURL!)
request.shouldReportPartialResults = false
let recognizer = SFSpeechRecognizer(locale: Locale(identifier: "en-US"))
if (recognizer?.isAvailable)! {
recognizer?.recognitionTask(with: request) { result, error in
//error handling
completion(results,nil,nil)
}
} else {
completion(nil,nil,"Reognizer could not be initialized");
}
}
When I run this code, only one task executes and other tasks give this error
+[AFAggregator logDictationFailedWithError:] Error
Domain=kAFAssistantErrorDomain Code=209 "(null)"
I searched this error on internet but there is no documentation that contains its details.
It might be due to running SFSpeechRecognitionTask in parallel, but in official Apple documents here they didn't forbid to do so, as we can create SFSpeechRecognitionRequest object separately. We don't use singleton object of SFSpeechRecognizer
let me know if anyone has any idea what is going on and what you suggest me to do.

Related

GKTurnBasedMatch saveCurrentTurnWithMatchData returning an error on every other call

The player takes multiple actions before completing a turn. After each action, I call saveCurrentTurnWIthMatchData, with the match data updated.
[gameMatch saveCurrentTurnWithMatchData: matchData completionHandler: ^(NSError *error){
if (error) {
NSLog(#"Error updating match = %#",error);
}
}];
On every other call I get "Error Domain=GKServerErrorDomain Code=5002 "status = 5002, Unexpected game state version expectedGameStateVersion='null'"
The GKTurnBasedMatch.state = 3 (GKTurnBasedMatchStatusMatching) in every call. I'm not changing this, I just check before the call. I have no idea if this is relevant.
Any suggestion what to try?
the "Unexpected game state version" error happens irregularly and is hard to reproduce -- although i can often reproduce it by calling saveCurrentTurn several times in rapid succession. it would be useful to have clarity from Apple on this since it appears to be server side (but i'm not sure). i wrote a unit test that does stress testing on GKTurnBasedMatch.saveCurrentTurn. it fails irregularly but often up to 20% of the time.
i have no full solution only a partial one. to partially mitigate the problem, you can wrap your saveCurrentTurn calls in a task queue, that way they wait for the previous one to finish. not a solution, but helps.
let dqt:DispatchQueueTask = {
gkTurnBasedMatch.saveCurrentTurn(withMatch:payload) { error in
//handle error
TaskQueue.completion() //step to next task
}
}
TaskQueue.add(task:dqt)
and here is the TaskQueue class i use
import Foundation
/*
Uses the DispatchQueue to execute network commands in series
useful for server commands like GKTurnBasedMatch.saveCurrentTurn(...)
Usage:
let doSomethingThatTakesTime:DispatchQueueTask = {
...
TaskQueue.completion()
}
TaskQueue.add(task: doSomethingThatTakesTime)
*/
typealias DispatchQueueTask = () -> ()
let DispatchQueue_serial = DispatchQueue(label: "org.my.queue.serial")
class TaskQueue {
static var isRunning:Bool = false
static var tasks:[DispatchQueueTask] = []
static func add(task:#escaping DispatchQueueTask) {
tasks.append(task)
run()
}
static func run() {
guard !isRunning else { return }
guard tasks.count > 0 else { return }
let task = tasks.removeFirst()
DispatchQueue_serial.async {
TaskQueue.isRunning = true
task()
}
}
static func completion() {
TaskQueue.isRunning = false
TaskQueue.run()
}
}

Swift how to fire callback if a given function hasn't been called within <x> seconds?

When uploading data (in my case, images) to an AWS S3 bucket, the default timeout for the upload operation is 50 minutes. The uploads I plan on doing are pretty small, ~150kb per photo, up to 9 photos, so if a user has a decent connection, it should only take a few seconds max. I plan on locking the UI on a 'uploading' spinning wheel while the upload is happening, so I need to guarantee a callback on a success or failure so I can unlock the UI and the user can move on.
The problem is that if the user has no connection, the AWSS3TransferUtilityUploadTask won't give a failure callback until the 50 minute timeout occurs- which is obviously too long for my use case.
Here is the relevant code for this:
import UIKit
import AWSCore
import AWSS3
enum UploadImageResult {
case success
case failure
}
struct UploadImagePacket {
let name : String
let image : UIImage
}
class ImageUploader {
private var packets : [UploadImagePacket] = []
private var currentPacketIndex = 0
private let bucketName = "this-is-not-a-real-bucket-name"
private var transferUtility : AWSS3TransferUtility {
get {
return AWSS3TransferUtility.default()
}
}
init() {
// Set up service configurations for user
let accessKey = "XXXXXXXXXXXXXXXXXXXX"
let secretKey = "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
let credentials = AWSStaticCredentialsProvider(accessKey: accessKey, secretKey: secretKey)
let configuration = AWSServiceConfiguration(region: AWSRegionType.USEast2, credentialsProvider: credentials)
AWSServiceManager.default().defaultServiceConfiguration = configuration
}
// Upload a single image to S3 bucket
private func uploadImage(_ packet : UploadImagePacket, completion : #escaping (UploadImageResult) -> Void) {
let imageData = packet.image.jpegData(compressionQuality: 1)!
// Called periodically to report upload progress
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { (task, progress) in
DispatchQueue.main.async(execute: {
// For solution 2, I would create a timer and reset it every time this closure is fired . . .
})
}
// Called when upload is complete
var completionHandler: AWSS3TransferUtilityUploadCompletionHandlerBlock?
completionHandler = { (task, error) -> Void in
DispatchQueue.main.async(execute: {
if let error = error {
// Failed
NSLog(error.localizedDescription)
completion(.failure)
} else {
// Did not fail
completion(.success)
}
})
}
// Actually begin upload
transferUtility.uploadData(imageData, bucket: bucketName, key: packet.name, contentType: "image/jpeg", expression: expression, completionHandler: completionHandler).continueWith { (task) -> Any? in
if let error = task.error {
NSLog("S3 upload failed with error : \(error.localizedDescription)")
completion(.failure)
}
return nil
}
}
// Upload multiple images to S3
func uploadImages(_ packets : [UploadImagePacket], completion : #escaping (UploadImageResult) -> Void) {
self.packets = packets
currentPacketIndex = 0
_uploadImages(completion: completion)
}
private func _uploadImages (completion : #escaping (UploadImageResult) -> Void) {
// If no packet to upload, call completion with a success
guard currentPacketIndex < packets.count else {
completion(.success)
return
}
NSLog("Uploading \(currentPacketIndex) of \(packets.count) images . . .")
// Upload packet & upload rest of packets in completion
uploadImage(packets[currentPacketIndex]) { result in
switch result {
case .success:
// On success, move onto next packet
self.currentPacketIndex += 1
self._uploadImages(completion: completion)
case .failure:
// On failure, call completion with a failure
completion(.failure)
}
}
}
#objc func uploadTimeOut() {
// This would be used for solution 2 to cancel all of the transfer utility tasks & call the completion with a failure . . .
}
}
The two potential solutions I have determined are
customize AWS's default timeout for this process, or
Use a swift Timer/NSTimer and instead of a timeout that is called if an upload isn't complete within [x] seconds, have a timeout that is called if the upload hasn't made any progress within [x] seconds.
For solution 1
I simply haven't been able to find a way to do this. I have only been able to find relative solutions pertaining to downloads, not uploads- the best hints found in this post.
For solution 2
Ideally I can use solution 1, but if not, the best way I have found to do this is to create a Timer, that will call a timeout after x amount of seconds (something like 5 seconds), and reset the timer every time the expression.progressBlock is called. My issue with this is the only way I've found to reset a Timer to invalidate and then redefine the timer, which seems awfully expensive considering how rapidly expression.progressBlock is called.
You can define a custom time out by setting timeoutIntervalForResource and registering the Custom configuration
let transferUtilityConfigurationShortExpiry = AWSS3TransferUtilityConfiguration()
transferUtilityConfigurationShortExpiry.isAccelerateModeEnabled = false
transferUtilityConfigurationShortExpiry.timeoutIntervalForResource = 2 //2 seconds
AWSS3TransferUtility.register(
with: AWSServiceManager.default().defaultServiceConfiguration!,
transferUtilityConfiguration: transferUtilityConfigurationShortExpiry,
forKey: "custom-timeout"
)
And then use the associated transferUtility
AWSS3TransferUtility.s3TransferUtility(forKey: "custom-timeout")

How to schedule a synchronous sequence of asynchronous calls in Combine?

I'd like to handle a series of network calls in my app. Each call is asynchronous and flatMap() seems like the right call. However, flatMap processes all arguments at the same time and I need the calls to be sequential -- the next network call starts only after the previous one is finished. I looked up an RxSwift answer but it requires concatMap operator that Combine does not have. Here is rough outline of what I'm trying to do, but flatMap fires all myCalls at the same time.
Publishers.Sequence(sequence: urls)
.flatMap { url in
Publishers.Future<Result, Error> { callback in
myCall { data, error in
if let data = data {
callback(.success(data))
} else if let error = error {
callback(.failure(error))
}
}
}
}
After experimenting for a while in a playground, I believe I found a solution, but if you have a better idea, please share. The solution is to add maxPublishers parameter to flatMap and set the value to max(1)
Publishers.Sequence(sequence: urls)
.flatMap(maxPublishers: .max(1)) // <<<<--- here
{ url in
Publishers.Future<Result, Error> { callback in
myCall { data, error in
if let data = data {
callback(.success(data))
} else if let error = error {
callback(.failure(error))
}
}
}
}
You can also use prepend(_:) method on observable which creates concatenated sequence which, I suppose is similar to Observable.concat(:) in RxSwift.
Here is a simple example that I tried to simulate your use case, where I have few different sequences which are followed by one another.
func dataTaskPublisher(_ urlString: String) -> AnyPublisher<(data: Data, response: URLResponse), Never> {
let interceptedError = (Data(), URLResponse())
return Publishers.Just(URL(string: urlString)!)
.flatMap {
URLSession.shared
.dataTaskPublisher(for: $0)
.replaceError(with: interceptedError)
}
.eraseToAnyPublisher()
}
let publisher: AnyPublisher<(data: Data, response: URLResponse), Never> = Publishers.Empty().eraseToAnyPublisher()
for urlString in [
"http://ipv4.download.thinkbroadband.com/1MB.zip",
"http://ipv4.download.thinkbroadband.com/50MB.zip",
"http://ipv4.download.thinkbroadband.com/10MB.zip"
] {
publisher = publisher.prepend(dataTaskPublisher(urlString)).eraseToAnyPublisher()
}
publisher.sink(receiveCompletion: { completion in
print("Completed")
}) { response in
print("Data: \(response)")
}
Here, prepend(_:) operator prefixes the sequence and so, prepended sequences starts first, completes and next sequence start.
If you run the code below, you should see that firstly 10 MB file is download, then 50 MB and at last 1 MB, since the last prepended starts first and so on.
There is other variant of prepend(_:) operator which takes array, but that does not seem to work sequentially.

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.

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