nsurlsession for multiple request in loop - ios

I have been trying to download image/txt but i need to do it until that url exists & download that image/txt file,so i keep calling same method again & when i set debug point i see this .
If url is right than i do not see any queue in debug navigator because it is not calling method again. i referred AFNetworking library to the same but i guess it`s working in same way as i am doing in NSURLSession,right?
Case:- I check for url if exists or not, so if it`s exists than load both urls(time.txt & image.png), otherwise call WebService(XmlParser) & keep check for urls for following files.
time.txt+image.png
or
tryagain.txt
show whichever exists.
Also checked this AFNetworking Question but it didnt helped because i do not want to add number of operations. i want to load file whichever exists.
Because Operations will be completed whether it is success or fail in AFNetworking/NSURLSession.
-(void)downloading
{
NSString *imageUrl = [NSString stringWithFormat:#"%#",txtNumber.text];
NSURLSessionConfiguration *sessionConfig =[NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session =[NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
NSURLSessionDownloadTask *getImageTask = [session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error)
{
UIImage *downloadedImage =[UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
if (downloadedImage)
{
carImgView.image = downloadedImage;
result = TRUE;
}
else
{
result = FALSE;
[self tryagain];
}
});
}];
[getImageTask resume];
}
-(void)tryagain
{
NSString *strImg = [[NSString stringWithFormat:#"%#",gblPolicyNo] stringByAppendingString:FilePolStatus];
NSString *apiImage = [NSString stringWithFormat:#"http://moviepoint.info/%#/%#",FolderPolStatus,strImg];
NSURL *aImgUrl = [NSURL URLWithString:apiImage];
// 2
NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
// 3
tryAgainSession =[NSURLSession sessionWithConfiguration:sessionConfig
delegate:self
delegateQueue:nil];
// 1
getTryAgainTask = [tryAgainSession downloadTaskWithURL:aImgUrl
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error)
{
// 2
UIImage *downloadedImage =[UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
//3
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
if (downloadedImage)
{
[policyImgWebView loadData:[NSData dataWithContentsOfURL:location] MIMEType:nil textEncodingName:nil baseURL:nil];
NSLog(#"YES");
}
else
{
NSLog(#"NO");
[self performInBackground];
}
});
}];
// 4
[getTryAgainTask resume];
}
Please Correct me if i am doing wrong & Help me to solve this problem.

Solved by taking One Global NSURLSession

Related

Wait for NSURLSessionDataTask to come back

I am new to Objective C and iOS development in general. I am trying to create an app that would make an http request and display the contents on a label.
When I started testing I noticed that the label was blank even though my logs showed that I had data back. Apparently this happens because the the response is not ready when the label text gets updated.
I put a loop on the top to fix this but I am almost sure there's got to be a better way to deal with this.
ViewController.m
- (IBAction)buttonSearch:(id)sender {
HttpRequest *http = [[HttpRequest alloc] init];
[http sendRequestFromURL: #"https://en.wiktionary.org/wiki/incredible"];
//I put this here to give some time for the url session to comeback.
int count;
while (http.responseText ==nil) {
self.outputLabel.text = [NSString stringWithFormat: #"Getting data %i ", count];
}
self.outputLabel.text = http.responseText;
}
HttpRequest.h
#import <Foundation/Foundation.h>
#interface HttpRequest : NSObject
#property (strong, nonatomic) NSString *responseText;
- (void) sendRequestFromURL: (NSString *) url;
- (NSString *) getElementBetweenText: (NSString *) start andText: (NSString *) end;
#end
HttpRequest.m
#implementation HttpRequest
- (void) sendRequestFromURL: (NSString *) url {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
}];
[task resume];
}
Thanks a lot for the help :)
Update
After reading a lot for the very useful comments here I realized that I was missing the whole point. So technically the NSURLSessionDataTask will add task to a queue that will make the call asynchronously and then I have to provide that call with a block of code I want to execute when the thread generated by the task has been completed.
Duncan thanks a lot for the response and the comments in the code. That helped me a lot to understand.
So I rewrote my procedures using the information provided. Note that they are a little verbose but, I wanted it like that understand the whole concept for now. (I am declaring a code block rather than nesting them)
HttpRequest.m
- (void) sendRequestFromURL: (NSString *) url
completion:(void (^)(NSString *, NSError *))completionBlock {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//Create a block to handle the background thread in the dispatch method.
void (^runAfterCompletion)(void) = ^void (void) {
if (error) {
completionBlock (nil, error);
} else {
NSString *dataText = [[NSString alloc] initWithData: data encoding: NSUTF8StringEncoding];
completionBlock(dataText, error);
}
};
//Dispatch the queue
dispatch_async(dispatch_get_main_queue(), runAfterCompletion);
}];
[task resume];
}
ViewController.m
- (IBAction)buttonSearch:(id)sender {
NSString *const myURL = #"https://en.wiktionary.org/wiki/incredible";
HttpRequest *http = [[HttpRequest alloc] init];
[http sendRequestFromURL: myURL
completion: ^(NSString *str, NSError *error) {
if (error) {
self.outputText.text = [error localizedDescription];
} else {
self.outputText.text = str;
}
}];
}
Please feel free to comment on my new code. Style, incorrect usage, incorrect flow; feedback is very important in this stage of learning so I can become a better developer :)
Again thanks a lot for the replies.
You know what, use AFNetworking to save your life.
Or just modify your HttpRequest's sendRequestFromURL:
- (void)sendRequestFromURL:(NSString *)url completion:(void(^)(NSString *str, NSError *error))completionBlock {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (error) {
completionBlock(nil, error);
} else {
completionBlock([[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding], error);
}
});
}];
[task resume];
}
and invoke like this
[http sendRequestFromURL:#"https://en.wiktionary.org/wiki/incredible" completion:^(NSString *str, NSError *error) {
if (!error) {
self.outputLabel.text = str;
}
}];
Rewrite your sendRequestFromURL function to take a completion block:
- (void) sendRequestFromURL: (NSString *) url
completion: (void (^)(void)) completion
{
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
if (completion != nil)
{
//The data task's completion block runs on a background thread
//by default, so invoke the completion handler on the main thread
//for safety
dispatch_async(dispatch_get_main_queue(), completion);
}
}];
[task resume];
}
Then, when you call sendRequestFromURL, pass in the code you want to run when the request is ready as the completion block:
[self.sendRequestFromURL: #"http://www.someURL.com&blahblahblah",
completion: ^
{
//The code that you want to run when the data task is complete, using
//self.responseText
}];
//Do NOT expect the result to be ready here. It won't be.
The code above uses a completion block with no parameters because your code saved the response text to an instance variable. It would be more typical to pass the response data and the NSError as parameters to the completion block. See #Yahoho's answer for a version of sendRequestFromURL that takes a completion block with a result string and an NSError parameter).
(Note: I wrote the code above in the SO post editor. It probably has a few syntax errors, but it's intended as a guide, not code you can copy/paste into place. Objective-C block syntax is kinda nasty and I usually get it wrong the first time at least half the time.)
If you want easy way then Don't make separate class for call webservice. Just make meethod in viewController.m instead. I mean write sendRequestFromURL in your viewController.m and update your label's text in completion handler something like,
- (void) sendRequestFromURL: (NSString *) url {
NSURL *myURL = [NSURL URLWithString: url];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL: myURL];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *task = [session dataTaskWithRequest: request
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
self.responseText = [[NSString alloc] initWithData: data
encoding: NSUTF8StringEncoding];
self.outputLabel.text = self.responseText;
})
}];
[task resume];
}

Asynchronously download images for UITableView using NSURLConnection

I have a TableView with customCells, when user press Start button on some cell the loading starts. There are many such cells, so I need to implement this downloading in parallel (asynchronously).
For image downloading and updating the cell in Table view I use next code:
#define myAsyncQueue dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)
I include this method into the async queue, that I supposed should enable parallel downloading of images.
- (void)didClickStartAtIndex:(NSInteger)cellIndex withData:
(CustomTableViewCell*)data
{
dispatch_async(myAsyncQueue, ^{
self.customCell = data;
self.selectedCell = cellIndex;
ObjectForTableCell* tmp =[self.dataDictionary objectForKey:self.names[cellIndex]];
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:tmp.imeageURL]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0];
self.connectionManager = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
});
}
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
self.urlResponse = response;
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
NSDictionary *dict = httpResponse.allHeaderFields;
NSString *lengthString = [dict valueForKey:#"Content-Length"];
NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
NSNumber *length = [formatter numberFromString:lengthString];
self.totalBytes = length.unsignedIntegerValue;
self.imageData = [[NSMutableData alloc] initWithCapacity:self.totalBytes];
}
-(void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
[self.imageData appendData:data];
self.customCell.progressView.progress = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length)/100;
float per = ((100.0/self.urlResponse.expectedContentLength)*self.imageData.length);
self.customCell.realProgressStatus.text = [NSString stringWithFormat:#"%0.f%%", per];
}
I tried to set this block to queue - main queue - cause its the place where image is already downloaded,
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
dispatch_async(dispatch_get_main_queue(), ^{
self.customCell.realProgressStatus.text = #"Downloaded";
UIImage *img = [UIImage imageWithData:self.imageData];
self.customCell.image.image = img;
self.customCell.tag = self.selectedCell;
});
[self.savedImages setObject:img forKey:self.customCell.nameOfImage.text];
NSNumber *myNum = [NSNumber numberWithInteger:self.selectedCell];
[self.tagsOfCells addObject:myNum];
}
Without all queues(when I comment it)all works properly - but just 1 downloading at a ones.
But when I tried to implement code with queues as a result it doesn't download anything. I understand that I did smh wrong but I can't define it.
Thanks a lot for any help in advance.
If your looking out for starting it form basics I guess you should start with NSURLSession as NSURLConnection most of implementation had been deprecated and won't be available after iOS 9. For complete reference URL Session Programming Guide and tutorial
Coming back to your question you should do something similar to this took it from tutorial
// 1
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
completionHandler:^(NSURL *location,
NSURLResponse *response,
NSError *error) {
// 2
UIImage *downloadedImage =
[UIImage imageWithData:
[NSData dataWithContentsOfURL:location]];
//3
dispatch_async(dispatch_get_main_queue(), ^{
// do stuff with image
_imageWithBlock.image = downloadedImage;
});
}];
// 4
[getImageTask resume];
But my personal recommendation is go for AFNetworking which is best for iOS networking and widely used/tested in iOS app world.
For image download using AFNetworking
[_imageView setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://i.imgur.com/fVhhR.png"]]
placeholderImage:nil
success:^(NSURLRequest *request , NSHTTPURLResponse *response , UIImage *image ){
NSLog(#"Loaded successfully: %d", [response statusCode]);
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){
NSLog(#"failed loading: %#", error);
}
];
EDIT : Async downloading using concurrency
// get main dispact queue
dispatch_queue_t queue = dispatch_get_main_queue();
// adding downloading task in queue using block
dispatch_async(queue, ^{
NSData* imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]];
UIImage* image = [[UIImage alloc] initWithData:imageData];
// after download compeletes geting main queue again as there can a possible crash if we assign directly
dispatch_async(dispatch_get_main_queue(), ^{
_imageWithBlock.image = image;
});
});
Use this sample code from Apple to solve your problem of lazy loading.

App Transport Security if I Don’t Know All the Insecure Domains I Need to Use

I have seen this question somewhat answered here but in my case I am using NSURLSession to display images. These images are uploaded by user or scanned into a database using a script.
In this case writing exception URL's (NSExceptionDomains) won't work because the image is hosted by a user on their site or some other site. If I allow NSAllowsArbitraryLoads will I still be able to be approve for App Store since I am not implementing the best practices of ATS?
I am not sure the best way to proceed. Any input would be appreciated!
Here is the code I am using.
NSString *thumbnail_url = [tempObject objectForKey:#"image"];
NSURL *url = [NSURL URLWithString:thumbnail_url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDownloadTask *downloadPhotoTask = [session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSData *imageData = [[NSData alloc] initWithContentsOfURL:location];
dispatch_async(dispatch_get_main_queue(), ^{
cell.tableImageView.image = [UIImage imageWithData:imageData];
});
}];
[downloadPhotoTask resume];
Yes, you'll pass Review even with this parameter.
We have uploaded many builds since iOS9 SDK with NSAllowsArbitraryLoads set to YES.
P.S.: Your code should better look like this:
cell.thumbnailURL = URL;
__weak typeof(cell) weak
NSURLSessionDownloadTask *downloadPhotoTask = [session downloadTaskWithURL:URL completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
NSData *imageData = [[NSData alloc] initWithContentsOfURL:location];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
if (weakCell.thumbnailURL != URL) {
return;
}
weakCell.tableImageView.image = image;
});
}];
[downloadPhotoTask resume];

Using NSURLSessionDownloadTask to display an image

I was wondering if someone could help me out. I'm trying to use NSURLSessionDownloadTask to display a picture in my UIImageView if I put the image URL into my textfield.
-(IBAction)go:(id)sender {
NSString* str=_urlTxt.text;
NSURL* URL = [NSURL URLWithString:str];
NSURLRequest* req = [NSURLRequest requestWithURL:url];
NSURLSession* session = [NSURLSession sharedSession];
NSURLSessionDownloadTask* downloadTask = [session downloadTaskWithRequest:request];
}
I am not sure where to go after this.
Two options:
Use [NSURLSession sharedSession], with rendition of downloadTaskWithRequest with the completionHandler. For example:
typeof(self) __weak weakSelf = self; // don't have the download retain this view controller
NSURLSessionTask* downloadTask = [session downloadTaskWithRequest:request completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
// if error, handle it and quit
if (error) {
NSLog(#"downloadTaskWithRequest failed: %#", error);
return;
}
// if ok, move file
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *documentsURL = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask][0];
NSURL *fileURL = [documentsURL URLByAppendingPathComponent:filename];
NSError *moveError;
if (![fileManager moveItemAtURL:location toURL:fileURL error:&moveError]) {
NSLog(#"moveItemAtURL failed: %#", moveError);
return;
}
// create image and show it im image view (on main queue)
UIImage *image = [UIImage imageWithContentsOfFile:[fileURL path]];
if (image) {
dispatch_async(dispatch_get_main_queue(), ^{
weakSelf.imageView.image = image;
});
}
}];
[downloadTask resume];
Clearly, do whatever you want with the downloaded file (put it somewhere else if you want), but this might be the basic pattern
Create NSURLSession using session:delegate:queue: and specify your delegate, in which you'll conform to NSURLSessionDownloadDelegate and handle the completion of the download there.
The former is easier, but the latter is richer (e.g. useful if you need special delegate methods, such as authentication, detecting redirects, etc., or if you want to use background session).
By the way, don't forget to [downloadTask resume], or else the download will not start.

UIImageView loading image very slowly

I have some code that gets an image from a web page and displays it in an ImageView. But the image loads very slowly for some reason I don't really understand! Through my logging I can see that all the data for the image (base64 string) arrives pretty instantly, yet it takes about 12 - 15 seconds for the image to appear in the ImageView.
I find this very strange because I used an NSStream to get the data for the image in a different method and the image loaded as soon as all the data arrived. But with this URLSession method its taking longer for the image to load. This doesn't really make sense! This method shouldn't affect how the ImageView loads that data.
Has anybody any ideas why this might be happening?
heres the code:
- (void)postMethod:(NSDictionary *)numDict
{
NSURL *url = [NSURL URLWithString:#"http://theWebAddress.com/aPage.php"]; // add url to page
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
request.HTTPMethod = #"POST";
NSError *error = nil;
NSData *data = [NSJSONSerialization dataWithJSONObject:numDict options:kNilOptions error:&error];
NSLog(#"%#", numDict);
if (!error)
{
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:data completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *diction = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableContainers error:nil];
for (id key in diction)
{
if ([key isEqualToString:#"text"])
{
NSLog(#"data is text");
self.messageLabel.text = diction[#"text"];
break;
}
else if ([key isEqualToString:#"image"])
{
NSLog(#"data is an image");
// gets the base64 string pretty instantly but takes 12 - 15 seconds to pop up in the imageView
NSData *ImgData = [[NSData alloc] init];
ImgData = [[NSData alloc] initWithBase64EncodedString:diction[#"image"] options:1];
self.ImageView.image = [UIImage imageWithData:ImgData];
break;
}
}
}];
[uploadTask resume];
}
}
many thanks!
Your completion handler might be operating on a background thread. UI updates should always work on the main thread. Put a break point at
self.ImageView.image = [UIImage imageWithData:ImgData];
and see if it is on the main thread. If not, dispatch it to the main thread before you set the ImageView.image:
dispatch_async(dispatch_get_main_queue(), ^{
self.ImageView.image = [UIImage imageWithData:ImgData];
});
You can try to use SDWebImage https://github.com/rs/SDWebImage and all you need is to set the image in imageView like this:
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
You are firstly downloading image and then showing image.You can download image by using lazy loading.
For this you can use EgoImageView not uiimageview.
self.ImageView.imageURL=[NSURL URLWithString:
here self.ImageView is of egoimageview type.
you can get this class from github.
https://github.com/enormego/EGOImageLoading

Resources