AFNetworking - POST file progress random stuck - iphone

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.

Related

AFNetworking gives incorrect progress count

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.

I am getting bad request from the server when making DELETE request

I am using AFNetworking shared client in order to make request to the REST server.
My code for delete is:
NSMutableDictionary* params = [[NSMutableDictionary alloc] init];
[[ApiClient sharedClient] deletePath:[NSString stringWithFormat:#"users/%#/venues/%#/", appDelegate.currentUser.userId, venue.venueId] parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"venue deleted from saved");
} failure:^(AFHTTPRequestOperation *operation, NSError *error){
NSLog(error.description);
}];
When I am configuring the client, I am adding:
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
[self setDefaultHeader:#"Accept" value:#"application/json"];
[self setDefaultHeader:#"Content-Type" value:#"application/json"];
[self setParameterEncoding:AFJSONParameterEncoding];
POST request is working fine, but there is a problem with the DELETE request. Where am I wrong?
You will need to look at the error and check what is going wrong. If there error is not helpful, then you will need to use a proxy (perhaps Charles) and see exactly what is happening with the request and why the server does not like it.

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!