Background thread completion blocks possibly firing before completion - ios

I am writing a photo upload queue manager.
Here is my process. The user can take a picture or choose one from the camera roll.
The image, along with some other string data, is placed in queue.
The queue is simply an array that contains the image path string and the other string data. The 'queued' image is saved to the documents directory of the application for later retrieval.
Every second the queue is checked to see if any images that need to be uploaded, then it does so, one-by-one.
When it uploads, the server responds with a string of success, if success then the picture is removed from list queue, the temp image is deleted and the program moves on to the next queued image.
If I call the uploadImage: method independently and pass it the image and string data, the image uploads fine. But when called from a background thread, the image is never truly uploaded even though the server responds success instantly. I feel like since the server is responding instantly with success, that the image is only partially being uploaded.
processImage: is the method that calls uploadImage: from the background.
I could really use an expert's eyes to see what might be causing the app to not fully wait on the photo to upload before continuing.
#interface ImageUploader ()
#property (nonatomic, strong) NSMutableArray * arrPhotoQueue;
#property (nonatomic, strong) NSTimer* timerUpload;
#end
#implementation ImageUploader {
NSUserDefaults * defaults; //Just were we are currently storing a list of images.
NSArray *paths;
NSString *documentsDirectory;
}
#pragma mark Public Methods
- (void) startPhotoUploadCheck {
if (!_timerUpload) {
if(![_timerUpload isValid]){
_timerUpload = [NSTimer scheduledTimerWithTimeInterval:_timerCheckInterval target:self selector:#selector(checkForPhotosToUpload) userInfo:nil repeats:YES];
}
}
}
- (void) addImageToQueue:(UIImage*)image withCallKey:(NSString*) callKey {
NSData *imageData = UIImagePNGRepresentation(image);
NSString *fileName = [NSString stringWithFormat:#"%#.png",[self randomStringWithLength:8]];
NSString *imagePath =[documentsDirectory stringByAppendingPathComponent:fileName];
NSMutableDictionary * data = [[NSMutableDictionary alloc] init];
[data setValue:fileName forKey:#"imagePath"];
[data setValue:callKey forKey:#"callKey"];
[_arrPhotoQueue addObject:data];
if (![imageData writeToFile:imagePath atomically:NO]) {
NSLog(#"Failed to cache image data to disk");
} else {
NSLog(#"Image %#", fileName);
[defaults setObject:_arrPhotoQueue forKey:#"PendingPhotos"];
[defaults synchronize];
}
[self totalImagesInQueue];
}
- (void) checkForPhotosToUpload {
//this does some logic to just see if there is a connection to the internet and that there are photos in queue then it calls
[self proceedWithUpload];
}
#pragma mark Upload Image
- (int) removePhotoFromMemory:(NSString *) callKeyOfUploadedPhoto
{
int nIndexOfUploadedPhoto = -1;
for (int i = 0; i < [_arrPhotoQueue count]; i++) {
NSString * callKey = [_arrPhotoQueue[i] objectForKey:#"callKey"];
if ([callKey isEqualToString:callKeyOfUploadedPhoto]) {
//remove uploaded photo
NSString *imagePath = [documentsDirectory stringByAppendingPathComponent:[_arrPhotoQueue[i] objectForKey:#"imagePath"]];
NSLog(#"Remove image from queue: %#", [_arrPhotoQueue[i] objectForKey:#"imagePath"]);
[[NSFileManager defaultManager] removeItemAtPath:imagePath error:nil];
nIndexOfUploadedPhoto = i;
break;
}
}
return nIndexOfUploadedPhoto;
}
- (void) proceedWithUpload {
NSMutableArray* removeArray = [[NSMutableArray alloc] init];
dispatch_group_t taskGroup = dispatch_group_create();
for (int i = 0; i < [_arrPhotoQueue count]; i++) {
dispatch_group_enter(taskGroup);
UIImage * image = [UIImage imageWithContentsOfFile:[_arrPhotoQueue[i] objectForKey:#"imagePath"]];
NSString * callKey = [_arrPhotoQueue[i] objectForKey:#"callKey"];
[self processImage:image :callKey completed:^(bool bSuccess){
if (bSuccess) {
//photo is uploaded successfully
int nPhotoIdx = [self removePhotoFromMemory:callKey];
if (nPhotoIdx > -1) {
[removeArray addObject:[NSString stringWithFormat:#"%d", nPhotoIdx]];
}
dispatch_group_leave(taskGroup);
} else {
dispatch_group_leave(taskGroup);
}
}];
}
// Here we wait for all the requests to finish
dispatch_group_notify(taskGroup, dispatch_get_main_queue(), ^{
// run code when all files are downloaded
[self removePhotoFromQueue:removeArray];
});
}
- (void) removePhotoFromQueue:(NSMutableArray*)index {
NSArray *sortedIndex = [index sortedArrayUsingComparator:^(id obj1, id obj2){
if ([obj1 isKindOfClass:[NSString class]] && [obj2 isKindOfClass:[NSString class]]) {
int nFirstIndex = (int)[obj1 integerValue];
int nSecondIndex = (int)[obj2 integerValue];
if (nFirstIndex > nSecondIndex) {
return (NSComparisonResult)NSOrderedAscending;
} else if (nFirstIndex < nSecondIndex) {
return (NSComparisonResult)NSOrderedDescending;
}
}
return (NSComparisonResult)NSOrderedSame;
}];
for (int i =0; i < [sortedIndex count]; i++) {
int nIdx = (int)[sortedIndex[i] integerValue];
[_arrPhotoQueue removeObjectAtIndex:nIdx];
}
[defaults removeObjectForKey:#"PendingPhotos"];
[defaults setObject:_arrPhotoQueue forKey:#"PendingPhotos"];
[defaults synchronize];
}
#pragma mark Image Processor
- (void) processImage:(UIImage*)image :(NSString*)callKey completed:(void(^)(bool bSuccess)) block{
__block NSMutableDictionary * message;
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
message = [self uploadImage:image CallKey:callKey];
[self uploadImage:image CallKey:callKey];
dispatch_async(dispatch_get_main_queue(), ^{
if ([[message objectForKey:#"status"] isEqualToString:#"success"]) {
block(true);
} else {
NSLog(#"Somethign is wrong");
block(false);
}
});
});
}
- (NSMutableDictionary*) uploadImage:(UIImage *)theimage CallKey:(NSString*)callKey {
NSData * imageData = UIImageJPEGRepresentation([self fixrotation:theimage], 90);
NSString * jsonString= [JSONCommunicator buildJSONForImageUpload:[self prepareDictionaryDataForUploadWithKey:callKey]];
NSURL * uploadURL = [NSURL URLWithString:MYURL];
// create the URL
NSMutableURLRequest *postRequest = [NSMutableURLRequest requestWithURL:uploadURL cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20.0];
[postRequest setHTTPMethod:#"POST"];
// just some random text that will never occur in the body
NSString *boundary = #"---------------------------14737809831466499882746641449";
NSString *headerBoundary = [NSString stringWithFormat:#"multipart/form-data; boundary=%#", boundary];
[postRequest addValue:headerBoundary forHTTPHeaderField:#"Content-Type"];
// create data
NSMutableData *postBody = [NSMutableData data];
// JSON part
[postBody appendData:[[NSString stringWithFormat:#"--%#\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[#"Content-Disposition: form-data; name=\"params\"\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[jsonString dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[#"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
// media part
[postBody appendData:[[NSString stringWithFormat:#"--%#\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:#"Content-Disposition: form-data; name=\"UploadedFile\"; filename=\"image.jpg\"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[#"Content-Type: image/jpeg\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:imageData];
[postBody appendData:[[NSString stringWithFormat:#"\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
// final boundary
[postBody appendData:[[NSString stringWithFormat:#"--%#--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
NSString *s = [[NSString alloc] initWithData:postBody encoding:NSASCIIStringEncoding];
//NSLog(#"Image String: %#", s);
// add body to post
[postRequest setHTTPBody:postBody];
// pointers to some necessary objects
NSURLResponse* response;
NSError* error;
// synchronous filling of data from HTTP POST response
NSData *responseData = [NSURLConnection sendSynchronousRequest:postRequest returningResponse:&response error:&error];
if (error) {
NSLog(#"Error: %#", [error localizedDescription]);
}
// convert data into string
NSString *responseString = [[NSString alloc] initWithBytes:[responseData bytes] length:[responseData length] encoding:NSUTF8StringEncoding];
NSLog(#"TOPS Response String: %#", responseString);
NSData *data = [responseString dataUsingEncoding:NSUTF8StringEncoding];
NSMutableDictionary * dictionary = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
return dictionary;
}

Related

Return NSMutableArray from completionHandler (Objective C)

I did post request to a web service and get response. I convert the response to NSMutableArray. My response in NSURLSessionDataTask and now I want to return NSMutableArray for using outside of NSURLSessionDataTask. Here is my code:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"url"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSString *postString = #"params";
NSString *postLength = [NSString stringWithFormat:#"%lu", ( unsigned long )[postString length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length" ];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error )
{
dispatch_async( dispatch_get_main_queue(),
^{
NSDictionary *dicData = [NSJSONSerialization
JSONObjectWithData:data
options:NSJSONReadingAllowFragments
error:nil];
NSDictionary *values = [dicData valueForKeyPath:#"smth"];
NSArray * dataArr = [dicData objectForKey:#"smth"];
NSArray * closeArr = [values objectForKey:#"0"];
NSUInteger dataCount = [dataArr count] ;
NSUInteger closeCount = [closeArr count] ;
NSMutableArray * newData = [NSMutableArray new] ; //<-- THIS ARRAY
for(int i = 0 ; i<dataCount && i<closeCount ; i++)
{
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[newData addObject:temp];
}
NSLog(#"%#", newData);
} );
}];
[task resume];
I need return NSMutableArray * newData = [NSMutableArray new];
Long story short, I get json data from web service, then transform it to appropriate json format for displaying it in the chart(I use shinobicontrols). Now, I display chart with the help of local json. Here is the code:
_timeSeries = [NSMutableArray new];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"AppleStockPrices" ofType:#"json"];
NSData* json = [NSData dataWithContentsOfFile:filePath];
NSArray* data = [NSJSONSerialization JSONObjectWithData:json
options:NSJSONReadingAllowFragments
error:nil];
for (NSDictionary* jsonPoint in data) {
SChartDataPoint* datapoint = [self dataPointForDate:jsonPoint[#"smth1"]
andValue:jsonPoint[#"smth2"]];
[_timeSeries addObject:datapoint];
}
When I am trying to implement this code in NSURLSessionDataTask, the chart doesn't appear. So I need return NSMutableArray(where my data in appropriate json format) outside.
How can I do this? Any ideas?
Thank you!
You cannot add a return statement in the completion handler since it may not be called if the session return an error. As a matter of fact, Xcode will give you an "Incompatible pointer type" error if you try to do that.
The best way I found to go around it is to set up your newData array as a property and make it available to the other methods in the class. If a specific method will need to handle this array when the url session task is over, you can call that method from the completion handler or use a notification.
Alternatively, if for some reason you do not want to user a class property, you can use the NSNotificationCenter, and pass the newData to the listener in the notification object.
EDIT: code example using a property
If you need the newData outside the completion block, an easy way is declaring the array as a property. This is not the only approach and probably not the best. But it doesn't add much complexity to the code.
You can declare the newData array in your .m class file:
#interface "whatever class you are using"
#property (nonatomic, strong) NSMutableArray *newData;
#end
Your can initialize the array in your viewDidLoad method:
- (void)viewDidLoad {
_newdata = [[NSMutableArray alloc] init];
}
In Your completion block, you would remove the initialization and add data to the array.
//NSMutableArray * newData = [NSMutableArray new] ; // REMOVE THE INTIALIZATION
for(int i = 0 ; i<dataCount && i<closeCount ; i++) {
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[_newData addObject:temp];
}
Again, this is not the best approach but it is relatively simple. One problem with doing this, is that since you have a strong reference to the array, if you need to perform another URL call, and load new data into the array, you will need to empty it. Otherwise, the new data will be appended to the old one. You can do it by calling [_newData removeAllObjects]; before the URL session is called again.
EDIT 2: changed code based on the user comment:
- (void)loadChartData {
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"url"]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
NSString *postString = #"params";
NSString *postLength = [NSString stringWithFormat:#"%lu", ( unsigned long )[postString length]];
[request setValue:postLength forHTTPHeaderField:#"Content-Length" ];
[request setHTTPBody:[postString dataUsingEncoding:NSUTF8StringEncoding]];
NSURLSessionDataTask *task = [[self getURLSession] dataTaskWithRequest:request completionHandler:^( NSData *data, NSURLResponse *response, NSError *error ) {
dispatch_async( dispatch_get_main_queue(), ^{
NSDictionary *dicData = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
NSDictionary *values = [dicData valueForKeyPath:#"smth"];
NSArray * dataArr = [dicData objectForKey:#"smth"];
NSArray * closeArr = [values objectForKey:#"smth0"];
NSUInteger dataCount = [dataArr count] ;
NSUInteger closeCount = [closeArr count] ;
NSMutableArray * newData = [NSMutableArray new] ;
for(int i = 0 ; i<dataCount && i<closeCount ; i++) {
NSMutableDictionary * temp = [NSMutableDictionary new] ;
NSString * dataString = [dataArr objectAtIndex:i];
NSString * closeString = [closeArr objectAtIndex:i];
[temp setObject:dataString forKey:#"smth"];
[temp setObject:closeString forKey:#"smth"];
[newData addObject:temp];
}
NSLog(#"%#", newData);
_timeSeries = [NSMutableArray new];
NSString* filePath = [[NSBundle mainBundle] pathForResource:#"AppleStockPrices" ofType:#"json"];
NSData* json = [NSData dataWithContentsOfFile:filePath];
NSArray* data = [NSJSONSerialization JSONObjectWithData:json options:NSJSONReadingAllowFragments error:nil];
for (NSDictionary* jsonPoint in data) {
SChartDataPoint* datapoint = [self dataPointForDate:jsonPoint[#"smth"] andValue:jsonPoint[#"smth"]];
[_timeSeries addObject:datapoint];
}
});
}];
[task resume];
// Code here has a good chance of being executed before the completion block is complete
// _newdata = [[NSMutableArray alloc] init];
// NSLog(#"%#", _newdata);
}

IOS HTTP Post Creating and handling response

I'm trying to refactor some code I had used in Phonegap Javascript that was uploading an Audio File to IOS Objective C and I'm a bit lost.
The code in JavaScript is as follows:
function uploadToClypit(file){
console.log(file);
var options = new FileUploadOptions();
options.name = file.fullPath.substr(file.fullPath.lastIndexOf('/')+ 1);
options.audioFile = file.nativeURL;
options.fileName = file.nativeURL;
options.mimeType = "audio/wav";
options.headers = {
Connection: "close"
}
options.chunkedMode = false;
console.log(options);
var ft = new FileTransfer();
console.log(file.nativeURL);
ft.upload(file.nativeURL, encodeURI("http://upload.clyp.it/upload"), win, failUpload, options);
}
And the response is handled like this:
var win = function(r){
var jsontext = r.response;
var json = JSON.parse(jsontext);
console.log(json);
}
So in IOS I am trying to create a HTTP Post Request, append an Audio File to the HTTP Body, send in some other text parameters and then handle the response.
I've done this so far.
Set my controller to implement the
Set up a NSMutableData variable *_responseData to hold the response
I create the HTTP request by doing the following:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]
initWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://upload.clyp.it/upload"]]];
[request setHTTPMethod:#"POST"];
NSString *stringBoundary = #"0xKhTmLbOuNdArY---This_Is_ThE_BoUnDaRyy---pqo";
NSString *headerBoundary = [NSString stringWithFormat:#"multipart/form-data; boundary=%#",stringBoundary];
[request addValue:headerBoundary forHTTPHeaderField:#"Content-Type"];
NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[[NSString stringWithFormat:#"--%#\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:#"Content-Disposition: form-data; name=\"file upload\"; filename=\"uploadaudio.mp3\"\r\n\r\n"] dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[#"Content-Type: audio/m4a\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
NSIndexPath *cellIndexPath = [self.tableView indexPathForCell:cell];
NSFileManager *fileManager = [NSFileManager defaultManager];
NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:cellIndexPath];
ICIRecordingCell *c = (ICIRecordingCell *)cell;
NSString *fileName = c.title.text;
NSString *filePath = [documentsPath stringByAppendingPathComponent:fileName];
NSData *audioData;
audioData = [[NSData alloc] initWithContentsOfFile:filePath];
[postBody appendData:audioData];
[postBody appendData:[#"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[postBody appendData:[[NSString stringWithFormat:#"--%#--\r\n", stringBoundary] dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:postBody];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
And then I am trying to set up the delegate methods to handle the response:
#pragma mark NSURLConnection Delegate Methods
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
// A response has been received, this is where we initialize the instance var you created
// so that we can append data to it in the didReceiveData method
// Furthermore, this method is called each time there is a redirect so reinitializing it
// also serves to clear it
_responseData = [[NSMutableData alloc] init];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
// Append the new data to the instance variable you declared
[_responseData appendData:data];
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse*)cachedResponse {
// Return nil to indicate not necessary to store a cached response for this connection
return nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
// The request is complete and data has been received
// You can parse the stuff in your instance variable now
NSString *d = (NSString *)_responseData;
NSLog(d);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
// The request has failed for some reason!
// Check the error var
}
When I look at what is being returned into the _responseData I'm not really sure what to do with it>
Printing description of self->_responseData: <7b224d65 73736167
65223a22 416e2065 72726f72 20686173 206f6363 75727265 642e227d>
I'm also not really sure if I've set up the request properly - I've just borrowed code that I've found here on StackOverflow without fully understanding it.
Any pointers direction more than welcome

FHSTwitterEngine image not post

Post image with FHStwitterEngine it return error But if i post tweet without imahe than it post successfully
-(void)postTweet:(NSString*)message withImage:(UIImage*)img {
dispatch_async(GCDBackgroundThread, ^{
#autoreleasepool {
NSString *strMsg =[NSString stringWithFormat:#"%#",message];
NSData *imgData =UIImagePNGRepresentation(img);
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSError *returnCode = [[FHSTwitterEngine sharedEngine]postTweet:strMsg withImageData:imgData];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
NSString *title = nil;
NSString *message = nil;
if (returnCode) {
title = [NSString stringWithFormat:#"Error %d",returnCode.code];
message = returnCode.localizedDescription;
} else {
title = #"Tweet Posted";
message = message;
}
dispatch_sync(GCDMainThread, ^{
#autoreleasepool {
UIAlertView *av = [[UIAlertView alloc]initWithTitle:title message:message delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[av show];
}
});
}
});
}
- (IBAction)btnPost:(id)sender {
NSString *str =#"Test5";
UIImage *image =[UIImage imageNamed:#"contact.png"];
if (image ==nil) {
[self postTweet:str];
}else {
[self postTweet:str withImage:image];
}
}
There is an issue with the current version of FHSTwitterEngine and posting images. I had the same issue in my app, but was able to solve it using the updates from this forked version:
https://github.com/alvani/FHSTwitterEngine/blob/master/FHSTwitterEngine/FHSTwitterEngine.m
It's easiest to just copy/paste that into FHSTwitterEngine.m, but here's the specific changes you'll need:
On or around Line 1599:
- (NSError *)sendPOSTRequestForURL:(NSURL *)url andParams:(NSDictionary *)params
...
[body appendData:[[NSString stringWithFormat:#"Content-Disposition: form-data; name=\"%#\"\r\n",key] dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:[#"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[body appendData:data];
Add the following code below:
if ([obj isKindOfClass:[NSData class]]) {
[body appendData:[#"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
}
On or around Line 688:
- (NSError *)postTweet:(NSString *)tweetString withImageData:(NSData *)theData inReplyTo:(NSString *)irt {
...
NSMutableDictionary *params = [NSMutableDictionary dictionary];
params[#"media[]"] = theData;
Add the following code below:
params[#"status"] = tweetString;
Hope this helps!

Uploading large images using Base64 and JSON

I am using this function to upload an image to a server using JSON. In order to do so, I first convert the image to NSData and then to NSString using Base64. The method works fine when the image is not very large but when I try to upload a 2Mb image, it crashes.
The problem is that the server doesn't receive my image even though the didReceiveResponse method is called as well as the didReceiveData which returns (null). At first I thought it was a time out issue but even setting it to 1000.0 it still doesn't work. Any idea? Thanks for your time!
Here's my current code:
- (void) imageRequest {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.myurltouploadimage.com/services/v1/upload.json"]];
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [NSString stringWithFormat:#"%#/design%i.png",docDir, designNum];
NSLog(#"%#",path);
NSData *imageData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:path]);
[Base64 initialize];
NSString *imageString = [Base64 encode:imageData];
NSArray *keys = [NSArray arrayWithObjects:#"design",nil];
NSArray *objects = [NSArray arrayWithObjects:imageString,nil];
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:kNilOptions error:&error];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d",[jsonData length]] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
[[NSURLConnection alloc] initWithRequest:request delegate:self];
NSLog(#"Image uploaded");
}
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(#"didReceiveResponse");
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(#"%#",[NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:nil]);
}
I finally decided to upload the Base64 image splitting it into smaller substrings. In order to do so, and as I needed many NSURLConnections, I created a subclass named TagConnection which gives a tag for each connection so that there's no possible confusion between them.
Then I created a TagConnection property in MyViewController with the purpose of accessing it from any function. As you can see, there's the -startAsyncLoad:withTag: function that allocs and inits the TagConnection and the -connection:didReceiveData: one which deletes it when I receive a response from the server.
Referring to the -uploadImage function, firstly, it converts the image into string and then splits it and put the chunks inside the JSON request. It is called until the variable offset is larger than the string length which means that all the chunks have been uploaded.
You can also prove that every chunk has been successfully uploaded by checking the server response every time and only calling the -uploadImage function when it returns success.
I hope this has been a useful answer. Thanks.
TagConnection.h
#interface TagConnection : NSURLConnection {
NSString *tag;
}
#property (strong, nonatomic) NSString *tag;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)tag;
#end
TagConnection.m
#import "TagConnection.h"
#implementation TagConnection
#synthesize tag;
- (id)initWithRequest:(NSURLRequest *)request delegate:(id)delegate startImmediately:(BOOL)startImmediately tag:(NSString*)tag {
self = [super initWithRequest:request delegate:delegate startImmediately:startImmediately];
if (self) {
self.tag = tag;
}
return self;
}
- (void)dealloc {
[tag release];
[super dealloc];
}
#end
MyViewController.h
#import "TagConnection.h"
#interface MyViewController : UIViewController
#property (strong, nonatomic) TagConnection *conn;
MyViewController.m
#import "MyViewController.h"
#interface MyViewController ()
#end
#synthesize conn;
bool stopSending = NO;
int chunkNum = 1;
int offset = 0;
- (IBAction) uploadImageButton:(id)sender {
[self uploadImage];
}
- (void) startAsyncLoad:(NSMutableURLRequest *)request withTag:(NSString *)tag {
self.conn = [[[TagConnection alloc] initWithRequest:request delegate:self startImmediately:YES tag:tag] autorelease];
}
- (void) uploadImage {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://www.mywebpage.com/upload.json"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:1000.0];
NSString *docDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
NSString *path = [NSString stringWithFormat:#"%#/design%i.png", docDir, designNum];
NSLog(#"%#",path);
NSData *imageData = UIImagePNGRepresentation([UIImage imageWithContentsOfFile:path]);
[Base64 initialize];
NSString *imageString = [Base64 encode:imageData];
NSUInteger length = [imageString length];
NSUInteger chunkSize = 1000;
NSUInteger thisChunkSize = length - offset > chunkSize ? chunkSize : length - offset;
NSString *chunk = [imageString substringWithRange:NSMakeRange(offset, thisChunkSize)];
offset += thisChunkSize;
NSArray *keys = [NSArray arrayWithObjects:#"design",#"design_id",#"fragment_id",nil];
NSArray *objects = [NSArray arrayWithObjects:chunk,#"design_id",[NSString stringWithFormat:#"%i", chunkNum],nil];
NSDictionary *jsonDictionary = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:jsonDictionary options:kNilOptions error:&error];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d",[jsonData length]] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
[self startAsyncLoad:request withTag:[NSString stringWithFormat:#"tag%i",chunkNum]];
if (offset > length) {
stopSending = YES;
}
}
- (void) connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSError *error;
NSArray *responseData = [NSJSONSerialization JSONObjectWithData:data options:kNilOptions error:&error];
if (!responseData) {
NSLog(#"Error parsing JSON: %#", error);
} else {
if (stopSending == NO) {
chunkNum++;
[self.conn cancel];
self.conn = nil;
[self uploadImage];
} else {
NSLog(#"---------Image sent---------");
}
}
}
#end
Please don't think this is the last option, this is just my observation.
I think you should send that NSData in chunks instead of complete Data.
I have seen such methodology in YouTube Video Uploading case.They send the Large set of NSData (NSData of Video File) in Chunks of many NSData.
They uses the Same Methodology for uploading the large data.
So should do google about the Youtube data Uploading API.And you should search out that method , YouTube Uploader Uses.
I hope it may help you .

Crashing on unrecognized selector sent with SimplePost request after it works successfully.

I've been using the SimplePost classes for several weeks and haven't had any problems. Now I'm crashing after a Request returns proper data in a Connection. I haven't (knowingly) touched the SimplePost class files. But when I run the analyzer, it now (never did before) points out the following method:
+ (NSMutableURLRequest *) urlencodedRequestWithURL:(NSURL *)url andDataDictionary:(NSDictionary *) dictionary {
// Create POST request
NSMutableURLRequest *urlencodedPostRequest = [NSMutableURLRequest requestWithURL:url];
[urlencodedPostRequest setHTTPMethod:#"POST"];
// Add HTTP header info
[urlencodedPostRequest addValue:#"application/x-www-form-urlencoded" forHTTPHeaderField: #"Content-Type"];
// Add POST body
NSMutableData *POSTBody = [NSMutableData data];
// Add k/v to the body
NSArray *keyArray = [dictionary allKeys];
for( int i = 0; i < [keyArray count]; ++i ) {
// Core Foundation function used to transform # ==> %40 , etc
NSString *escapedString = (__bridge NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)([dictionary objectForKey:[keyArray objectAtIndex:i]]),NULL, (CFStringRef)#"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8);
[POSTBody appendData:[[NSString stringWithFormat:#"%#=%#", [keyArray objectAtIndex:i], escapedString] dataUsingEncoding:NSUTF8StringEncoding]];
if( i < ([keyArray count] - 1) ) {
[POSTBody appendData:[[NSString stringWithFormat:#"&"] dataUsingEncoding:NSUTF8StringEncoding]];
}
}
[urlencodedPostRequest setHTTPBody:POSTBody];
return urlencodedPostRequest;
}
And running the Analyzer shows me:
the lines continue as:
I'm having a hard time understanding what's happening. Can anyone help, please? Thanks!
Have you tried the __bridge_transfer for escapedString?
NSString *escapedString = (__bridge_transfer NSString *) CFURLCreateStringByAddingPercentEscapes(NULL, (__bridge CFStringRef)([dictionary objectForKey:[keyArray objectAtIndex:i]]),NULL, (CFStringRef)#"!*'();:#&=+$,/?%#[]", kCFStringEncodingUTF8);
cf. Correct bridging for ARC?

Resources