Can't get OHHTTPStubs to work with NSURLSession - iphone

I'm trying to use OHHTTPStubs in my XCTest class,
This is how I configured OHTTPStubs in my test file.
//
// Tests Configuration
//
- (void)setUp
{
[super setUp];
_bundle = [NSBundle bundleForClass:[self class]];
[self configureHTTPStubs];
[self installHTTPStubs];
}
- (void)configureHTTPStubs
{
[OHHTTPStubs onStubActivation:^(NSURLRequest *request, id<OHHTTPStubsDescriptor> stub) {
NSLog(#"[OHHTTPStubs] Request to %# has been stubbed with %#", request.URL, stub.name);
}];
}
- (void)installHTTPStubs
{
HIAPIRequests *requester = [[HIAPIOperator sharedOperator] requester];
[OHHTTPStubs setEnabled:YES forSessionConfiguration:requester.session.configuration];
[OHHTTPStubs stubRequestsPassingTest:^BOOL(NSURLRequest *request) {
return [request.URL.path isEqualToString:#"/image_upload"];
} withStubResponse:^OHHTTPStubsResponse *(NSURLRequest *request) {
return [[OHHTTPStubsResponse responseWithFileAtPath:OHPathForFileInBundle(#"image_upload_ws_response.json", nil)
statusCode:201
headers:#{#"Content-Type":#"text/json"}] responseTime:OHHTTPStubsDownloadSpeed3G];
}].name = #"Image Upload OK";
}
//
// In my Requester class this is how I setup the NSURLSession configuration
//
- (void)configureURLSession
{
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config];
}
And this is how I'm performing a request
- (void)uploadImage:(UIImage *)image
completionBlock:(operationCompletionBlock)completionBlock
progressBlock:(operationProgressBlock)progressBlock
{
NSData *imageData = UIImageJPEGRepresentation(image, 0.80);
NSURLRequest *request = [NSURLRequest requestWithURLString:#"/image_upload"];
NSURLSessionUploadTask *uploadTask = [_session uploadTaskWithRequest:request
fromData:imageData
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
completionBlock(data, error);
}];
[_progressTable setObject:progressBlock forKey:uploadTask];
[uploadTask resume];
}
In the completionHandler callback I'm basically getting a no domain found error (error NSURLError * domain: #"NSURLErrorDomain" - code: -1003 0x08a70740) , #"A server with the specified hostname could not be found."
I'm completely sure that I'm querying the correct URL (the one I stubbed with OHHTTPStubs) in my test.
What could be going on here? Bug maybe?

#Goles I know we already discussed that directly on the related issue you created on my GitHub, but I'm answering it here to so that other SO readers can have the solution too.
The issue in #Goles ' code is that he uses [OHHTTPStubs setEnabled:YES forSessionConfiguration:requester.session.configuration]; on an NSURLSessionConfiguration that was already used to create its NSURLSession.
As the Apple documentation explains, you can't modify an NSURLSessionConfiguration once it has been used to create an NSURLSession. Well, you can, but it won't have any effect on the already-created NSURLSession. If you modify an NSURLSessionConfiguration, you will have to create a new NSURLSession with that modified NSURLSessionConfiguration to take the new configuration into account.
Moreover, in the latest versions of OHHTTPStubs, it is not necessary anymore to explicitly call +[setEnabled:forSessionConfiguration:]: as explained in the documentation, every NSURLSessionConfiguration created with defaultSessionConfiguration or ephemeralSessionConfiguration will automatically have OHHTTPStubs enabled on them if you use my library.
This means that you don't need to change anything in your working code for your stubs to hook into your sessions and network requests (even if you create the NSURLSessionConfiguration and NSURLSession somewhere deeply hidden in your app code).
For those of you who want to use OHHTTPStubs with NSURLSession I strongly recommend to use the latest 3.0.2 version of OHHTTPStubs. NSURLSession support was indeed introduced in version 2.4.0 of OHHTTPStubs, but there were some glitches left in that version that have been fixed in version 3.x since.

Related

How to pass info with NSURLConnection instance so I can get it from connectionDidFinishLoading

I am using NSURLConnection to load data from a response. It works as it should, the delegate method connectionDidFinishLoading has the connection instance with the data I need. The problem is that I want to pass some information along with the request so that I can get it when the connection finishes loading:
User wants to share the content of a URL via (Facebook, Twitter,
C, D).
NSURLConnection is used to get the content of the URL
Once I have the content, I use the SL framework
SLComposeViewController:composeViewControllerForServiceType and need
to give it the service type
At this point I don't know what service the user selected in step 1. I'd like to send that with the NSURLConnection.
Can I extend NSURLConnection with a property for this? That seems very heavy-handed. There must be a "right way" to do this.
Many Thanks
Assuming you don't need the delegate-based version of the NSURLConnection process for some other reason, this is a good use case for the block-based version:
- (void)shareContentAtURL:(NSURL *)shareURL viaService:(NSString *)service
{
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:shareURL];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] == 0 && error == nil) {
// handle empty response
} else if (error != nil) {
// handle error
} else {
// back to the main thread for UI stuff
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// do whatever you do to get something you want to post from the url content
NSString *postText = [self postTextFromData:data];
// present the compose view
SLComposeViewController *vc = [SLComposeViewController composeViewControllerForServiceType:service];
[vc setInitialText:postText];
[self presentViewController:vc animated:YES];
}];
}
}];
}
Since blocks can capture variables from their surrounding scope, you can just use whatever context you already had for the user's choice of service inside the NSURLConnection's completion block.
If you're still wed to the delegate-based NSURLConnection API for whatever reason, you can always use an ivar or some other piece of state attached to whatever object is handling this process: set self.serviceType or some such when the user chooses a service, then refer back to it once you get your content from the NSURLConnectionDelegate methods and are ready to show a compose view.
You could check the URL property of an NSURLConnection instance and determine the service by parsing the baseURL or absoluteString property of the URL with something like - (ServiceType)serviceTypeForURL:(NSURL *)theURL;
All the NSURLConnectionDelegate methods pass the calling NSURLConnection object-so you could get it from
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
or
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error

nsurlconnection asynchronous request

First of all the questions are failry simiple.. if you just want to see what they are skip to the bottom of this post and you will see them in bold.. for more detail then you can read the rest of this post...
I am just trying to iron out my NSURLConnection so that its working smoothly and I understand this properly. There is a profound lack of example/tutorials for Asynchronous connections on the internet or not any that I can find that explaine what is going on with any level of depth other than getting the connection up and running which after working on it seems pretty simple. Hopefully this question can full the void that I feel is out there for other users.
So, in my .h file i have imported the foundations headers and declared the methods required for the received or lack of received data (errors etc).
.h
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h> //add foundations
//.. other headers can be imported here
#interface MyViewController: UITableViewController {
//Im not setting any delegates to access the methods because Is all happening in the same
//place so I just use the key word 'self' when accessing the methods declared below
//I'm not sure if this is the best thing to do but I wasn't able to get my head around declaring the delegate or how it would help me with the way I have set up my request etc.
}
- (IBAction)setRequestString:(NSString *)string; //this method sets the request and connection methods
//these methods receive the response from my async nsurlconnection
- (void)receivedData:(NSData *)data;
- (void)emptyReply;
- (void)timedOut;
- (void)downloadError:(NSError *)error;
So thats my header file.. pretty simple not much explaining needed.
.m
//call setRequestString from some other method attached to a button click or something
[self setRequestString:#"rss.xml"];
//..
- (IBAction)setRequestString:(NSString *)string
{
//Set database address
NSMutableString *databaseURL = [[NSMutableString alloc] initWithString:#"http:www.comicbookresources/feeds/"]; // address not real jsut example
//append the string coming in to the end of the databaseURL
[databaseURL appendString:string];
//prepare NSURL with newly created string
NSURL *url = [NSURL URLWithString:databaseURL];
//AsynchronousRequest to grab the data
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
if ([data length] > 0 && error == nil){
[self receivedData:data];
}else if ([data length] == 0 && error == nil){
[self emptyReply];
}else if (error != nil && error.code == NSURLErrorTimedOut){ //used this NSURLErrorTimedOut from foundation error responses
[self timedOut];
}else if (error != nil){
[self downloadError:error];
}
}];
}
now set up the methods that were initialized in the .h file and called in the if statement above
- (void)receivedData:(NSData *)data
{
NSString* newStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", newStr); //logs recived data
//now you can see what data is coming in on your log
//do what you want with your data here (i.e. start parsing methods
}
- (void)emptyReply
{
//not sure what to do here yet?
}
- (void)timedOut
{
//also not sure what to do here yet?
}
- (void)downloadError:(NSError *)error
{
NSLog(#"%#", error);
UIAlertView *errorAlert = [[UIAlertView alloc] initWithTitle:#"Error!" message:#"A connection failure occurred." delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil, nil];
[errorAlert show];
}
Cool so that pretty much the basics of what I have done right there.. now the questions I have are as follows.
Question one:
Where I call NSURLConnection like so
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error)
{
What is happening here what is the ^ for is that executing that whole block (including the if statements) on a different thread or something? because it looks alot like grand central dispatch formatting but slightly different.
Question two:
what should I be doing inside emptyReply & timedOut methods?
Question three:
How would I incorporate caching into this? I would like to cache the responses I get back from different requests. i.e. with my setRequestString you will see there is a string input parameter, so i can request different rss feeds with the same method.. I need to figure out how to cache these responses into individual caches.. but im not sure where to start with it.
Finally
If you have made it this far, thank you very much for reading my question. Hopefully with your responses we can get a pretty nice solution going here.. that other people can use for themselves and pick and choose the bits and peices they need that works for there own solution..
Anyway thank you very much for reading and I look forward to your replies.. even if they are just refrences to tutorials or examples you think might help me.. anything is good I just want to fully understand whats going on and whats a good solution.
Read about blocks in Apple documentation. Its new. Or you can read here
You can show errors such as request timed out etc. You don't really have to handle them separately than the error one unless you have special logic.
Try this for caching
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:TIMEOUT_INTERVAL];

Using NSUrlConnection but getting beat by race conditions

I have a program that has multiple url request so I used the the code in http://snippets.aktagon.com/snippets/350-How-to-make-asynchronous-HTTP-requests-with-NSURLConnection and put it in it's own class (class B).To call the class I am simple initializing class B in class A, sending a url to class B's get method([classname get:url]) and then getting the server response upon return.
The problem is that I am getting defeated by race conditions due to the fact that the didReceiveData: method is not complete by the time my method is returned.
I have gone through the developer example of using NSUrlConnection and they are updating views once the response finally came in so they didn't have to fight this problem.
Thank you so much for your help.
I need to keep the calls asynchronous due to the number of them I have to make but I am open to any suggestions.
Edit (moved from answer)
I changed the code to GCD based off of a tutorial and I am still getting defeated by the race condition. Here is the code that I am using now:
I changed it to GCS based on on your suggestion but I am still getting caught by the race condition. Below is the code that I changed it to and I am calling it by:
NSString *responseStringClassA = [InitalizedInstanceOfClassA LogIn:#"username" #"password"];
//Log into the server
-(NSString *)logIn: (NSString *) username password:(NSString *) password
{
NSString* returnString;
dispatch_queue_t downloadQueue = dispatch_queue_create("Login", NULL);
dispatch_async(downloadQueue, ^{
BOOL success = YES;
NSString *urlAsString =[NSString stringWithFormat:#""URL HERE];
NSLog(#"url sent out: %#", urlAsString);
NSURL *url = [NSURL URLWithString:urlAsString];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];
NSError *error = nil;
NSData *connectionData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:nil error:&error];
NSLog(#"Connection Data: %#", [[NSString alloc] initWithData:connectionData encoding:NSASCIIStringEncoding]);
[returnString isEqualToString:[NSString stringWithUTF8String:[connectionData bytes]]];
if ([connectionData length] > 0 && error == nil) {
//success
success = YES;
}
else if([connectionData length] == 0 && error == nil){
//nodata
success = YES;
}
else if(error != nil){
//error ..
success = NO;
}
dispatch_async(dispatch_get_main_queue(), ^{
[returnString isEqualToString:[[NSString alloc] initWithData:connectionData encoding:NSASCIIStringEncoding] ];
});
});
return returnString;
}
It's the very purpose of asynchronous requests that the intial method returns almost immediately without having done the work. Later, when the work has been done, you will be notified and you can access and use the result.
But obviously, you're looking for something else than asynchronous operations. And alternative would be to use synchronous URL requests but run them from separate threads. The best way to achieve this is to use GCD (grand central dispatch).
Note that you may not update the user interface from background threads. Instead, when the URL request has finished and you want to display your results, you have to call performSelectorOnMainThread (part of NSObject) for that.

iOS download and save image inside app

Is it possible for me to download an image from website and save it permanently inside my app? I really have no idea, but it would make a nice feature for my app.
Although it is true that the other answers here will work, they really aren't solutions that should ever be used in production code. (at least not without modification)
Problems
The problem with these answers is that if they are implemented as is and are not called from a background thread, they will block the main thread while downloading and saving the image. This is bad.
If the main thread is blocked, UI updates won't happen until the downloading/saving of the image is complete. As an example of what this means, say you add a UIActivityIndicatorView to your app to show the user that the download is still in progress (I will be using this as an example throughout this answer) with the following rough control flow:
Object responsible for starting the download is loaded.
Tell the activity indicator to start animating.
Start the synchronous download process using +[NSData dataWithContentsOfURL:]
Save the data (image) that was just downloaded.
Tell the activity indicator to stop animating.
Now, this might seem like reasonable control flow, but it is disguising a critical problem.
When you call the activity indicator's startAnimating method on the main (UI) thread, the UI updates for this event won't actually happen until the next time the main run loop updates, and this is where the first major problem is.
Before this update has a chance to happen, the download is triggered, and since this is a synchronous operation, it blocks the main thread until it has finished download (saving has the same problem). This will actually prevent the activity indicator from starting its animation. After that you call the activity indicator's stopAnimating method and expect all to be good, but it isn't.
At this point, you'll probably find yourself wondering the following.
Why doesn't my activity indicator ever show up?
Well, think about it like this. You tell the indicator to start but it doesn't get a chance before the download starts. After the download completes, you tell the indicator to stop animating. Since the main thread was blocked through the whole operation, the behavior you actually see is more along the lines telling the indicator to start and then immediately telling it to stop, even though there was a (possibly) large download task in between.
Now, in the best case scenario, all this does is cause a poor user experience (still really bad). Even if you think this isn't a big deal because you're only downloading a small image and the download happens almost instantaneously, that won't always be the case. Some of your users may have slow internet connections, or something may be wrong server side keeping the download from starting immediately/at all.
In both of these cases, the app won't be able to process UI updates, or even touch events while your download task sits around twiddling its thumbs waiting for the download to complete or for the server to respond to its request.
What this means is that synchronously downloading from the main thread prevents you from possibly implementing anything to indicate to the user that a download is currently in progress. And since touch events are processed on the main thread as well, this throws out the possibility of adding any kind of cancel button as well.
Then in the worst case scenario, you'll start receiving crash reports stating the following.
Exception Type: 00000020 Exception Codes: 0x8badf00d
These are easy to identify by the exception code 0x8badf00d, which can be read as "ate bad food". This exception is thrown by the watch dog timer, whose job is to watch for long running tasks that block the main thread, and to kill the offending app if this goes on for too long. Arguably, this is still a poor user experience issue, but if this starts to occur, the app has crossed the line between bad user experience, and terrible user experience.
Here's some more info on what can cause this to happen from Apple's Technical Q&A about synchronous networking (shortened for brevity).
The most common cause for watchdog timeout crashes in a network application is synchronous networking on the main thread. There are four contributing factors here:
synchronous networking — This is where you make a network request and block waiting for the response.
main thread — Synchronous networking is less than ideal in general, but it causes specific problems if you do it on the main thread. Remember that the main thread is responsible for running the user interface. If you block the main thread for any significant amount of time, the user interface becomes unacceptably unresponsive.
long timeouts — If the network just goes away (for example, the user is on a train which goes into a tunnel), any pending network request won't fail until some timeout has expired....
...
watchdog — In order to keep the user interface responsive, iOS includes a watchdog mechanism. If your application fails to respond to certain user interface events (launch, suspend, resume, terminate) in time, the watchdog will kill your application and generate a watchdog timeout crash report. The amount of time the watchdog gives you is not formally documented, but it's always less than a network timeout.
One tricky aspect of this problem is that it's highly dependent on the network environment. If you always test your application in your office, where network connectivity is good, you'll never see this type of crash. However, once you start deploying your application to end users—who will run it in all sorts of network environments—crashes like this will become common.
Now at this point, I'll stop rambling about why the provided answers might be problematic and will start offering up some alternative solutions. Keep in mind that I've used the URL of a small image in these examples and you'll notice a larger difference when using a higher resolution image.
Solutions
I'll start by showing a safe version of the other answers, with the addition of how to handle UI updates. This will be the first of several examples, all of which will assume that the class in which they are implemented has valid properties for a UIImageView, a UIActivityIndicatorView, as well as the documentsDirectoryURL method to access the documents directory. In production code, you may want to implement your own method to access the documents directory as a category on NSURL for better code reusability, but for these examples, this will be fine.
- (NSURL *)documentsDirectoryURL
{
NSError *error = nil;
NSURL *url = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory
inDomain:NSUserDomainMask
appropriateForURL:nil
create:NO
error:&error];
if (error) {
// Figure out what went wrong and handle the error.
}
return url;
}
These examples will also assume that the thread that they start off on is the main thread. This will likely be the default behavior unless you start your download task from somewhere like the callback block of some other asynchronous task. If you start your download in a typical place, like a lifecycle method of a view controller (i.e. viewDidLoad, viewWillAppear:, etc.) this will produce the expected behavior.
This first example will use the +[NSData dataWithContentsOfURL:] method, but with some key differences. For one, you'll notice that in this example, the very first call we make is to tell the activity indicator to start animating, then there is an immediate difference between this and the synchronous examples. Immediately, we use dispatch_async(), passing in the global concurrent queue to move execution to the background thread.
At this point, you've already greatly improved your download task. Since everything within the dispatch_async() block will now happen off the main thread, your interface will no longer lock up, and your app will be free to respond to touch events.
What is important to notice here is that all of the code within this block will execute on the background thread, up until the point where the downloading/saving of the image was successful, at which point you might want to tell the activity indicator to stopAnimating, or apply the newly saved image to a UIImageView. Either way, these are updates to the UI, meaning you must dispatch back the the main thread using dispatch_get_main_queue() to perform them. Failing to do so results in undefined behavior, which may cause the UI to update after an unexpected period of time, or may even cause a crash. Always make sure you move back to the main thread before performing UI updates.
// Start the activity indicator before moving off the main thread
[self.activityIndicator startAnimating];
// Move off the main thread to start our blocking tasks.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// Create the image URL from a known string.
NSURL *imageURL = [NSURL URLWithString:#"http://www.google.com/images/srpr/logo3w.png"];
NSError *downloadError = nil;
// Create an NSData object from the contents of the given URL.
NSData *imageData = [NSData dataWithContentsOfURL:imageURL
options:kNilOptions
error:&downloadError];
// ALWAYS utilize the error parameter!
if (downloadError) {
// Something went wrong downloading the image. Figure out what went wrong and handle the error.
// Don't forget to return to the main thread if you plan on doing UI updates here as well.
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
NSLog(#"%#",[downloadError localizedDescription]);
});
} else {
// Get the path of the application's documents directory.
NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
// Append the desired file name to the documents directory path.
NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:#"GCD.png"];
NSError *saveError = nil;
BOOL writeWasSuccessful = [imageData writeToURL:saveLocation
options:kNilOptions
error:&saveError];
// Successful or not we need to stop the activity indicator, so switch back the the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Now that we're back on the main thread, you can make changes to the UI.
// This is where you might display the saved image in some image view, or
// stop the activity indicator.
// Check if saving the file was successful, once again, utilizing the error parameter.
if (writeWasSuccessful) {
// Get the saved image data from the file.
NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
// Set the imageView's image to the image we just saved.
self.imageView.image = [UIImage imageWithData:imageData];
} else {
NSLog(#"%#",[saveError localizedDescription]);
// Something went wrong saving the file. Figure out what went wrong and handle the error.
}
[self.activityIndicator stopAnimating];
});
}
});
Now keep in mind, that the method shown above is still not an ideal solution considering it can't be cancelled prematurely, it gives you no indication of the progress of the download, it can't handle any kind of authentication challenge, it can't be given a specific timeout interval, etc. (lots and lots of reasons). I'll cover a few of the better options below.
In these examples, I'll only be covering solutions for apps targeting iOS 7 and up considering (at time of writing) iOS 8 is the current major release, and Apple is suggesting only supporting versions N and N-1. If you need to support older iOS versions, I recommend looking into the NSURLConnection class, as well as the 1.0 version of AFNetworking. If you look at the revision history of this answer, you can find basic examples using NSURLConnection and ASIHTTPRequest, although it should be noted that ASIHTTPRequest is no longer being maintained, and should not be used for new projects.
NSURLSession
Lets start with NSURLSession, which was introduced in iOS 7, and greatly improves the ease with which networking can be done in iOS. With NSURLSession, you can easily perform asynchronous HTTP requests with a callback block and handle authentication challenges with its delegate. But what makes this class really special is that it also allows for download tasks to continue running even if the application is sent to the background, gets terminated, or even crashes. Here's a basic example of its usage.
// Start the activity indicator before starting the download task.
[self.activityIndicator startAnimating];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use a session with a custom configuration
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:#"http://www.google.com/images/srpr/logo3w.png"];
// Create the download task passing in the URL of the image.
NSURLSessionDownloadTask *task = [session downloadTaskWithURL:imageURL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// Get information about the response if neccessary.
if (error) {
NSLog(#"%#",[error localizedDescription]);
// Something went wrong downloading the image. Figure out what went wrong and handle the error.
// Don't forget to return to the main thread if you plan on doing UI updates here as well.
dispatch_async(dispatch_get_main_queue(), ^{
[self.activityIndicator stopAnimating];
});
} else {
NSError *openDataError = nil;
NSData *downloadedData = [NSData dataWithContentsOfURL:location
options:kNilOptions
error:&openDataError];
if (openDataError) {
// Something went wrong opening the downloaded data. Figure out what went wrong and handle the error.
// Don't forget to return to the main thread if you plan on doing UI updates here as well.
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(#"%#",[openDataError localizedDescription]);
[self.activityIndicator stopAnimating];
});
} else {
// Get the path of the application's documents directory.
NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
// Append the desired file name to the documents directory path.
NSURL *saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:#"NSURLSession.png"];
NSError *saveError = nil;
BOOL writeWasSuccessful = [downloadedData writeToURL:saveLocation
options:kNilOptions
error:&saveError];
// Successful or not we need to stop the activity indicator, so switch back the the main thread.
dispatch_async(dispatch_get_main_queue(), ^{
// Now that we're back on the main thread, you can make changes to the UI.
// This is where you might display the saved image in some image view, or
// stop the activity indicator.
// Check if saving the file was successful, once again, utilizing the error parameter.
if (writeWasSuccessful) {
// Get the saved image data from the file.
NSData *imageData = [NSData dataWithContentsOfURL:saveLocation];
// Set the imageView's image to the image we just saved.
self.imageView.image = [UIImage imageWithData:imageData];
} else {
NSLog(#"%#",[saveError localizedDescription]);
// Something went wrong saving the file. Figure out what went wrong and handle the error.
}
[self.activityIndicator stopAnimating];
});
}
}
}];
// Tell the download task to resume (start).
[task resume];
From this you'll notice that the downloadTaskWithURL: completionHandler: method returns an instance of NSURLSessionDownloadTask, on which an instance method -[NSURLSessionTask resume] is called. This is the method that actually tells the download task to start. This means that you can spin up your download task, and if desired, hold off on starting it (if needed). This also means that as long as you store a reference to the task, you can also utilize its cancel and suspend methods to cancel or pause the task if need be.
What's really cool about NSURLSessionTasks is that with a little bit of KVO, you can monitor the values of its countOfBytesExpectedToReceive and countOfBytesReceived properties, feed these values to an NSByteCountFormatter, and easily create a download progress indicator to your user with human readable units (e.g. 42 KB of 100 KB).
Before I move away from NSURLSession though, I'd like to point out that the ugliness of having to dispatch_async back to the main threads at several different points in the download's callback block can be avoided. If you chose to go this route, you can initialize the session with its initializer that allows you to specify the delegate, as well as the delegate queue. This will require you to use the delegate pattern instead of the callback blocks, but this may be beneficial because it is the only way to support background downloads.
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:[NSOperationQueue mainQueue]];
AFNetworking 2.0
If you've never heard of AFNetworking, it is IMHO the end-all of networking libraries. It was created for Objective-C, but it works in Swift as well. In the words of its author:
AFNetworking is a delightful networking library for iOS and Mac OS X. It's built on top of the Foundation URL Loading System, extending the powerful high-level networking abstractions built into Cocoa. It has a modular architecture with well-designed, feature-rich APIs that are a joy to use.
AFNetworking 2.0 supports iOS 6 and up, but in this example, I will be using its AFHTTPSessionManager class, which requires iOS 7 and up due to its usage of all the new APIs around the NSURLSession class. This will become obvious when you read the example below, which shares a lot of code with the NSURLSession example above.
There are a few differences that I'd like to point out though. To start off, instead of creating your own NSURLSession, you'll create an instance of AFURLSessionManager, which will internally manage a NSURLSession. Doing so allows you take advantage of some of its convenience methods like -[AFURLSessionManager downloadTaskWithRequest:progress:destination:completionHandler:]. What is interesting about this method is that it lets you fairly concisely create a download task with a given destination file path, a completion block, and an input for an NSProgress pointer, on which you can observe information about the progress of the download. Here's an example.
// Use the default session configuration for the manager (background downloads must use the delegate APIs)
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
// Use AFNetworking's NSURLSessionManager to manage a NSURLSession.
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// Create the image URL from some known string.
NSURL *imageURL = [NSURL URLWithString:#"http://www.google.com/images/srpr/logo3w.png"];
// Create a request object for the given URL.
NSURLRequest *request = [NSURLRequest requestWithURL:imageURL];
// Create a pointer for a NSProgress object to be used to determining download progress.
NSProgress *progress = nil;
// Create the callback block responsible for determining the location to save the downloaded file to.
NSURL *(^destinationBlock)(NSURL *targetPath, NSURLResponse *response) = ^NSURL *(NSURL *targetPath, NSURLResponse *response) {
// Get the path of the application's documents directory.
NSURL *documentsDirectoryURL = [self documentsDirectoryURL];
NSURL *saveLocation = nil;
// Check if the response contains a suggested file name
if (response.suggestedFilename) {
// Append the suggested file name to the documents directory path.
saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:response.suggestedFilename];
} else {
// Append the desired file name to the documents directory path.
saveLocation = [documentsDirectoryURL URLByAppendingPathComponent:#"AFNetworking.png"];
}
return saveLocation;
};
// Create the completion block that will be called when the image is done downloading/saving.
void (^completionBlock)(NSURLResponse *response, NSURL *filePath, NSError *error) = ^void (NSURLResponse *response, NSURL *filePath, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
// There is no longer any reason to observe progress, the download has finished or cancelled.
[progress removeObserver:self
forKeyPath:NSStringFromSelector(#selector(fractionCompleted))];
if (error) {
NSLog(#"%#",error.localizedDescription);
// Something went wrong downloading or saving the file. Figure out what went wrong and handle the error.
} else {
// Get the data for the image we just saved.
NSData *imageData = [NSData dataWithContentsOfURL:filePath];
// Get a UIImage object from the image data.
self.imageView.image = [UIImage imageWithData:imageData];
}
});
};
// Create the download task for the image.
NSURLSessionDownloadTask *task = [manager downloadTaskWithRequest:request
progress:&progress
destination:destinationBlock
completionHandler:completionBlock];
// Start the download task.
[task resume];
// Begin observing changes to the download task's progress to display to the user.
[progress addObserver:self
forKeyPath:NSStringFromSelector(#selector(fractionCompleted))
options:NSKeyValueObservingOptionNew
context:NULL];
Of course since we've added the class containing this code as an observer to one of the NSProgress instance's properties, you'll have to implement the -[NSObject observeValueForKeyPath:ofObject:change:context:] method. In this case, I've included an example of how you might update a progress label to display the download's progress. It's really easy. NSProgress has an instance method localizedDescription which will display progress information in a localized, human readable format.
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary *)change
context:(void *)context
{
// We only care about updates to fractionCompleted
if ([keyPath isEqualToString:NSStringFromSelector(#selector(fractionCompleted))]) {
NSProgress *progress = (NSProgress *)object;
// localizedDescription gives a string appropriate for display to the user, i.e. "42% completed"
self.progressLabel.text = progress.localizedDescription;
} else {
[super observeValueForKeyPath:keyPath
ofObject:object
change:change
context:context];
}
}
Don't forget, if you want to use AFNetworking in your project, you'll need to follow its installation instructions and be sure to #import <AFNetworking/AFNetworking.h>.
Alamofire
And finally, I'd like to give a final example using Alamofire. This is a the library that makes networking in Swift a cake-walk. I'm out of characters to go into great detail about the contents of this sample, but it does pretty much the same thing as the last examples, just in an arguably more beautiful way.
// Create the destination closure to pass to the download request. I haven't done anything with them
// here but you can utilize the parameters to make adjustments to the file name if neccessary.
let destination = { (url: NSURL!, response: NSHTTPURLResponse!) -> NSURL in
var error: NSError?
// Get the documents directory
let documentsDirectory = NSFileManager.defaultManager().URLForDirectory(.DocumentDirectory,
inDomain: .UserDomainMask,
appropriateForURL: nil,
create: false,
error: &error
)
if let error = error {
// This could be bad. Make sure you have a backup plan for where to save the image.
println("\(error.localizedDescription)")
}
// Return a destination of .../Documents/Alamofire.png
return documentsDirectory!.URLByAppendingPathComponent("Alamofire.png")
}
Alamofire.download(.GET, "http://www.google.com/images/srpr/logo3w.png", destination)
.validate(statusCode: 200..<299) // Require the HTTP status code to be in the Successful range.
.validate(contentType: ["image/png"]) // Require the content type to be image/png.
.progress { (bytesRead, totalBytesRead, totalBytesExpectedToRead) in
// Create an NSProgress object to represent the progress of the download for the user.
let progress = NSProgress(totalUnitCount: totalBytesExpectedToRead)
progress.completedUnitCount = totalBytesRead
dispatch_async(dispatch_get_main_queue()) {
// Move back to the main thread and update some progress label to show the user the download is in progress.
self.progressLabel.text = progress.localizedDescription
}
}
.response { (request, response, _, error) in
if error != nil {
// Something went wrong. Handle the error.
} else {
// Open the newly saved image data.
if let imageData = NSData(contentsOfURL: destination(nil, nil)) {
dispatch_async(dispatch_get_main_queue()) {
// Move back to the main thread and add the image to your image view.
self.imageView.image = UIImage(data: imageData)
}
}
}
}
Asynchronous downloaded images with caching
Asynchronous downloaded images with caching
Here is one more repos which can be used to download images in background
You cannot save anything inside the app's bundle, but you can use +[NSData dataWithContentsOfURL:] to store the image in your app's documents directory, e.g.:
NSData *imageData = [NSData dataWithContentsOfURL:myImageURL];
NSString *imagePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:#"/myImage.png"];
[imageData writeToFile:imagePath atomically:YES];
Not exactly permanent, but it stays there at least until the user deletes the app.
That's the main concept. Have fun ;)
NSURL *url = [NSURL URLWithString:#"http://example.com/yourImage.png"];
NSData *data = [NSData dataWithContentsOfURL:url];
NSString *path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
path = [path stringByAppendingString:#"/yourLocalImage.png"];
[data writeToFile:path atomically:YES];
Since we are on IO5 now, you no longer need to write images to disk neccessarily.
You are now able to set "allow external storage" on an coredata binary attribute.
According to apples release notes it means the following:
Small data values like image thumbnails may be efficiently stored in a
database, but large photos or other media are best handled directly by
the file system. You can now specify that the value of a managed
object attribute may be stored as an external record - see setAllowsExternalBinaryDataStorage:
When enabled, Core Data heuristically decides on a per-value basis if
it should save the data directly in the database or store a URI to a
separate file which it manages for you. You cannot query based on the
contents of a binary data property if you use this option.
As other people said, there are many cases in which you should download a picture in the background thread without blocking the user interface
In this cases my favorite solution is to use a convenient method with blocks, like this one: (credit -> iOS: How To Download Images Asynchronously (And Make Your UITableView Scroll Fast))
- (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
And call it like
NSURL *imageUrl = //...
[[MyUtilManager sharedInstance] downloadImageWithURL:[NSURL URLWithString:imageURL] completionBlock:^(BOOL succeeded, UIImage *image) {
//Here you can save the image permanently, update UI and do what you want...
}];
Here's how I download an ad banner. It's best to do it in the background if you're downloading a large image or a bunch of images.
- (void)viewDidLoad {
[super viewDidLoad];
[self performSelectorInBackground:#selector(loadImageIntoMemory) withObject:nil];
}
- (void)loadImageIntoMemory {
NSString *temp_Image_String = [[NSString alloc] initWithFormat:#"http://yourwebsite.com/MyImageName.jpg"];
NSURL *url_For_Ad_Image = [[NSURL alloc] initWithString:temp_Image_String];
NSData *data_For_Ad_Image = [[NSData alloc] initWithContentsOfURL:url_For_Ad_Image];
UIImage *temp_Ad_Image = [[UIImage alloc] initWithData:data_For_Ad_Image];
[self saveImage:temp_Ad_Image];
UIImageView *imageViewForAdImages = [[UIImageView alloc] init];
imageViewForAdImages.frame = CGRectMake(0, 0, 320, 50);
imageViewForAdImages.image = [self loadImage];
[self.view addSubview:imageViewForAdImages];
}
- (void)saveImage: (UIImage*)image {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent: #"MyImageName.jpg" ];
NSData* data = UIImagePNGRepresentation(image);
[data writeToFile:path atomically:YES];
}
- (UIImage*)loadImage {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString* path = [documentsDirectory stringByAppendingPathComponent:#"MyImageName.jpg" ];
UIImage* image = [UIImage imageWithContentsOfFile:path];
return image;
}
Here is code to download an image asynchronously from url and then save where you want in objective-c:->
+ (void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
If you are using AFNetworking library to download image and that images are using in UITableview then You can use below code in cellForRowAtIndexPath
[self setImageWithURL:user.user_ProfilePicturePath toControl:cell.imgView];
-(void)setImageWithURL:(NSURL*)url toControl:(id)ctrl
{
NSURLRequest *request = [NSURLRequest requestWithURL:url];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request imageProcessingBlock:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
if (image) {
if([ctrl isKindOfClass:[UIButton class]])
{
UIButton btn =(UIButton)ctrl;
[btn setBackgroundImage:image forState:UIControlStateNormal];
}
else
{
UIImageView imgView = (UIImageView)ctrl;
imgView.image = image;
}
}
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"No Image");
}];
[operation start];}
You can download image without blocking UI with using NSURLSessionDataTask.
+(void)downloadImageWithURL:(NSURL *)url completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSURLSessionDataTask* _sessionTask = [[NSURLSession sharedSession] dataTaskWithRequest:[NSURLRequest requestWithURL:url]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (error != nil)
{
if ([error code] == NSURLErrorAppTransportSecurityRequiresSecureConnection)
{
completionBlock(NO,nil);
}
}
else
{
[[NSOperationQueue mainQueue] addOperationWithBlock: ^{
dispatch_async(dispatch_get_main_queue(), ^{
UIImage *image = [[UIImage alloc] initWithData:data];
completionBlock(YES,image);
});
}];
}
}];
[_sessionTask resume];
}
Here is a Swift 5 solution for downloading and saving an image or in general a file to the documents directory by using Alamofire:
func dowloadAndSaveFile(from url: URL) {
let destination: DownloadRequest.DownloadFileDestination = { _, _ in
var documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
documentsURL.appendPathComponent(url.lastPathComponent)
return (documentsURL, [.removePreviousFile])
}
let request = SessionManager.default.download(url, method: .get, to: destination)
request.validate().responseData { response in
switch response.result {
case .success:
if let destinationURL = response.destinationURL {
print(destinationURL)
}
case .failure(let error):
print(error.localizedDescription)
}
}
}

NSURLConnection - how to wait for completion

Our iPhone app code currently uses NSURLConnection sendSynchronousRequest and that works fine except we need more visibility into the connection progress and caching so we're moving to an async NSURLConnection.
What's the simplest way to wait for the async code to complete? Wrap it in a NSOperation/NSOperationQueue, performSelector..., or what?
Thanks.
I'm answering this in case anyone else bumps into the issue in the future. Apple's URLCache sample code is a fine example of how this is done. You can find it at:
iOS Developer Library - URLCache
As John points out in the comment above - don't block/wait - notify.
To use NSURLConnection asynchronously you supply a delegate when you init it in initWithRequest:delegate:. The delegate should implement the NSURLConnection delegate methods. NSURLConnection processing takes place on another thread but the delegate methods are called on the thread that started the asynchronous load operation for the associated NSURLConnection object.
Apart from notifications mentioned prior, a common approach is to have the class that needs to know about the URL load finishing set itself as a delegate of the class that's handling the URL callbacks. Then when the URL load is finished the delegate is called and told the load has completed.
Indeed, if you blocked the thread the connection would never go anywhere since it works on the same thread (yes, even if you are using the asynch methods).
I ran into this because our app used NSURLConnection sendSynchronousRequest in quite a few places where it made sense, like having some processing occurring on a background thread occasionally needing extra data to complete the processing. Something like this:
// do some processing
NSData * data = someCachedData;
if (data = nil) {
data = [NSURLConnection sendSynchronousRequest....]
someCachedData = data;
}
// Use data for further processing
If you have something like 3 different places in the same flow that do that, breaking it up into separate functions might not be desirable(or simply not doable if you have a large enough code base).
At some point, we needed to have a delegate for our connections(to do SSL certificate pinning) and I went trolling the internet for solutions and everything was of the form: "just use async and don't fight the framework!". Well, sendSynchronousRequest exists for a reason, this is how to reproduce it with an underlying async connection:
+ (NSData *)sendSynchronousRequest:(NSURLRequest *)request returningResponse:(NSURLResponse *__autoreleasing *)response error:(NSError *__autoreleasing *)error
{
static NSOperationQueue * requestsQueue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
requestsQueue = [[NSOperationQueue alloc] init];
requestsQueue.maxConcurrentOperationCount = NSOperationQueueDefaultMaxConcurrentOperationCount;
});
NSCondition * waitLock = [NSCondition new];
[waitLock lock];
__block NSError * returnedError;
__block NSURLResponse * returnedResponse;
__block NSData * returnedData;
__block BOOL done = NO;
[NSURLConnection sendAsynchronousRequest:request
queue:requestsQueue
completionHandler:^(NSURLResponse * response, NSData * data, NSError * connectionError){
returnedError = connectionError;
returnedResponse = response;
returnedData = data;
[waitLock lock];
done = YES;
[waitLock signal];
[waitLock unlock];
}];
if (!done) {
[waitLock wait];
}
[waitLock unlock];
*response = returnedResponse;
*error = returnedError;
return returnedData;
}
Posted here in case anyone comes looking as I did.
Note that NSURLConnection sendAsynchrounousRequest can be replaced by whatever way you use to send an async request, like creating an NSURLConnection object with a delegate or something.