My WatchKit extension makes heavy use of openParentApplication to load data and images into the WatchKit app, as adviced by Apple. This is working fine, especially after using the clever advice here:
http://www.fiveminutewatchkit.com/blog/2015/3/11/one-weird-trick-to-fix-openparentapplicationreply
However, it's only working for about 15 minutes. After that, the WatchKit extension fails to wake up the iPhone application in the background and the iOS AppDelegate handleWatchKitExtensionRequest is never called. If I manually open the iPhone app, the call gets through and it answers directly to the WatchKit extension, but that is hardly something I can ask the user to do... I want the WatchKit app to usable without having to manually wake up the iOS app every 15 minutes.
I'm using iOS 8.2 on the testing device, Swift and Xcode 6.2. Maybe it could be related to using a developer provisioning profile? Anyone else out there having experienced this?
Code used in utility class WatchUtils, this is used on several occations in the app:
class func openPhoneApp(userInfo: [NSObject : AnyObject], complete:(reply:[NSObject : AnyObject]!, error:NSError!) -> Void) {
WKInterfaceController.openParentApplication(userInfo, reply: { (reply:[NSObject : AnyObject]!, error:NSError!) -> Void in
complete(reply:reply, error:error)
})
}
openPhoneApp is used like this:
WatchUtils.openPhoneApp(requestData, complete: { (reply, error) -> Void in
if let reply = reply {
// Do stuff
}
})
AppDelegate:
func application(application: UIApplication!, handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!, reply: (([NSObject : AnyObject]!) -> Void)!) {
// Bogus task for keeping app going
var bogusTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler { () -> Void in
}
// End bogus task after 2 seconds
delay(2.0, closure: { () -> () in
UIApplication.sharedApplication().endBackgroundTask(bogusTask)
})
// Code here for collecting information from phone app
// ...
var replyDict:[NSObject : AnyObject] = ["userHighscore":45]
reply(replyDict)
UIApplication.sharedApplication().endBackgroundTask(bogusTask) // End the bogusTask in case the work was faster than 2 seconds
}
// Utility function for delay
func delay(delay:Double, closure:()->()) {
dispatch_after(
dispatch_time(
DISPATCH_TIME_NOW,
Int64(delay * Double(NSEC_PER_SEC))
),
dispatch_get_main_queue(),
closure
)
}
I had the same problem and reading the docs fixed it for me.
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/#//apple_ref/occ/intfm/UIApplicationDelegate/application:handleWatchKitExtensionRequest:reply:
Because this method is likely to be called while your app is in the background, call the beginBackgroundTaskWithName:expirationHandler: method at the start of your implementation and the endBackgroundTask: method after you have processed the reply and executed the reply block. Starting a background task ensures that your app is not suspended before it has a chance to send its reply.
Some of my code:
nint taskId = 0;
public override void HandleWatchKitExtensionRequest (UIApplication application, NSDictionary userInfo, Action<NSDictionary> reply)
{
try {
taskId= UIApplication.SharedApplication.BeginBackgroundTask(delegate
{ //this is the action that will run when the task expires
});
if (userInfo != null && userInfo.Keys [0] != null && userInfo.Keys [0].ToString() == "match") {
MyWebClient wc = new MyWebClient ();
wc.Encoding = Encoding.UTF8;
wc.DownloadStringCompleted += (object sender, DownloadStringCompletedEventArgs e) => {
String res = e.Result;
reply (new NSDictionary (
"justTesting", new NSString (res)
));
if(taskId != 0)
UIApplication.SharedApplication.EndBackgroundTask(taskId);
};
wc.DownloadStringAsync(new Uri(myUrl));
}
}
After I changed to doing this it has been rock stable for days for me while previously it always stopped working after 5-15 minutes.
Related
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")
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
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
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.
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()
});