AFNetworking gives incorrect progress count - iphone

I'm using AFHTTPRequestOperation to download a file. But on pausing and resuming the operation, the api gives incorrect progress count. I am downloading data using following code
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:str]];
_downloadOperation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
_downloadOperation.outputStream = [NSOutputStream outputStreamToFileAtPath:strFilePath append:YES];
[_downloadOperation setDownloadProgressBlock:^(NSUInteger bytesRead, long long totalBytesRead, long long totalBytesExpectedToRead) {
NSLog(#"Progress %lld",totalBytesRead * 100 / totalBytesExpectedToRead);
}];
[_downloadOperation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"downloaded %#",operation.request.URL);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"failed");
}];
[_downloadOperation start];
I pause the operation when user goes in background,
- (void)applicationDidEnterBackground:(UIApplication *)application
{
if ([objAPI.downloadOperation isExecuting])
[objAPI.downloadOperation pause];
}
and resume operation when user comes in foreground
- (void)applicationWillEnterForeground:(UIApplication *)application
{
if ([objAPI.downloadOperation isPaused])
[objAPI.downloadOperation resume];
}
Example: If the operation is paused at progress 20% , on resuming it starts from 20% but ends at 120% . In other words the progress count goes incorrectly after pausing the operation.
Kindly help me to solve the problem

There is a related issue which would cause the percentages when using AFURLSessionManager to be incorrect.
The pull-request at https://github.com/AFNetworking/AFNetworking/pull/1786 may fix this issue.

Related

Perform AFHTTPClient request, in background, with NSOperationQueue chronologically

I have an HTTPClient request as follows :
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:urlStringMain]];
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:url];
NSDictionary *params = [NSDictionary dictionaryWithObjectsAndKeys:
//parameters
nil];
[self beginBackgroundUpdateTask];
[httpClient postPath:postPath parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
//id results = [NSJSONSerialization JSONObjectWithData:responseObject options:NSJSONWritingPrettyPrinted error:nil];
//completion code
[self endBackgroundUpdateTask];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure code
}];
[httpClient release];
The background task is executed in :
- (void) beginBackgroundUpdateTask{
[operationQueue addOperationWithBlock:^{
NSLog(#"started upload process as a background job");
self.backgroundUpdateTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{
[self endBackgroundUpdateTask];
}];
}];
}
And ended in :
- (void) endBackgroundUpdateTask{
NSLog(#"complete upload process as a background job");
[[UIApplication sharedApplication] endBackgroundTask: self.backgroundUpdateTask];
self.backgroundUpdateTask = UIBackgroundTaskInvalid;
}
where self.backgroundUpdateTask is a UIBackgroundTaskIdentifier object, and operationQueue is an object of NSOperationQueue (public member), initialized in viewDidLoad:
operationQueue = [[NSOperationQueue alloc] init];
[operationQueue setMaxConcurrentOperationCount:NSOperationQueueDefaultMaxConcurrentOperationCount];
Now what I want to do is, perform these requests chronologically, in the background, such that pushing/popping from the viewController does not affect the request. It should also not be affected if the application goes into background. At times I post text, and at other times I post an image. Now, images take longer to upload, than text so if subsequent requests of text and images are made, texts are posted first, and images later. This breaks the chronology of the tasks, hence I wanted to use the NSOperationQueue. But being new to operation queues, I cannot seem to make it work. The chronology is still not being respected. How do I perform the task in the way I want to.
PS. Also, as you can see in the code, i have added [self endBackgroundUpdateTask] in both, the completion block of the httpClient request, and the beginBackgroundUpdateTask method. Now i understand this is not good. Where exactly should the endBackgroundUpdateTask method be called ?
Thank you.
The HTTPClient can handle the background task for you if you ask it to, simply set the appropriate flag (you need to create the operation and call setShouldExecuteAsBackgroundTaskWithExpirationHandler:).
Rather than change how the uploads operate, keep it simple. Send a date with the upload and use that to maintain your order information on the server.
If you really must execute your operations serially then you can get the operationQueue from the client and set it to only execute a single operation at a time.
In either case you shouldn't need to create your own operation queue.

AFNetworking - POST file progress random stuck

I'm using AFNetworking and AFJSONRequestOperation to post an image along with some parameters to my REST server with this code.
NSData* uploadFile = nil;
if ([params objectForKey:#"file"]) {
uploadFile = (NSData*)[params objectForKey:#"file"];
[params removeObjectForKey:#"file"];
}
NSMutableURLRequest *apiRequest =
[self multipartFormRequestWithMethod:#"POST"
path:kAPIPath
parameters:params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
if (uploadFile) {
[formData appendPartWithFileData:uploadFile
name:#"file"
fileName:#"photo.jpg"
mimeType:#"image/jpeg"];
[formData throttleBandwidthWithPacketSize:5000 delay:kAFUploadStream3GSuggestedDelay];
}
}];
AFHTTPRequestOperation *operation2 = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation2 setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation2, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation2, NSError *error) {
//failure :(
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation2 setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
if (self.delegate) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
[self.delegate didReceiveData:(totalBytesWritten / (float)totalBytesExpectedToWrite)];
}
}];
[[API sharedInstance] enqueueHTTPRequestOperation:operation2];
This works randomly. I mean some times everything works perfectly and some times the progression get stucked in the middle of the sending process and finish with a Timeout error.
I tried with tons of parameters and different combinations but I have always the same behavior.
PS : I use the last version of the Framework.
PS2 : All my tests are made on both simulator and device, and also over a Wifi connection
This works randomly. I mean some times everything works perfectly and some times the progression get stucked in the middle of the sending process and finish with a Timeout error.
This is a server-side issue, then. Depending on what kind of endpoint your uploading to, you may need to throttle the data rate of the upload.

Objective-C GCD wait for Block to finish (AFNetworking)

I've been trying to experiment with some code from a tutorial, however not having much success due to not getting my head around GCD.
I have an class named API.m and here is the code regarding GCD:
+ (API *) sharedInstance
{
static API *sharedInstance = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedInstance = [[self alloc] initWithBaseURL:[NSURL URLWithString:APIHost]];
});
return sharedInstance;
}
-(void)commandWithParams:(NSMutableDictionary*)params
onCompletion:(JSONResponseBlock)completionBlock
{
NSMutableURLRequest *apiRequest = [self multipartFormRequestWithMethod:#"POST"
path:APIPath
parameters:params
constructingBodyWithBlock: ^(id <AFMultipartFormData>formData) {
//TODO: attach file if needed
}];
AFJSONRequestOperation* operation = [[AFJSONRequestOperation alloc] initWithRequest: apiRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
//success!
completionBlock(responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
//failure :(
completionBlock([NSDictionary dictionaryWithObject:[error localizedDescription] forKey:#"error"]);
}];
[operation start];
}
I make a simple test by implementing a button and getting an NSArray to print it's content to the output window:
- (IBAction)test:(id)sender {
NSMutableDictionary* params =[NSMutableDictionary dictionaryWithObjectsAndKeys:
#"pending", #"command",
[[[API sharedInstance] user] objectForKey:#"UserID"] , #"userID",
nil];
[[API sharedInstance] commandWithParams:params
onCompletion:^(NSDictionary *json) {
//result returned
if ([json objectForKey:#"error"]==nil) {
// Simple example
[self.users addObject:#"1"];
} else {
//error
[UIAlertView title:#"Error" withMessage:[json objectForKey:#"error"]];
}
}];
NSLog(#"%#", self.users);
}
Now when I first click the button an empty NSArray is printed to the output window, but when I press it again it print's "1". It's clear that the program is reaching NSLog before the completion block has time to fully execute. Could someone please help me modify the code so that I have the option to have the NSLog execute after the completion block has finished?
Not sure as to what you are trying to accomplish, but if the goal is to just have NSLog execute after the completion block, you can move the NSLog statement after
[self.users addObject:#"1"];
If you have some code which you want to execute after adding it to the array, you can have
[self methodName]; in the completion block and it will get called there.
Completion block, is the code which is run after execution of the code which you wanted run. The code which you wanted run, will happen asynchronously and on another thread. After that code is run, the completion block code will get executed.

Converting from ASIHTTPRequest to AFNetworking

I am converting my app routines from ASIHTTP to AFNetworking due to the unfortunate discontinuation of work on that project ... and what I found out later to be the much better and smaller codebase of AFNetworking.
I am finding several issues. My code for ASIHTTPRequest is built as a method. This method takes a few parameters and posts the parameters to a url ... returning the resulting data. This data is always text, but in the interests of making a generic method, may sometimes be json, sometimes XML or sometimes HTML. Thus I built this method as a standalone generic URL downloader.
My issue is that when the routine is called I have to wait for a response. I know all the "synchronous is bad" arguments out there...and I don't do it a lot... but for some methods I want synchronous.
So, here is my question. My simplified ASIHTTP code is below, followed by the only way i could think of coding this in AFNetworking. The issue I have is that the AFNetworking sometimes does not for the response before returning from the method. The hint that #mattt gave of [operation waitUntilFinished] totally fails to hold the thread until the completion block is called... and my other method of [queue waitUntilAllOperationsAreFinished] does not necessarily always work either (and does NOT result in triggering the error portion of the [operation hasAcceptableStatusCode] clause). So, if anyone can help, WITHOUT The ever-present 'design it asynchronously', please do.
ASIHTTP version:
- (NSString *) queryChatSystem:(NSMutableDictionary *) theDict
{
NSString *response = [NSString stringWithString:#""];
NSString *theUrlString = [NSString stringWithFormat:#"%#%#",kDataDomain,kPathToChatScript];
ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:theUrlString]];
for (id key in theDict)
{
[request setPostValue:[theDict objectForKey:key] forKey:key];
}
[request setNumberOfTimesToRetryOnTimeout:3];
[request setAllowCompressedResponse:YES];
[request startSynchronous];
NSError *error = [request error];
if (! error)
{
response = [request responseString];
}
return response;
}
AFNetworking version
- (NSString *) af_queryChatSystem:(NSMutableDictionary *) theDict
{
NSMutableDictionary *theParams = [NSMutableDictionary dictionaryWithCapacity:1];
for (id key in theDict)
{
[theParams setObject:[theDict objectForKey:key] forKey:key];
}
AFHTTPClient *httpClient = [[AFHTTPClient alloc] initWithBaseURL:[NSURL URLWithString:kDataDomain]];
NSMutableURLRequest *theRequest = [httpClient requestWithMethod:#"POST" path:[NSString stringWithFormat:#"/%#",kPathToChatScript] parameters:theParams];
__block NSString *responseString = [NSString stringWithString:#""];
AFHTTPRequestOperation *operation = [[[AFHTTPRequestOperation alloc] initWithRequest:theRequest] autorelease];
operation.completionBlock = ^ {
if ([operation hasAcceptableStatusCode]) {
responseString = [operation responseString];
NSLog(#"hasAcceptableStatusCode: %#",responseString);
}
else
{
NSLog(#"[Error]: (%# %#) %#", [operation.request HTTPMethod], [[operation.request URL] relativePath], operation.error);
}
};
NSOperationQueue *queue = [[[NSOperationQueue alloc] init] autorelease];
[queue addOperation:operation];
[queue waitUntilAllOperationsAreFinished];
[httpClient release];
return responseString;
}
Thanks very much for any ideas.
- (void)af_queryChatSystem:(NSMutableDictionary *) theDict block:(void (^)(NSString *string))block {
...
}
Now within the completionBlock do:
block(operation.responseString);
block will act as the delegate for the operation. remove
-waitUntilAllOperationsAreFinished
and
return responseString
You call this like:
[YourInstance af_queryChatSystem:Dict block:^(NSString *string) {
// use string here
}];
Hope it helps. You can refer to the iOS example AFNetworking has
I strongly recommend to use this opportunity to convert to Apple's own NSURLConnection, rather than adopt yet another third party API. In this way you can be sure it won't be discontinued. I have found that the additional work required to get it to work is minimal - but it turns out to be much more robust and less error prone.
My solution is manually to run the current thread runloop until the callback have been processed.
Here is my code.
- (void)testRequest
{
MyHTTPClient* api = [MyHTTPClient sharedInstance]; // subclass of AFHTTPClient
NSDictionary* parameters = [NSDictionary dictionary]; // add query parameters to this dict.
__block int status = 0;
AFJSONRequestOperation* request = [api getPath:#"path/to/test"
parameters:parameters
success:^(AFHTTPRequestOperation *operation, id responseObject) {
// success code
status = 1;
NSLog(#"succeeded");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// failure
status = 2;
NSLog(#"failed");
}];
[api enqueueHTTPRequestOperation:request];
[api.operationQueue waitUntilAllOperationsAreFinished];
while (status == 0)
{
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate date]];
}
STAssertEquals(status, 1, #"success block was executed");
}

How can I check the progress of my Facebook iOS upload?

I'm using the Facebook iOS SDK and using the Graph API to upload videos to Facebook.
The uploading is working perfectly fine, but can I keep track of the progress of the upload so I can reflect the progress in a progress bar.
This is an old question but what you're trying to do is possible with latest Facebook iOS SDK v3.9. (27 Oct 2013)
Essentially, FBRequestConnection exposes a property urlRequest (NSMutableURLRequest) that you can use to send out the data any other third party networking frameworks or even the ones Apple provided.
https://developers.facebook.com/docs/reference/ios/current/class/FBRequestConnection#urlRequest
Here's an example how I get progress callbacks using AFNetworking 1.x.
Prepare Request Body
NSDictionary *parameters = #{ #"video.mov": videoData,
#"title": #"Upload Title",
#"description": #"Upload Description" };
Create FBRequest
FBRequest *request = [FBRequest requestWithGraphPath:#"me/videos"
parameters:parameters
HTTPMethod:#"POST"];
Generate FBRequestConnection (Cancel & Extract URLRequest)
FBRequestConnection *requestConnection = [request startWithCompletionHandler:^(FBRequestConnection *connection, id result, NSError *error) {
}];
[requestConnection cancel];
NSMutableURLRequest *urlRequest = requestConnection.urlRequest;
Use AFNetworking HTTPRequestOperation
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
// Do your success callback.
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// Do your failure callback.
}];
Set Progress Callback
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes", totalBytesWritten, totalBytesExpectedToWrite);
}];
Start the operation
[[APIClient sharedInstance] enqueueHTTPRequestOperation:operation];
// APIClient is a singleton class for AFHTTPClient subclass
I've finally found a way of doing this after looking around in NSURLConnection. It means adding the following code inside of the FBRequest.h and FBRequest.m files to create a new delegate.
At the bottom of the FBRequest.m file there are all of the methods for NSURLConnectionDelegate. Add this code here:
- (void)connection:connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
float percentComplete = ((float)totalBytesWritten/(float)totalBytesExpectedToWrite);
if ([_delegate respondsToSelector:#selector(request:uploadPercentComplete:)])
{
[_delegate request:self uploadPercentComplete:percentComplete];
}
}
Now put this in the FBRequest.h class to create a new FBRequest delegate:
/**
* Called a data packet is sent
*
* The result object is a float of the percent of data sent
*/
- (void)request:(FBRequest *)request uploadPercentComplete:(float)per;
This goes at the bottom of the FBRequest.h file after:
#protocol FBRequestDelegate <NSObject>
#optional
Now all you have to do is call this new delegate anywhere in your code like you would any other FBRequest delegate and it will give you a float from 0.0 to 1.0 (0% to 100%).
Strange that the Facebook API doesn't have this (along with upload cancel which I found out how to do here How to cancel a video upload in progress using the Facebook iOS SDK?) as it's not that tricky.
Enjoy!