Bulk Video Upload with AFNetworking - ios

I am attempting to get AFNetworking (1.x) to batch upload some long videos to my server, however when I use the standard functions the application's memory spikes to 400MB and then quits.
NSMutableArray *requests = [[NSMutableArray alloc] init];
NSMutableURLRequest *request = [[MyServerClient sharedClient] multipartFormRequestWithMethod:#"POST" path:URL parameters:#{ #"json": jsonString } constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:mediaData name:idString fileName:fileName mimeType:mimeType];
}];
[request setTimeoutInterval:10800];
[requests addObject:operation];
[[MYServerClient sharedClient] enqueueBatchOfHTTPRequestOperationsWithRequests:requests progressBlock:^(NSUInteger numberOfCompletedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"Number - %d %d", numberOfCompletedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(#"Completion block");
}];
The MyServerClient is a pretty standard subclass of AFHTTPClient:
- (id)initWithBaseURL:(NSURL *)url {
self = [super initWithBaseURL:url];
[self.operationQueue setMaxConcurrentOperationCount:1];
[self registerHTTPOperationClass:[AFJSONRequestOperation class]];
return self;
}
I'm guessing the memory crash might have something to do with the application failing to throttle the amount of concurrent operations, but in light of setting setMaxConcurrentOperationCount, I'm not sure. Does anyone have any ideas why this would be happening?
EDIT: My guess is that the crash is due to the app attempting to load the media into memory prior to attaching it to the multipart request. Is there some way to stream the upload from disk within this POST usage scenario?

For anyone struggling with the same issue, the following appears to fix my crash:
NSMutableURLRequest *request = [[MyServerClient sharedClient] multipartFormRequestWithMethod:#"POST" path:URL parameters:#{ #"json": jsonString } constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileURL:mediaURL name:idString error:nil];
}];

Related

Timeout when issuing too many AFNetworking requests

I have this code to download 40 json
NSMutableArray *mutableOperations = [NSMutableArray array];
for (NSDictionary *dict in general_URL) {
NSURL *url = [dict objectForKey:#"url"];
NSString *key = [dict objectForKey:#"key"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operation.responseSerializer = [AFHTTPResponseSerializer serializer];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
[self.all_data setObject:[self parseJSONfile:responseObject] forKey:key];
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperations progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"progress:%f", (float)numberOfFinishedOperations / totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog (#"all done");
}];
[manager.operationQueue addOperations:operations waitUntilFinished:NO];
As you can see I use a manager to have a queue of request. The problem is that suddenly, it go in timeout with -1001 code.
It happens only in EDGE mode, in wifi and 3g it don't happen.
What's the problem?
If you specify the maxConcurrentOperationCount of the operation queue, that will control how many concurrent operations are attempted, thus mitigating any timeouts resulting from the fact that iOS limits how many simultaneous network connections are permitted:
manager.operationQueue.maxConcurrentOperationCount = 4;
[manager.operationQueue addOperations:operations waitUntilFinished:NO];
In the absence of this, when you submit your 40 operations, all of them are likely to attempt to start NSURLConnection objects, even though only 4 or 5 can really run at at a time. On slow connections, this can result in some of your latter requests timing out.
If you specify the maxConcurrentOperationCount, it won't attempt to start the latter connections until the prior connections have completed. You'll still enjoy the performance benefit of concurrent requests, but you won't be making a bunch of requests that will timeout because of the throttling of concurrent NSURLConnection requests that iOS enforces.

Sending multiple Images to Web server

I am working in an app, where i need to send 3 images to a Web Server. I don't know the perfect method that works fast and efficient.
I have 3 UIImageView that capture image data from camera or photo album. Below,I am using AFNetworking to send 1 image to Web Server.
NSString *imgPath = [[NSBundle mainBundle]pathForResource:#"Default" ofType:#"png"];
NSData *imgData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:imgPath]);
NSData *imagVIewData = UIImageJPEGRepresentation(imageView1.image,90);
if (imagVIewData) {
AFHTTPClient *client = [AFHTTPClient clientWithBaseURL:[NSURL URLWithString:#"http://myurl.com/xxx.php]];
NSMutableURLRequest *myRequest = [client multipartFormRequestWithMethod:#"POST" path:Nil parameters:Nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
[formData appendPartWithFileData:imagVIewData name:#"file_upload" fileName:#"123.jpg" mimeType:#"images/jpeg"];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc]initWithRequest:myRequest];
[operation setUploadProgressBlock:^(NSUInteger bytesWritten, long long totalBytesWritten, long long totalBytesExpectedToWrite) {
NSLog(#"Sent %lld of %lld bytes",totalBytesWritten,totalBytesExpectedToWrite);
}];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"upload complete");
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"%#",operation.responseString);
}];
NSOperationQueue *queue = [[NSOperationQueue alloc]init];
[queue addOperation:operation];
}
}
i need someone advice to send 3 different images from 3 UIImageViews. Is it possible with this program or do i need to work through different methods?
any idea?
The majority of the code you have can actually be kept. Why not try putting all the JPEG representations of the images into an array
NSArray *myArrayOfImages = #[Image1,Image2,Image3]
NSArray *myArrayOfNames = #[strings..]
NSArray *myArrayOfFileNames = #[strings..]
Then within the constructing body with block parameter put something like this..
for(int i=0; i < myArrayOfImages.length; i++){
NSData *temp = [myArrayOfImages objectAtIndex:i];
NSString *tempFile = [myArrayOfNames objectAtIndex:i]
NSString *tempFile = [myArrayOfFileNames objectAtIndex:i]
[formData appendPartWithFileData:temp name:tempName fileName:tempFile mimeType:#"images/jpeg"];
}
you could also use a dictionary or whatever data structure you want, point is you just loop over and append within the constructing block.

AFNetworking batch requests - canceling when first one fails

I'm using AFNetworking code for batching requests. I have really copy & paste from example code - it looks like that:
NSMutableArray *mutableOperations = [NSMutableArray array];
for (NSURL *fileURL in filesToUpload) {
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:#"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData
[formData appendPartWithFileURL:fileURL name:#"images[]" error:nil];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[mutableOperations addObject:operation];
}
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:mutableOperation progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations) {
NSLog(#"%lu of %lu complete", numberOfFinishedOperations, totalNumberOfOperations);
} completionBlock:^(NSArray *operations) {
NSLog(#"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
Now what I want to achieve is to cancel all other operation in that queue if the first one fails.
I found a solution for 1.0.3 with AFHttpClient but nothing for 2.0.
Any tips ?
Rather than adding the operations to the [NSOperationQueue mainQueue], create your own operation queue. So, in your #interface define a queue:
#property (nonatomic, strong) NSOperationQueue *networkQueue;
Then, instantiate a queue:
self.networkQueue = [[NSOperationQueue alloc] init];
self.networkQueue.name = #"com.domain.app.networkqueue";
// if you want it to be a serial queue, set maxConcurrentOperationCount to 1
//
// self.networkQueue.maxConcurrentOperationCount = 1;
//
// if you want it to be a concurrent queue, set it to some reasonable value
//
// self.networkQueue.maxConcurrentOperationCount = 4;
Then, add your network operations to this queue (bypassing batchOfRequestOperations):
NSOperation *completionOperation = [NSBlockOperation blockOperationWithBlock:^{
NSLog(#"All operations done");
}];
// NSOperation *previousOperation = nil; // if you uncomment dependency code below, uncomment this, too
for (NSURL *fileURL in filesToUpload) {
NSURLRequest *request = [[AFHTTPRequestSerializer serializer] multipartFormRequestWithMethod:#"POST" URLString:#"http://example.com/upload" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData
[formData appendPartWithFileURL:fileURL name:#"images[]" error:nil];
}];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
// if you want the operations to run serially, you can also
// make each dependent upon the prior one, as well
//
// if (previousOperation)
// [operation addDependency:previousOperation];
//
// previousOperation = operation;
[completionOperation addDependency:operation];
[self.networkQueue addOperation:operation];
}
[self.networkQueue addOperation:completionOperation];
And, finally, if you want to cancel the operations, you can do:
[self.networkQueue cancelAllOperations];

Uploading images to server

I am trying to upload an image to a server (that is already built) and I am getting errors like Request has timed out. Other methods of sending text and fetch data from the server are working properly. However, sending an image I found it hard to do it.
I am using the following code at the moment:
-(void)uploadImage:(NSData*)image callbackBlock: (void (^)(BOOL success)) callbackBlock
{
NSString *path = [NSString stringWithFormat:#"upload"];
NSMutableDictionary *params = [NSMutableDictionary dictionaryWithObjectsAndKeys:image, #"image", nil];
[params addEntriesFromDictionary:self.sessionManager.authParameters];
NSMutableURLRequest *request = [self multipartFormRequestWithMethod:#"POST" path:path parameters:params constructingBodyWithBlock: ^(id <AFMultipartFormData>formData){
[formData appendPartWithFormData:image name:#"Image"];
}];
AFHTTPRequestOperation *operation = [self HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSLog(#"!!!Response object: %#",responseObject);
callbackBlock(YES);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Failure: %#",error.description);
callbackBlock(NO);
}];
[self enqueueHTTPRequestOperation:operation];
}
Do you have any idea what the problem is? Can you give me some suggestions or possible errors on the above code.
Thank you very much.
You can send your image as a base64 encoded text... This should work.
You can use this category to create base64 encoded image:
https://github.com/l4u/NSData-Base64

How to batch request with AFNetworking 2?

So I'm rewriting an app for iOS 7 with AFNetworking 2.0 and I'm running into the issue of sending a batch of requests at once and tracking their progress. In the old AFNetworking there was the enqueueBatchOfHTTPRequestOperations:progressBlock:completionBlock: method on AFHTTPClient, this is clearly refactored out and I'm a bit confused on how to enqueue multiple requests.
I have created a subclass of AFHTTPSessionManager and I'm using the POST:... and GET:... methods to communicate with the server. But I can't find anything in the code and/or docs to enqueue multiple requests at once like with the old AFHTTPClient.
The only thing I can find is the undocumented batchOfRequestOperations:progressBlock:completionBlock: method on AFURLConnectionOperation, but that looks like the iOS 6 way of doing this.
Clearly I'm missing something in the new NSURLSession concept that I should use to batch requests or looking over a new AFNetworking feature. Hope someone can help me on the right track here!
tl;dr: How can I send a batch of requests with my AFHTTPSessionManager subclass?
Thanks Sendoa for the link to the GitHub issue where Mattt explains why this functionality is not working anymore. There is a clear reason why this isn't possible with the new NSURLSession structure; Tasks just aren't operations, so the old way of using dependencies or batches of operations won't work.
I've created this solution using a dispatch_group that makes it possible to batch requests using NSURLSession, here is the (pseudo-)code:
// Create a dispatch group
dispatch_group_t group = dispatch_group_create();
for (int i = 0; i < 10; i++) {
// Enter the group for each request we create
dispatch_group_enter(group);
// Fire the request
[self GET:#"endpoint.json"
parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
// Leave the group as soon as the request succeeded
dispatch_group_leave(group);
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
// Leave the group as soon as the request failed
dispatch_group_leave(group);
}];
}
// Here we wait for all the requests to finish
dispatch_group_notify(group, dispatch_get_main_queue(), ^{
// Do whatever you need to do when all requests are finished
});
I want to look write something that makes this easier to do and discuss with Matt if this is something (when implemented nicely) that could be merged into AFNetworking. In my opinion it would be great to do something like this with the library itself. But I have to check when I have some spare time for that.
Just updating the thread... I had the same problem and after some researches I found some good solutions, but I decided to stick with this one:
I am using the project called Bolts. So, for the same sample above posted by #Mac_Cain13, it would be:
[[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
BFTask *task = [BFTask taskWithResult:nil];
for (int i = 0; i < 10; i++) {
task = [task continueWithBlock:^id(BFTask *task) {
return [self executeEndPointAsync];
}];
}
return task;
}] continueWithBlock:^id(BFTask *task) {
// Everything was executed.
return nil;
}];;
- (BFTask *) executeEndPointAsync {
BFTaskCompletionSource *task = [BFTaskCompletionSource taskCompletionSource];
[self GET:#"endpoint.json" parameters:nil
success:^(NSURLSessionDataTask *task, id responseObject) {
[task setResult:responseObject];
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
[task setError:error];
}];
}];
return task.task;
}
Basically, it's stacking all of the tasks, waiting and unwrapping until there is no more tasks, and after everything is completed the last completion block is executed.
Another project that does the same thing is RXPromise, but for me the code in Bolts was more clear.
For request which can be post or get, you can use AFNetworking 2.0 for batch operation as firstly you need to create operation like this:
//Request 1
NSString *strURL = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters = your parameters here
NSMutableURLRequest *request = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL parameters:dictParameters error: nil];
AFHTTPRequestOperation *operationOne = [[AFHTTPRequestOperation alloc] initWithRequest:request];
operationOne = [AFHTTPResponseSerializer serializer];
[operationOne setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request 2
NSString *strURL1 = [NSString stringWithFormat:#"your url here"];
NSLog(#"scheduleurl : %#",strURL);
NSDictionary *dictParameters1 = your parameters here
NSMutableURLRequest *request1 = [[AFHTTPRequestSerializer serializer] requestWithMethod:#"POST" URLString:strURL1 parameters:dictParameters1 error: nil];
AFHTTPRequestOperation *operationTwo = [[AFHTTPRequestOperation alloc] initWithRequest:request1];
operationTwo = [AFHTTPResponseSerializer serializer];
[operationTwo setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
//do something on completion
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"%#",[error description]);
}];
//Request more here if any
Now perform batch operation like this :
//Batch operation
//Add all operation here
NSArray *operations = [AFURLConnectionOperation batchOfRequestOperations:#[operationOne,operationTwo] progressBlock:^(NSUInteger numberOfFinishedOperations, NSUInteger totalNumberOfOperations)
{
NSLog(#"%i of %i complete",numberOfFinishedOperations,totalNumberOfOperations);
//set progress here
yourProgressView.progress = (float)numberOfFinishedOperations/(float)totalNumberOfOperations;
} completionBlock:^(NSArray *operations)
{
NSLog(#"All operations in batch complete");
}];
[[NSOperationQueue mainQueue] addOperations:operations waitUntilFinished:NO];
On AFNetworking 2.0, AFHTTPClient has been split on AFHTTPRequestOperationManager and AFHTTPSessionManager, so probably you could start with the first, which has operationQueue property.
Currently, NSURLSession tasks are not suitable for the same kind of patterns request operations use. See the answer from Mattt Thompson here regarding this issue.
Direct answer: if you need dependencies or batches, you'll still need to use request operations.

Resources