I need to make a synchronization with a server when my application is running.
Sometimes this synchronization can take several minutes.
On the iPhone this task is interrupted if the user press the home button or if the device does auto-lock.
I tried something like:
backgroundTaskID = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
}];
[self Synchronization];
funcao sincronização:
-(void)Synchronization{
object=nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[TheAppDelegate setSync:YES];
send = NO;
responseObj = [[ConteinerInicial alloc] init];
object = [self fillObjectContainer:1];
error = NO;
[[objectManager HTTPClient] setDefaultHeader:#"Token" value:TheAppDelegate.token.token];
[[objectManager HTTPClient] setDefaultHeader:#"Username" value:TheAppDelegate.token.userName];
[[objectManager HTTPClient] setDefaultHeader:#"IDDevice" value:TheAppDelegate.token.IDDevice];
[objectManager postObject:object path:#"" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *result){
NSArray *response = [result array];
NSLog(#"Loading mapping result: %#", result);
NSDictionary *headerDictionary = [operation.HTTPRequestOperation.response allHeaderFields];
NSString *authenticationStatus = [headerDictionary objectForKey:#"AuthenticationStatus"];
// if server returns "NotAuthorized", user must login again
if ([authenticationStatus isEqualToString:#"NotAuthorized"]) {
[AppDelegate showErrorAlert:#"Login expired!"];
[TheAppDelegate clearLoginToken];
error = YES;
return;
}
responseObj = [response objectAtIndex:0];
if(responseObj.Package.count>0)
{
[self savePackage:responseObj tipo:1];
[self Synchronization];
}else
{
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
}
} failure: ^(RKObjectRequestOperation *operation, NSError *reportError){
RKLogError(#"Operation failed with error: %#", reportError);
[AppDelegate showErrorAlert:#"There is some problem with the connection or the server! Please, check your connection or the address of the server and try again."];
send = YES;
[TheAppDelegate setSync:NO];
error = YES;
}];
});
}
And at the end of synchronization is ready:
[[UIApplication sharedApplication] endBackgroundTask:backgroundTaskID];
backgroundTaskID = UIBackgroundTaskInvalid;
but this code does not work, anyone have an idea how I can overcome this challenge?
The way to sync is to be asking for data until no longer exist. but with this code it just ends up in the backgroud object that is receiving at the time that goes into background
Your code is calling [self synchronization]; inside the expiration handler. That block is only called when your app runs out of time for running in the background. So you are trying to start your synchronisation only when there is no time left to do it.
Call [self synchronization]; outside of the block. The block should contain clean up / pause code and end the background task...
Also, look at using NSURLSession for good iOS 7 compatibility.
Related
I'm using this method to upload images/videos to S3 (see code below)
I'm wondering if a user backgrounds the app and much later opens it back up can the pause/resume be used to resume all those uploads? It looks like maybe it's persistently caching the uploads in the SDK with self.cache.diskCache. In other words can I use a UIBackgroundTask to pause the downloads in the expiration handler and when the app comes back in the foreground resumeAll?
I was watching this talk on how to do persistent uploads with NSURLSession and am trying to design a good way to do it in my current app.
Something like this:
- (void)applicationWillEnterForeground:(NSNotification *)notification
{
[self.transferManager resumeAll:^(AWSRequest *request) {
POLYLog(#"request %#",request.description);
}];
}
- (void)applicationDidEnterBackground:(NSNotification *)notification
{
UIApplication* app = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier task;
task = [app beginBackgroundTaskWithExpirationHandler:^{
[self.transferManager pauseAll];
[app endBackgroundTask:task];
task = UIBackgroundTaskInvalid;
}];
}
Reference from AWS Docs:
// Construct the NSURL for the download location.
NSString *downloadingFilePath = [NSTemporaryDirectory() stringByAppendingPathComponent:#"downloaded-myImage.jpg"];
NSURL *downloadingFileURL = [NSURL fileURLWithPath:downloadingFilePath];
// Construct the download request.
AWSS3TransferManagerDownloadRequest *downloadRequest = [AWSS3TransferManagerDownloadRequest new];
downloadRequest.bucket = #"myBucket";
downloadRequest.key = #"myImage.jpg";
downloadRequest.downloadingFileURL = downloadingFileURL;
Now we can pass the download request to the download: method of the TransferManager client. The AWS Mobile SDK for iOS uses AWSTask to support asynchronous calls to Amazon Web Services. The download: method is asynchronous and returns a AWSTask object, so we’ll use it accordingly:
[[transferManager upload:uploadRequest] continueWithExecutor:[AWSExecutor mainThreadExecutor]
withBlock:^id(AWSTask *task) {
if (task.error) {
if ([task.error.domain isEqualToString:AWSS3TransferManagerErrorDomain]) {
switch (task.error.code) {
case AWSS3TransferManagerErrorCancelled:
case AWSS3TransferManagerErrorPaused:
break;
default:
NSLog(#"Error: %#", task.error);
break;
}
} else {
// Unknown error.
NSLog(#"Error: %#", task.error);
}
}
if (task.result) {
AWSS3TransferManagerUploadOutput *uploadOutput = task.result;
// The file uploaded successfully.
}
return nil;
}];
Using a Background Fetch we trigger a method
- (void)sendPush:(SPUserInfo *)userInfo {
PFQuery *findUserQuery = [PFUser query];
[findUserQuery whereKey:#"objectId" equalTo:userInfo.userID];
[findUserQuery findObjectsInBackgroundWithBlock:^(NSArray* arr, NSError* err) {
if (err) {
MLog(#"Error Finding User to Spark: %#", err.description);
}
else {
if (arr.count) {
MLog(#"User found");
PFObject *spark = [PFObject objectWithClassName:kSparkClassName];
[...]
[spark saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (!error) {
//save
// send push through Parse
[...]
PFPush *push = [PFPush push];
NSString *channel = [NSString stringWithFormat:#"u_%#", receiver.objectId];
[push setChannels:#[channel]];
[push setData:data];
[push sendPushInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
if (nil == error) {
MLog(#"Push success");
//do the stuff
[self.queueSendingSpark removeObject:userInfo];
[self saveSendingSparkQueue];
}
else {
MLog(#"Push Error : %#", error.description);
}
}];
}
else {
MLog(#"Spark is not saved on parse | %#", error);
}}];
}
else {
MLog(#"ZERO Users");
}
}
}];
}
This is all fine and dandy if the app is running in the foreground or if the phone is awake. But when the iPhone is locked/asleep, the findObjectsInBackgroundWithBlock never returns any data.
Is this expected behaviour? Is there a way to ensure this query, and others, are returning as normal when a device is asleep?
So theres a property that you can use to handle this...
#property (nonatomic, assign) UIBackgroundTaskIdentifier fileUploadBackgroundTaskId;
Within your init method set the property like so:
self.fileUploadBackgroundTaskId = UIBackgroundTaskInvalid;
Set the value when needed
// Request a background execution task to allow us to finish uploading the photo even if the app is backgrounded
self.fileUploadBackgroundTaskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[[UIApplication sharedApplication] endBackgroundTask:self.fileUploadBackgroundTaskId];
}];
Run your query, then when the query is finished...
[[UIApplication sharedApplication] endBackgroundTask:self.fileUploadBackgroundTaskId];
We're currently trying to get HealthKit to work in the background, in order to deliver steps data to our server when the App is closed.
For experimental purposes we've created a brand new iOS project in XCode, enabled HealhtKit and all background modes in Compabilities. After that, we pretty much run the code (see further down).
So what happens first is that the app ofcourse asks for the permissions, which we grant. What we're expecting is that the app should keep deliver the steps data every hour, to the server. But it doesnt do that, it seems like the app cant do anything when it's not active.
The app only deliver data when it gets resumed or started, but not at all from the background (Soft-closed / Hard-closed)
appdelegate.m:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setTypes];
return YES;
}
-(void) setTypes
{
self.healthStore = [[HKHealthStore alloc] init];
NSMutableSet* types = [[NSMutableSet alloc]init];
[types addObject:[HKObjectType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]];
[self.healthStore requestAuthorizationToShareTypes: types
readTypes: types
completion:^(BOOL success, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
[self observeQuantityType];
[self enableBackgroundDeliveryForQuantityType];
});
}];
}
-(void)enableBackgroundDeliveryForQuantityType{
[self.healthStore enableBackgroundDeliveryForType: [HKQuantityType quantityTypeForIdentifier: HKQuantityTypeIdentifierStepCount] frequency:HKUpdateFrequencyImmediate withCompletion:^(BOOL success, NSError *error) {
}];
}
-(void) observeQuantityType{
HKSampleType *quantityType = [HKSampleType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount];
HKObserverQuery *query =
[[HKObserverQuery alloc]
initWithSampleType:quantityType
predicate:nil
updateHandler:^(HKObserverQuery *query,
HKObserverQueryCompletionHandler completionHandler,
NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (completionHandler) completionHandler();
[self getQuantityResult];
});
}];
[self.healthStore executeQuery:query];
}
-(void) getQuantityResult{
NSInteger limit = 0;
NSPredicate* predicate = nil;
NSString *endKey = HKSampleSortIdentifierEndDate;
NSSortDescriptor *endDate = [NSSortDescriptor sortDescriptorWithKey: endKey ascending: NO];
HKSampleQuery *query = [[HKSampleQuery alloc] initWithSampleType: [HKQuantityType quantityTypeForIdentifier:HKQuantityTypeIdentifierStepCount]
predicate: predicate
limit: limit
sortDescriptors: #[endDate]
resultsHandler:^(HKSampleQuery *query, NSArray* results, NSError *error){
dispatch_async(dispatch_get_main_queue(), ^{
// sends the data using HTTP
[self sendData: [self resultAsNumber:results]];
});
}];
[self.healthStore executeQuery:query];
}
I found this out a little while ago when talking to someone from Apple. Apparently you can't access HK data in the background if the device is locked:
NOTE
Because the HealthKit store is encrypted, your app cannot read data
from the store when the phone is locked. This means your app may not
be able to access the store when it is launched in the background.
However, apps can still write data to the store, even when the phone
is locked. The store temporarily caches the data and saves it to the
encrypted store as soon as the phone is unlocked.
from:
https://developer.apple.com/library/ios/documentation/HealthKit/Reference/HealthKit_Framework/
I see something that might be causing an issue in your AppDelegate, particularly this line:
[[NSURLConnection alloc] initWithRequest:request delegate:self];
This is creating an NSURLConnection, but not starting it. Try changing it to this:
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
Edit: After taking a second look at the docs
They recommend setting up your observer queries in your application didFinishLaunchingWithOptions: method. In your code above, you set the HKObserverQuery up in the authorization handler, which is called on a random background queue. Try making this change to set it up on the main thread:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[self setTypes];
[self observeQuantityType];
return YES;
}
HKObserverQuery Reference
I am trying out MagicalRecord 3.0 for the first time and I can't make it work.
This is what I am doing.
1- Setup stack:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[MagicalRecord setupClassicStackWithSQLiteStoreNamed:#"Model"];}
My model name is "Model.xcdatamodeld".
2- Create entities and Save:
Preferences *p = [Preferences createEntity];
p.visibilityWindow = #"08:00-23:30";
[[NSManagedObjectContext MR_context] saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
if (success) {
NSLog(#"You successfully saved your context.");
} else if (error) {
NSLog(#"Error saving context: %#", error.description);
}
}];
3- Load data:
NSArray *matches;
NSError * error = nil;
matches = [Preferences MR_findAll];
Preferences *p = nil;
NSLog(#"Fetch request data %#",matches);
if(!matches || error || ([matches count] >1)){
//handle error;
NSLog(#"Error %# matches count %lu", error, (unsigned long)[matches count]);
}else if ([matches count]){
p = [matches firstObject];
NSLog(#"Preferences found in coredata %#", p);
}else{
NSLog(#"No matches %i", [matches count]);
}
I am also "Cleaning up" as the documentation suggests:
- (void)applicationWillTerminate:(UIApplication *)application {
[MagicalRecord cleanUp];
}
It was working totally fine when I was using the traditional core data framework.
It says that I am saving successfully. But when I quit the app and try to run it again it doesn't work anymore.
What am I doing wrong?
Also, from the posts I read, everyone talks about a "MR_defaultContext". Was it deprecated?
A couple of thoughts:
Looking at the code for MagicalRecord 3.0, MR_context returns a NSPrivateQueueConcurrencyType. Using MR_createEntity appears to use a different, main queue context. Try creating your entity with MR_createEntityInContext: and passing in MR_context.
Also, without knowing when you are calling each of the three methods, try using MR_saveToPersistentStoreAndWaitWithError:.
EDIT:
Probably the quickest thing to do to test is to change step 2:
Preferences *p = [Preferences createEntity];
p.visibilityWindow = #"08:00-23:30";
NSError *error; // add this
BOOL success = [p.managedObjectContext MR_saveToPersistentStoreWithError:error] // change this
if (success) {
NSLog(#"You successfully saved your context.");
} else if (error) {
NSLog(#"Error saving context: %#", error.description);
}
I had the same problem... there is that I used (with your stuff) also I add the option to store with UIBackgroundTaskIdentifier (opcional)
--
Preferences *p = [Preferences createEntity];
p.visibilityWindow = #"08:00-23:30";
[SomethingManagerObject saveIntoContext]
--
SomethingManagerObject
+ (void) saveWithContext{
// THIS IS opcional!!! -> Prepar for BackgroundTask
UIApplication * application = [UIApplication sharedApplication];
__block UIBackgroundTaskIdentifier bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
// Get the context
NSManagedObjectContext *localContext = [[MagicalRecordStack defaultStack] context];
// Save the modification in the context
[localContext MR_saveToPersistentStoreWithCompletion:^(BOOL success, NSError *error) {
[application endBackgroundTask:bgTask];
bgTask = UIBackgroundTaskInvalid;
}];
}
Or you can save with managedObjectContext of the entity. In saveWithContext method just add parameter and send the p.managedObjectContext. Inside change [[MagicalRecordStack defaultStack] context]
The SomethingManagerObject is a NSObject that you can use to have class methods like that... save...create...
I am using AFNetworking for downloading files having size between 1 to 4 gbs,.
Currently while downloading such a huge files I pause the current download when app enters in background state and resume when it gets active.
But what happens wrong in my case is that, first time while downloading when I minimize the app I pause it and when I again maximize app after 20 to 30 mins I resume it and download continues from where it was left paused last time. But it works only first time, second time when I again minimize the app with same download it gets paused and when I again maximizes it , it stuck at the same point showing some wrong values for progress and current transfer speed and it never moves forward or never continues current download.
Strange behaviour??
I have tried both old and new (2.0) versions but no luck.
Can you guess what is happening wrong in my case?
Or
Please suggest me some good alternatives to using AFNetworking.
UPDATE
Method called to download file
-(void) downloadTracksFromProgramArray:(NSArray*) programs
{
if (programs.count == 0) {
return;
}
queue = [[NSOperationQueue alloc] init];
[queue setMaxConcurrentOperationCount:1];
queueSize = 0;
urlString = [programs objectAtIndex:0];
NSString *filename = [urlString lastPathComponent];
// 11-09-12
// remove query string from aws
NSString *string1 = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:#"Documents/%#",[filename lastPathComponent]]] ;
// remove query string from aws
NSArray *jaysarray = [string1 componentsSeparatedByString:#"?"];
NSString *downloadPath1 = [NSString stringWithFormat:#"%#",[jaysarray objectAtIndex:0]];
extract_file_path_after_download = downloadPath1;
NSLog(#"%#",[jaysarray objectAtIndex:0]);
// NSLog(#"%#",[jaysarray objectAtIndex:1]);
current_downloading_file_path = [downloadPath1 copy];
NSLog(#"download url %#",[NSURL URLWithString:urlString]);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString]];
operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.outputStream = [NSOutputStream outputStreamToFileAtPath:downloadPath1 append:NO];
//handle successful completion of each track download
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"Successfully downloaded file to %#", downloadPath1);
//if ([[queue operations] count] == 0) {
NSNotification *success = [NSNotification notificationWithName:#"AudioDone" object:[NSNumber numberWithBool: YES]];
[[NSNotificationCenter defaultCenter] postNotification:success];
queueSize = 0;
//} else {
//send total track info
//get total queue size by the first success and add 1 back
if (queueSize ==0) {
queueSize = [[queue operations] count] +1.0;
}
float progress = (float)(queueSize-[[queue operations] count])/queueSize;
NSNumber * totProgress = [NSNumber numberWithFloat:progress];
NSLog(#"Total Progress: %#", totProgress);
current_downloading_file_path = #"";
//Commented by rakesh biradar - becoz #"TotalProgress" notification method does not do anything(memory).
//NSNotification * totalProgressNotification = [NSNotification notificationWithName:#"TotalProgress"
// object:totProgress];
//[[NSNotificationCenter defaultCenter] postNotification:totalProgressNotification];
//}
NSLog(#"QueueCount: %d", [[queue operations] count]); //[[self sharedQueue] operationCount]);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//deletes the partial downloaded file from document folder
if(([current_downloading_file_path length] > 0) && [[NSFileManager defaultManager] fileExistsAtPath:current_downloading_file_path])
[[NSFileManager defaultManager] removeItemAtPath:current_downloading_file_path error:nil];
current_downloading_file_path = #"";
NSLog(#"Error: %#", error);
}];
//Send progress notification
[operation setDownloadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
//NSLog(#"Sent %lld of %lld bytes, %#", totalBytesWritten, totalBytesExpectedToWrite, path);
float percentDone = ((float)((int)totalBytesWritten) / (float)((int)totalBytesExpectedToWrite));
//NSLog(#"Percent: %f", percentDone);
NSDictionary *userInfo = [NSDictionary dictionaryWithObjects: [NSArray arrayWithObjects:filename, [NSNumber numberWithFloat: percentDone],[NSNumber numberWithLongLong:totalBytesWritten],[NSNumber numberWithLongLong:totalBytesExpectedToWrite],[NSNumber numberWithUnsignedInteger:bytesWritten],nil]
forKeys:[NSArray arrayWithObjects:#"message", #"percent",#"totalBytesWritten",#"totalBytesExpectedToWrite",#"bytesWritten", nil]];
NSNotification * progress = [NSNotification notificationWithName:#"DownloadingAudio" object:nil userInfo:userInfo];
[[NSNotificationCenter defaultCenter] postNotification:progress];
}];
[queue addOperation:operation];
//[self enqueueHTTPRequestOperation:operation];
//NSLog(#"Operation Queue: %#", [self sharedQueue]);
}
Method when app goes in background
- (void)applicationWillResignActive:(UIApplication *)application {
// 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.
if (operation)
{
NSLog(#"%#",operation);
//[self saveCustomObject:operation];
[operation pause];
}
}
Method called when app becomes active
- (void)applicationDidBecomeActive:(UIApplication *)application {
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// Handle the user leaving the app while the Facebook login dialog is being shown
// For example: when the user presses the iOS "home" button while the login dialog is active
if (operation)
{
//operation = [self loadCustomObjectWithKey:#"myEncodedObjectKey"];
NSLog(#"%#",operation);
[operation resume];
}
[FBAppCall handleDidBecomeActive];
}