How to upload to ASP.NET in Objective-C using NSURLSession - objective-c

I need to upload an image to my ASP.Net website, i have got it to work with NSURLConnection but when i needed to upload extremely big files, it crashes due to the lack of ram to transfer the file to NSData then upload it. So I found out about the new API NSURLSession and the method uploadTaskWithRequest:withFile to allow bigger files to be transferred. I have changed the max file limit on the ASP.NET server to allow huge files, and tested it and it works with large files (100MB+), but at some point the phone does not have enough ram to allocate the NSData of the file, when I use NSURLConnection. I have tried NSURLSession numerous times with no success.
Heres an example of what i've tried:
NSString *urlString = [NSString stringWithFormat:#"https://examplesecuresite.com/Index.aspx?username=%#", username];
NSMutableURLRequest *request =
[[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:#"PUT"];
// 3
NSData *noteContents = UIImageJPEGRepresentation(image, 1);
// 4
NSURLSession *session = [[NSURLSession alloc] init];
NSURLSessionUploadTask *uploadTask = [session
uploadTaskWithRequest:request
fromData:noteContents];
[uploadTask resume];

A couple of reactions:
This is not how you create a NSURLSession. You should either use the shared session:
NSURLSession *session = [NSURLSession sharedSession];
Or create a configuration with a delegate (obviously, implementing the NSURLSessionTaskDelegate and NSURLSessionDelegate protocols) and use that:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request
fromData:noteContents];
Or create a configuration without a delegate (but create upload task with completion block) and use that:
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:noteContents completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error) {
NSLog(#"%#", error);
return;
}
// do whatever other checking you want of the response here
}];
I don't know why you're retrieving the NSData from the UIImage. If this is a UIImage that you created dynamically (e.g. a screen snapshot), then fine, that's what you have to do. But if you can go back to the original asset (e.g. the original asset from ALAssetLibrary, the file we used in imageWithContentsOfFile or imageWithContentsOfURL, etc.), often that will be more efficient (both smaller asset as well as you can upload directly from persistent storage).
Unfortunately, using UIImageJPEGRepresentation with a quality setting of 1.0 often retrieves a much larger NSData than the original asset. (Plus we preserve any meta data, if any, its far more memory efficient, etc.) You can use a factor of 0.9 or 0.8, which is lossy, but will result in a much smaller file. If you really want something lossless, often UIImagePNGRepresentation will generate a smaller file than UIImageJPEGRepresentation with a quality setting of 1.0.
If you're worried about a memory efficient approach, this seems no more efficient. You're uploading a NSData in RAM. Any approach that consists of loading the data into a NSData will be no more memory efficient than your other approach, most likely.
If you want to be more memory efficient, avoid loading the image into an NSData at all (if you can), and just use the uploadTaskWithRequest:fromFile: with the original asset.
By the way, if you had the image in a file in persistent storage, it doesn't strike me that you have to make the shift to NSURLSession solely to address the memory issues. Strikes me that you could have use the delegate-based NSURLConnection, returning a NSInputStream from connection:needNewBodyStream: rather than loading the image into a NSData.

Related

NSURLSession and API

I am extremely confused with the NSUrlSession and the API. This is my first time trying to use an API so please explain this in the simplest form possible.
I found an API which gets the weather, I have made a string for the weather location. then did all the NSUrl / nsurlrequest. My goal is to output everything so I can see the keys of that API. Heres what I have so far but all It displays is 'Program ended with exit code 0'
I don't really know what is happening during the NSUrlsession because I learned how to use API with the NSUrlConnection via a youtube video.
NSString *location = #"London";
NSString *weatherString = [NSString stringWithFormat:#"https://api.openweathermap.org/data/2.5/weather?q=%#", location];
NSURL *weatherURL = [NSURL URLWithString:weatherString];
NSURLRequest *request = [NSURLRequest requestWithURL:weatherURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request
completionHandler:
^(NSData *data, NSURLResponse *response, NSError *error){
NSDictionary *weatherDictionary = [NSJSONSerialization JSONObjectWithData:data
options:NSJSONReadingMutableContainers
error:nil];
NSLog(#"%#", [weatherDictionary description]);
}];
It's hard to tell from this snippet, but one or more of the following problems are likely causing your issues:
You are retaining a reference to that task somewhere, right?
From the documentation for dataTaskWithRequest, you need to call [task resume] to actually start the task.
That URL won't work, because the api.openweathermap.org site doesn't support HTTPS. You'll need to change it to http, and possibly add an exception in the app's Info.plist to allow non-secure connections (they're disabled by default for new apps).
After you fix all that, you'll need an API key for the request to actually succeed.

Running app in Debug via Xcode vs running app manually

I have a Cocoa application. It runs fine via XCode 6, but when I run it manually via Finder, it behaves very strange: it seems that only the static XIB loads, no other code gets executed.
Do I need to sign it in order to work? I also tried archiving. For any clues, this is the code that executes first:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:verb];
NSData *data = [qs dataUsingEncoding:NSUTF8StringEncoding];
[request setHTTPBody:data];
NSError *err;
NSURLResponse *response;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&err];
NSString *resSrt = [NSString stringWithFormat:#"%#", responseData];
Yes, there are HTTP requests made first in applicationDidFinishLaunching for the design to change.
Thank you!
EDIT: Forgot to mention that I use dispatch_queue_t and dispatch_async for those requests, so I am not blocking the main thread.
I was actually having problems with some inexistent files.

loading big xml file

i'm trying to load big file from server, but when it's loading always very slow.
Can you suggest me method, how to fast load more, then 200k objects ?
Printing description of data:
(NSData *) data = 0x08e51d80 151341098 bytes
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if (error)
{
NSLog(#"Error,%#", [error localizedDescription]);
}
else
{
NSLog(#"%#", [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding]);
}
}];
Two points:
You can use file output stream for network request instead of holding the whole NSData in memory. For example, AFNetworking supports such option.
You can use a SAX parser with stream support for deserialising your data from file, to, again, avoid loading whole XML into memory. For example, NSXMLParser does support stream parsing.
Here's the example of setting up NSXMLParser:
NSInputStream *fileStream = [NSInputStream inputStreamWithURL:temporaryXMLFileURL];
NSXMLParser *parser = [[NSXMLParser alloc] initWithStream:fileStream];
parser.delegate = yourParserDelegate;
BOOL result = [parser parse];
You can implement your parsing & object instantiation code in a class adopting NSXMLParserDelegate, there're a lot of examples over the internet, even one from Apple.
Please keep in mind that all this tricks won't speed up downloading of your large file from the internet, it depends entirely on your connection speed. My approach would only help to reduce your application memory footprint (hence reducing the risk of your app being jettisoned by iOS because of memory pressure).
Switch to JSON. JSON will load and parse MUCH faster than XML. I have a JSON file that is 1.8 MB and it downloads and parses (I use AFNetworking for the connection) in around 7 seconds. Another tip, cache the resulting NSArray on the device so that the next time, you can load directly from that cached array and in the background request the file again and compare to the cached Array. If different, update the model with the new data.
Something like this:
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if (!error) {
NSError *error = nil;
id jsonObj = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error];
}
}
else {
NSLog(#"Error,%#", [error localizedDescription]);
}
}];

How to assign result of NSURLSession to a variable in same method?

To say I'm new to Objective-C would be a huge understatement. I'm primarily a Ruby/Rails developer and it completely spoiled me when it comes to OOP & programming in general.
After getting tired of reading tutorials, I decided to try to use NSRULSession to hit one of my Rails apps (an Elder Scrolls Online skill planner) & display some of the JSON response on my iOS app. Delegates make no sense, I'm not sure how to break this functionality up into methods, etc, so I thought I'd keep it simple & do it all in the viewDidLoad() method (yes, I know it's bad practice).
- (void)viewDidLoad {
[super viewDidLoad];
__block NSDictionary *skillData; // No clue what __block is
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"http://www.esomix.com/skill_builds/17.json"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
NSLog(#"%#", json[#"name"]);
skillData = json;
}];
[dataTask resume];
UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 200, 100)];
myLabel.textColor = [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1];
[self.view addSubview:myLabel];
NSLog(#"%#", skillData[#"name"]);
NSLog(#"bottom of method");
}
After lots of toying around, I figured out that despite the NSURLSession code before my NSLogs at the bottom, it returns its data after they're rendered. No wonder my label.text (not shown) wasn't getting set! Here's the order of my three NSLogs:
(null)
bottom of method
Single-Target Lockdown Sniper
I guess my question is what's the simplest, proper way to make an JSON API request and use the data to generate a UI element/other tasks after the data has returned. Thanks so much!
A couple of clarifying points:
You ask
what's the simplest, proper way to make an JSON API request and use the data to generate a UI element/other tasks after the data has returned
In short, use the JSON response inside the completion block, not after it, for example:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:[NSURL URLWithString:#"http://www.esomix.com/skill_builds/17.json"] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *skillData = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
// use `skillData` here
// finally, any UI/model updates should happen on main queue
dispatch_async(dispatch_get_main_queue(), ^{
UILabel *myLabel = [[UILabel alloc] initWithFrame:CGRectMake(50, 100, 200, 100)];
myLabel.textColor = [UIColor colorWithRed:255.0/255.0 green:255.0/255.0 blue:255.0/255.0 alpha:1];
myLabel.text = skillData[#"name"]; // or whatever you wanted from `skillData`
[self.view addSubview:myLabel];
});
}];
[dataTask resume];
// don't try to use `skillData` here, as the above block runs asynchronously,
// and thus `skillData` will not have been set yet
}
The purpose of the __block qualifier is to let the block update a variable whose scope is outside the block. But, because the NSURLSessionDataTask runs asynchronously, there's no point in trying to reference skillData outside that block (because the viewDidLoad method will have completed well before the completionHandler for the NSURLSessionDataTask is invoked, as illustrated by your NSLog results).
So, since there's no point in referencing skillData outside the block, then there's no point in defining it with the __block qualifier outside of the block. Just make it a local variable inside the block. If you want to update your model (or perhaps some properties of the view controller), you can do that (but when dealing with class properties and ivars, no __block qualifier is needed).

How to handle a few thousand NSURLConnection requests, download images and keep the memory usage down

I'm working on an app that downloads resources and writes them to disk for later offline use and it's always custom content. Currently we're working with content where there are about 4000 JPGs. The user initializes the download of the content onto the iPad and there's a progress bar in the UI, so the user does basically wait until it's done. Problem is that around 180 - 190 MB of memory allocated, it crashes.
What I've seen in Instruments is that CFData (store) is the main culprit and my understanding is that CFData (store) is the cache for NSURLConnection requests.
I've tried:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:nil];
[NSURLCache setSharedURLCache:sharedCache];
[sharedCache release];
and
[[NSURLCache sharedURLCache] removeAllCachedResponses];
as well as setting the Cache policy, to no improvement.
For reference, this is what my post request looks like:
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setCachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData];
[request setURL:[NSURL URLWithString:urlString]];
[request setHTTPMethod:#"POST"];
NSString *contentType = [NSString stringWithFormat:#"text/xml"];
[request addValue:contentType forHTTPHeaderField: #"Content-Type"];
NSMutableData *postBody = [NSMutableData data];
[postBody appendData:[xmlMessage dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPBody:postBody];
//get response
NSHTTPURLResponse* urlResponse = nil;
NSData *responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&httpError];
Any help would be met with great applause.
If you really want to do this with synchronous requests, then you might consider explicitly retianing and releasing the objects used in each request. Using the autorelease pool means that the leftover detritus from every request sits in the autorelease pool until the pool is drained.
If you have a for loop or some other loop handling these requests, you can also create a more local autorelease pool within the loop and then drain it just before the loop ends. If you do that and you have data that you want to retain beyond the scope of that loop (and the pool) you should retain it in the loop and relase it at some point later.
At some point, depending on your experience level, you should consider doing this all on a secondary thread. You can treat each fetch as a self-contained operation and then used something like NSOperation and NSOperationQueue to manage it. That way, you can launch multiple operations simulataneously and not block your thread while waiting for each response.