How to write data to a file on iOS in the background - ios

I have an app that runs continuously in the background and needs to write data to a file. Occasionally I am finding a partial record written to the file so I have added some additional code to try and ensure that even if the app is backgrounded it will still have some chance of completing any writes.
Here is the code so far, and it seems to work but I am still not really sure if the APIs that I am using are the best ones for this job.
For example, is there a better way of opening the file and keeping it open so as to not have to seek to the end of the file each time ?
Is the approach for marking the task as a background task correct to ensure that iOS will allow the task to complete - it executes approximately once every second.
/// We wrap this in a background task to ensure that task will complete even if the app is switched to the background
/// by the OS
func asyncWriteFullData(dataString: String, completion: (() -> Void)?) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let taskID = self.beginBackgroundUpdateTask()
self.writeFullData(dataString)
self.endBackgroundUpdateTask(taskID)
if (completion != nil) {
completion!()
}
})
}
func beginBackgroundUpdateTask() -> UIBackgroundTaskIdentifier {
return UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({})
}
func endBackgroundUpdateTask(taskID: UIBackgroundTaskIdentifier) {
UIApplication.sharedApplication().endBackgroundTask(taskID)
}
/// Write the record out to file. If the file does not exist then
/// create it.
private func writeFullData(dataString: String) {
let filemgr = NSFileManager.defaultManager()
if let filePath = self.fullDataFilePath {
if filemgr.fileExistsAtPath(filePath) {
if filemgr.isWritableFileAtPath(filePath) {
let file: NSFileHandle? = NSFileHandle(forUpdatingAtPath: filePath)
if file == nil {
// This is a major problem so best notify the User
// How are we going to handle this type of error ?
DebugLog("File open failed for \(self.fullDataFilename)")
AlertManager.sendActionNotification("We have a problem scanning data, please contact support.");
} else {
let data = (dataString as
NSString).dataUsingEncoding(NSUTF8StringEncoding)
file?.seekToEndOfFile()
file?.writeData(data!)
file?.closeFile()
}
} else {
//print("File is read-only")
}
} else {
//print("File not found")
if createFullDataFile() {
// Now write the data we were asked to write
writeFullData(dataString)
} else {
DebugLog("Error unable to write Full Data record")
}
}
}
}

I suggest you use NSOutputStream. In addition, GCD IO can also deal your work.
Techniques for Reading and Writing Files Without File Coordinators

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 3 GCD lock variable and block_and_release error

I am using Swift 3 GCD in order to perform some operations in my code. But I'm getting _dispatch_call_block_and_release error often. I suppose the reason behind this error is because different threads modify same variable, but I'm not sure how to fix problem. Here is my code and explanations:
I have one variable which is accessed and modified in different threads:
var queueMsgSent: Dictionary<Date,BTCommand>? = nil
func lock(obj: AnyObject, blk:() -> ()) {
objc_sync_enter(obj)
blk()
objc_sync_exit(obj)
}
func addMsgSentToQueue(msg: BTCommands) {
if queueMsgSent == nil {
queueMsgSent = Dictionary.init()
}
let currentDate = Date()
lock(obj: queueMsgSent as AnyObject) {
queueMsgSent?.updateValue(msg, forKey: currentDate)
}
}
func deleteMsgSentWithId(id: Int) {
if queueMsgSent == nil { return }
for (date, msg) in queueMsgSent! {
if msg.isAck() == false && msg.getId()! == id {
lock(obj: queueMsgSent as AnyObject) {
queueMsgSent?.removeValue(forKey: date)
}
}
}
}
func runSent() -> Void {
while(true) {
if queueMsgSent == nil { continue }
for (date, msg) in queueMsgSent! {
if msg.isSent() == false {
mainSearchView?.btCom?.write(str: msg.getCommand()!)
msg.setSent(val: true)
lastMsgSent = Date()
continue
}
if msg.isAck() == true {
lock(obj: queueMsgSent as AnyObject) {
queueMsgSent?.removeValue(forKey: date)
}
continue
}
}
}
}
I start runSent method as:
DispatchQueue.global().async(execute: runSent)
I need that runSent continuously check some conditions withinn queueMsgSent, and other functions addMsgSentToQueueue and deleteMsgSentWithId are called in main thread id necessary. I am using some locking mechanism but its not working properly
I strongly suggest you to use the DispatchQueue(s) provided by Grand Central Dispatch, they makes multithreading management much easier.
Command
Let's start with your command class
class Command {
let id: String
var isAck = false
var isSent = false
init(id:String) {
self.id = id
}
}
Queue
Now we can build our Queue class, it will provide the following functionalities
This is our class should not be confused with the concept of DispatchQueue!
push a Command into the queue
delete a Command from the queue
start the processing of all the elements into the queue
And now the code:
class Queue {
typealias Element = (date:Date, command:Command)
private var storage: [Element] = []
private let serialQueue = DispatchQueue(label: "serialQueue")
func push(command:Command) {
serialQueue.async {
let newElement = (Date(), command)
self.storage.append(newElement)
}
}
func delete(by id: String) {
serialQueue.async {
guard let index = self.storage.index(where: { $0.command.id == id }) else { return }
self.storage.remove(at: index)
}
}
func startProcessing() {
Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
self.processElements()
}
}
private func processElements() {
serialQueue.async {
// send messages where isSent == false
let shouldBeSent = self.storage.filter { !$0.command.isSent }
for elm in shouldBeSent {
// TODO: add here code to send message
elm.command.isSent = true
}
// remove from storage message where isAck == true
self.storage = self.storage.filter { !$0.command.isAck }
}
}
}
How does it work?
As you can see the storage property is an array holding a list of tuples, each tuple has 2 components: Date and Command.
Since the storage array is accesses by multiple threads we need to make sure it is accessed in a thread safe way.
So each time we access storage we wrap our code into this
serialQueue.async {
// access self.storage safely
}
Each code we write into the closure 👆👆👆 shown above is added to our Serial Dispatch Queue.
The Serial Queue does process 1 closure at the time. That's why our storage property is accessed in a thread safe way!
Final consideration
The following block of code is evil
while true {
...
}
It does use all the available CPU time, it does freeze the UI (when executed on the main thread) and discharge the battery.
As you can see I replaced it with
Timer.scheduledTimer(withTimeInterval: 10, repeats: true) { timer in
self.processElements()
}
which calls self.processElements() every 10 seconds leaving plenty of time to the CPU to process other threads.
Of course it's up to you changing the number of seconds to better fit your scenario.
If you're uncomfortable with the objc mechanisms, you might take a look here. Using that, you create a PThreadMutex for the specific synchronizations you want to coordinate, then use mutex.fastsync{ *your code* } to segregate accesses. It's a simple, very lightweight mechanism using OS-level calls, but you'll have to watch out for creating deadlocks.
The example you provide depends on the object always being the same physical entity, because the objc lock uses the address as the ID of what's being synchronized. Because you seem to have to check everywhere for the existence of queueMsgSent, I'm wondering what the update value routine is doing - if it ever deletes the dictionary, expecting it to be created later, you'll have a potential race as different threads can be looking at different synchronizers.
Separately, your loop in runSent is a spin loop - if there's nothing to do, it's just going to burn CPU rather than waiting for work. Perhaps you could consider revising this to use semaphores or some more appropriate mechanism that would allow the workers to block when there's nothing to do?

How to save/load files from File Manager in background thread?

I am using File manger to save a simple JSON file. The issue is I want to do this asynchronously and on the background thread. So far I am having issues getting this accomplished. I am adding a code sample below and would welcome some help! Thanks!
init(apiService: apiService) {
self.updateAction = Action(apiService.revision)
self.updateAction.values.observeValues { [weak self] revisionDataContainer in
guard let data = revisionDataContainer.data, let this = self else {
return
}
do {
try this.save(data: data, to: this.currentRevisionUrl)
this.currentRevision.value = try this.parse(data: data) as Revision
} catch let error {
debugPrint(error.localizedDescription)
}
}
do {
if FileManager.default.fileExists(atPath: self.currentRevisionUrl.path) {
self.currentRevision.value = try self.load(from: self.currentRevisionUrl) as Revision
} else {
self.currentRevision.value = try self.load(from: self.seedRevisionUrl) as Revision
}
} catch let error {
debugPrint(error.localizedDescription)
}
}
If you want to run this code in background thread, then you need to create a new instance of FileManager instead of using default.
This method always represents the same file manager object. If you
plan to use a delegate with the file manager to receive notifications
about the completion of file-based operations, you should create a new
instance of FileManager (using the init method) rather than using the
shared object.
https://developer.apple.com/documentation/foundation/filemanager/1409234-default

Swift Background Sync Manager

I want to make an Upload Manager Singleton that uploads multipart data every 10 minutes. The upload itself is clear but how can i make a class that uploads the data in this time interval in background?
Just to specify:
The data that I want to upload is a model that has an array of objects. Each of the objects has a flag and when this flag is set the object is ready for the upload. That whole "Sync-function" should be called once and repeat itself every 10 minutes, no matter on which ViewController I am. Does anyone know how I can do this?
The code uses some external frameworks. It is based on recursion.
Alamofire // for networking
*The above framework is not important. I just used it to fasten the development process.
Sync Manager
import Foundation
import Alamofire
let SyncMangerIdentifier = "com.example.background.syncmanger"
class SyncManager: Alamofire.Manager{
static let instance = SyncManager()
private var pendingTasks = [SyncTask]() // SyncTask is a class with 3 variables [image,audio,[tags]] that are being uploading to server
private var request: Request?
private var isSyncing = false
private init(){
let configuration = NSURLSessionConfiguration.backgroundSessionConfigurationWithIdentifier(SyncMangerIdentifier)
configuration.allowsCellularAccess = Config.cellularAccess
super.init(configuration: configuration)
}
// CALL THIS FUNCTION TO START THE SYNC
// variable isSyncing guards multiple execution of syncManager
func start(){
guard !isSyncing else {
// WE ARE ALREADY SYNCING
return
}
// CALL TO PREPARE FUNCTION TO EVALUATE WHETHER WE CAN SYNC OR NOT
prepare()
}
/*
initialize the syncItem variable with the first entry from SyncTask
if we are stopping return
if syncTask isEmpty stop
if there are no items in first syncTask remove the task and restart the process.
*/
private func prepare(){
// I use a database query to store & retrieve pendingTasks
guard !pendingTasks.isEmpty else{
// todo no more data to sync
isSyncing = false // syncing process ended
// Notify app that your long running task has finished
(UIApplication.sharedApplication().delegate as? AppDelegate)?.endBackgroundSyncTask()
return
}
isSyncing = true // we are in syncing process
// Notify app that our long running task has begun
(UIApplication.sharedApplication().delegate as? AppDelegate)?.beginBackgroundRestoreTask()
// Call function to start the first upload
uploadFileOrData()
}
}
/**
upload the files & data from array recursively
*/
private func uploadFileOrData(){
var task = pendingTasks[0]
let imageUrl = task.imageUrl
let audioUrl = task.audioUrl
let tags = task.tags.reduce(""){ prev, next in
if prev.isEmpty{
return next.text
}
return "\(prev),\(next.text)"
}
let form : (MultipartFormData) -> () = { data in
if imageUrl.checkResourceIsReachableAndReturnError(nil){
data.appendBodyPart(fileURL: imageUrl, name: "image")
}
if audioUrl.checkResourceIsReachableAndReturnError(nil){
data.appendBodyPart(fileURL: audioUrl, name: "audio")
}
data.appendBodyPart(data: tags.dataUsingEncoding(NSUTF8StringEncoding,allowLossyConversion: false)!, name: "tags")
}
upload(.POST, Api.fileUploadUrl, multipartFormData: form ,encodingCompletion: {
// Call function to process the response
self.processUploadFileResponse($0)
})
}
private func processUploadFileResponse(result: Manager.MultipartFormDataEncodingResult){
switch result {
case .Success(let upload, _, _):
// PERFORM ACTION ON SUCCESS
// MOVE TO NEXT LOCATION
self.moveToNextTask()
case .Failure(_):
// PERFORM ACTION ON FALIURE
// MOVE TO NEXT LOCATION
self.moveToNextTask()
}
}
private func moveToNextTask(){
// DELETE pendingTasks[0] & CALL prepare() function
// If you want to repeat after every 10 MINUTE
// Then wrap your function call 'prepare()' inside dispatch_after
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(10 * 60 * Double(NSEC_PER_SEC)) // 10 * 60 to convert seconds into minute
),
dispatch_get_main_queue(), {
self.prepare()
})
}
AppDelegate class
// bind the alamofire backgroundCompletionHandler
func application(application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: () -> Void) {
// NSLog("handle events for background: \(identifier)")
if identifier == SyncMangerIdentifier{
SyncManager.instance.backgroundCompletionHandler = completionHandler
}
}
// Identifier for long running background task for SyncManager class
var backgroundSyncTask: UIBackgroundTaskIdentifier?
// Call this at the beginning of syncing
func beginBackgroundSyncTask() {
backgroundRestoreTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler({
self.endBackgroundRestoreTask()
})
}
// Call this when syncing process ends
func endBackgroundSyncTask() {
guard backgroundSyncTask != nil else {
return
}
UIApplication.sharedApplication().endBackgroundTask(self.backgroundSyncTask!)
self.backgroundSyncTask = UIBackgroundTaskInvalid
}
Note
In order to continue request when your app enters background you may need to enable BackGroundFetchMode from app's Capabilities section
Since you want the method of upload to be called in all ViewControllers(VCs) One approach would be :-
extension UIViewController
{
func uploadData(parameters) ->Bool
{
return true/false;
}
}
Then in all ViewControllers(VCs) you can call uploadData method in viewDidLoad or in specific function like :-
if(self.uploadData(parameters)) // if true method is called i.e. new objects available to upload or 10mins have passed as per your requirement
{
}
Second Approach would be to define the NSTimer part which checks whether 10 mins have passed in AppDelegate and create a empty Swift file which does Upload and call the method in didFinishLaunchingWithOptions within AppDelegate.
There are many ways to go about it but it depends on how flow needs to happen in your app.
Note:- Use
NSURLSessionUploadTask - > To upload and
NSTimer -> To check whether 10 mins have passed

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