I am sending an asynchronous http GET request and the completionHandler is being called correctly. Code in the callback like NSLog gets run as I can see the output in the logs. However, the lines: self.imageView.image = nil; doesn't seem to go into effect until a few seconds after the NSLog statement "got here". Does anyone know what's happening? The sample code is below:
In ViewController.m:
#interface ViewController ()
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
#end
#implementation ViewController
#synthesize imageView = _imageView;
-void viewDidLoad {
// ImageView
UIImage *image = [UIImage imageNamed:#"test.jpg"];
self.imageView.backgroundColor = [UIColor blackColor];
self.imageView.clipsToBounds = YES;
self.imageView.image = image;
}
-void test {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://someurl"] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[request setHTTPMethod:#"GET"];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
self.imageView.image = nil;
NSLog(#"got here");
}];
}
#end
Ah, looks like setting the queue param to [NSOperationQueue mainQueue] fixed as it is here:
NSURLConnection sendAsynchronousRequest:queue:completionHandler: making multiple requests in a row?
As Undept suggested, call thoses lines on main thread. Like so:
dispatch_async (dispatch_get_main_queue (), ^{
self.imageView2.image = nil;
self.imageView.image = nil;
});
Related
In my app I am downloading image using blocks but it is freezing my UI. I have one network class which contains method to download image,
-(void)downloadImageWithCompletionHandler:^(NSData *aData, NSError *error)aBlock;
I am calling above method in my view controller to download image. So once the image is downloaded I am using NSData to show in image view. The network class method uses NSURLConnection methods to download the image.
[[NSURLConnection alloc] initWithRequest:theURLRequest delegate:self];
Once the data download is complete I am calling completion handler block of the view controller.
But I am not sure why my UI is freezing? Can anyone help me find where I am doing wrong?
Thanks in advance!
- (void) setThumbnailUrlString:(NSString *)urlString
{
NSString *url= [NSString stringWithFormat:#"%#",urlString];
//Set up Request:
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]init];
[request setURL:[NSURL URLWithString:url]];
NSOperationQueue *queue=[[NSOperationQueue alloc] init];
if ( queue == nil ){
queue = [[NSOperationQueue alloc] init];
}
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * resp, NSData *data, NSError *error)
{
dispatch_async(dispatch_get_main_queue(),^
{
if ( error == nil && data )
{
UIImage *urlImage = [[UIImage alloc] initWithData:data];
_headImageView.image=urlImage;
_backgroundImageView.image=urlImage;
}
});
}];
}
You need to download the image in background thread to avoid freezing the UI thread.There is a simple demo to achieve this.
- (void)downloadImageWithCompletionHandler:(void(^)(NSData *aData, NSError *error))aBlock {
NSURLRequest *theURLRequest = nil; // assign your request here.
NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:theURLRequest queue:mainQueue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// UIThread.
aBlock(data,connectionError);
}];
}
how to call this method.
[self downloadImageWithCompletionHandler:^(NSData *aData, NSError *error) {
// get UIImage.
UIImage *image = [UIImage imageWithData:aData];
}];
I figured out the problem. Problem was not in the block or using NSUrlConnection method, it is working properly. Problem was, I was saving data in file once I download it. This operation was happening on main thread which was blocking the UI.
I have several memory leaks(NO! see Update 1) in my App and they all come down to an asynchronous URLRequest. The code below gives me a memory leak, it seems like the 'data' is never released (The code below is logically not used in my app as it is a completely useless infinite loop, I just wrote it to show the memory leak. This makes the used RAM go from 5 to 20 MB in less than one second. My internet speed does match this [just for the record]):
- (void)start{
NSOperationQueue *oQC = [[NSOperationQueue alloc] init];
NSLog(#"a");
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.online-image-editor.com//styles/2014/images/example_image.png"]] queue:oQC completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(#"b");
[self start];
}];
}
I have also tried setting data = nil, but that did, like supposed, not work.
Does anybody have any Idea of how to avoid the memory leak and if this is the normal behavior of a NSURLConnection?
UPDATE 1:
This doesn't seem to be related to a memory leak but to a caching problem. (Thanks to #rdelmar, who saw the problem, but his solution doesn't quite work)
Based on this post I have tried creating a new 'Loader' class with this in it's .h file:
#import <Foundation/Foundation.h>
#interface Loader : NSObject <NSURLConnectionDelegate, NSURLConnectionDataDelegate>
#property NSMutableData *mData;
#property (nonatomic, copy) void (^returnBlock)(NSData *data, NSError *error);
- (id)initForWhateverWithURLString:(NSString*)urlString andHandler:(void (^)(NSData *data, NSError *error))handler;
#end
And this in it's .m file:
#import "Loader.h"
#implementation Loader
#synthesize mData;
- (id)initForWhateverWithURLString:(NSString*)urlString andHandler:(void (^)(NSData *data, NSError *error))handler{
self = [super init];
if (self) {
/*NSOperationQueue *oQC = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:mUR queue:oQC completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
[[NSURLCache sharedURLCache] removeCachedResponseForRequest:mUR];
handler(nil, nil);
}];*/
mData = [NSMutableData new];
self.returnBlock = handler;
NSMutableURLRequest *mUR = [[NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:60] mutableCopy];
NSURLConnection *URLCon = [[NSURLConnection alloc] initWithRequest:mUR delegate:self];
[URLCon start];
}
return self;
}
- (NSCachedURLResponse *)connection:(NSURLConnection *)connection willCacheResponse:(NSCachedURLResponse *)cachedResponse{
return nil;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[mData appendData:data];
data = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
self.returnBlock(mData, nil);
mData = nil;
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
self.returnBlock(nil, error);
}
#end
I also implemented:
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:#"nsurlcache"];
[NSURLCache setSharedURLCache:sharedCache];
Still, neither of these methods helps reducing the drastic RAM usage!
I don't think what you're seeing is a leak. I think the memory keeps increasing because the data is being cached. If you change the caching protocol of the request, the problem goes away (you're getting the default behavior, NSURLRequestUseProtocolCachePolicy, by using requestWithURL:)
- (void)viewDidLoad {
[super viewDidLoad];
self.req = [NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.online-image-editor.com//styles/2014/images/example_image.png"] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
[self start];
}
- (void)start{
NSOperationQueue *oQC = [NSOperationQueue mainQueue]; // this queue is where the completion block runs, so you should use mainQueue if you want to do any UI updating
NSLog(#"a");
[NSURLConnection sendAsynchronousRequest:self.req queue:oQC completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
NSLog(#"b");
[self start];
}];
}
After Edit:
After looking at this some more, I'm not sure why sendAsynchronousRequest: causes a rise in memory usage with time. I tested using NSURLSession (which is what we should be using now anyway), and that seemed to work without causing a memory increase.
- (void)viewDidLoad {
[super viewDidLoad];
NSString *urlString = #"http://www.online-image-editor.com//styles/2014/images/example_image.png";
self.req = [NSURLRequest requestWithURL:[NSURL URLWithString:urlString] cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
[self start];
}
- (void)start{
NSLog(#"a");
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:self.req completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
self.counter ++;
NSLog(#"counter is: %ld", self.counter);
[self start];
}];
[task resume];
}
if you are not using ARC then change
[[NSOperationQueue alloc] init];
to
[[[NSOperationQueue alloc] init] autorelease];
and
[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.online-image-editor.com//styles/2014/images/example_image.png"]]
to
[[NSURLRequest requestWithURL:[NSURL URLWithString:#"http://www.online-image-editor.com//styles/2014/images/example_image.png"]] autorelease]
My app displays a lot of images, so I am using page control to display the images in single controller.
User can able to see all the images by swiping.
I am using AFNetworking Library to show the good performance. I have used the property setImageWithUrl
// This is myCode
NSURL *myUrl = [NSURL URLWithString:#"http://mdb.scicloudsolutions.com:8001/sites/default/files/landscape_design_1.jpg"];
[imageView setImageWithUrl:myUrl];
it's not showing at first time only..
When I Print url and image
NSLog(#"Url is .. : %# image. is : %#",myUrl,imageView.image);
Response:
am receiving the url value but not image value.
It shows image is null at first time.
How to solve?
Here You Set [imageView setImageWithUrl:myUrl]; So it take time to load an Image in to ImageView.
Use SDWebImage Library For Better. Just Simple Download this Library From Link and Set Image like as
[imageView sd_setImageWithURL:url
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
Use following solution for this:
#property (nonatomic, strong) NSMutableData *data
#property (nonatomic, strong) NSURLConnection *connection;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
Add these delegate methods to your view controller:
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)incrementalData {
if (data==nil) {
data = [[NSMutableData alloc] initWithCapacity:2048];
}
[data appendData:incrementalData];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
connection = nil;
UIImage *image = [UIImage imageWithData:data];
// Set above image to your imageView.
data = nil;
}
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
/*============= YOU ARE ON BACK GROUND THREAD ==================*/
UIImageView *yourImageView=[[UIImageView alloc] init];
UIImage *img=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:#"pass your url here"]]];
dispatch_async(dispatch_get_main_queue(), ^(void) {
/*============= YOU ARE ON MAIN THREAD ==================*/
yourImageView.image=img;
});
});
i try to show image in my tableview from my array list of image url (my server)
i would like to show the number of cell for each of my image in my array list
i call a request
#import "ProfileViewController.h"
#import "SWRevealViewController.h"
#import "Recipe.h"
#interface ProfileViewController ()
#end
#implementation ProfileViewController {
}
- (void)viewDidLoad
{
[super viewDidLoad];
self.navigationItem.titleView = [[UIImageView alloc] initWithImage: [UIImage imageNamed:#"NavBarImage.png"]];
UIColor *background = [[UIColor alloc] initWithPatternImage:[UIImage imageNamed:#"BackGround.png"]];
self.view.backgroundColor = background;
_showBtn.target = self.revealViewController;
_showBtn.action = #selector(revealToggle:);
[self.view addGestureRecognizer:self.revealViewController.panGestureRecognizer];
CFStringRef tokenK = CFSTR("token");
CFStringRef token;
// Read the preference.
token = (CFStringRef)CFPreferencesCopyAppValue(tokenK,
kCFPreferencesCurrentApplication);
// When finished with value, you must release it
CFRelease(token);
NSLog(#"Token: %#",token);
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://test.egip.it/luca/imglist.php"]];
NSString *post =[[NSString alloc] initWithFormat:#"token=%#",token];
NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSError *error = [[NSError alloc] init];
NSHTTPURLResponse *response = nil;
NSData *urlData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
NSLog(#"Response code: %ld", (long)[response statusCode]);
NSData *returnData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:nil];
UIImage *image = [UIImage imageWithData:returnData];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = image;
NSString *returnString = [[NSString alloc] initWithData:returnData encoding:NSUTF8StringEncoding];
NSLog(#"returnString: %#",returnString);
;
}
#end
in my log i see the string of the url's image but i I would like to put on the tableview this image but i don't know how doing this
thanks for the answer and i apologize for my english !
UIImage image = [UIImage imageWithData:returnData];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = image;
After having the UIImageView setup correctly as shown above, you will add it as the subview of whatever view you want to see it in:
[tableViewCell addSubview:imageView];
However, getting the image for a UITableViewCell from the server synchronously is NOT a good idea. It will slow down the UITableView scrolling like crazy.
Why not just display the image using the AFNetworking function setImageWithURL, it takes the URL of an image and sets the UIImageView to that image. Its a much simpler way of doing things if you only need to display it.
It looks something like this:
[imageView setImageWithURL:[NSURL URLWithString:URL]];
In the header file you'll have something like this:
#property (weak, nonatomic) IBOutlet UIImageView *imageView;
Its much faster as well.
Here's a link: https://github.com/AFNetworking/AFNetworking
I am trying to download an image off of a server asynchronously. After the image is downloaded and placed, I then want the app to go to the next view controller with an image view that will contain the image. Here is my code:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://...../.png"]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error ) {
image = [[UIImage alloc] initWithData:data];
if (image != NULL) {
[self performSegueWithIdentifier:#"midnight" sender:self];
}
}
NSLog(#"%#", image);
}];
}
The problem is that the next view controller will come up with nothing on it for about 10-15 seconds and then show the image and the text that is supposed to be displayed on the view controller. Is there something that I am doing wrong here?
This worked fine for me. I used your code, but changed the operation queue to the mainQueue, and added code to pass the image to the ImageViewController:
#import "ViewController.h"
#import "ImageViewController.h"
#interface ViewController ()
#property (strong,nonatomic) UIImage *image;
#end
#implementation ViewController
-(IBAction)downloadPic:(id)sender {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:#"https://atmire.com/labs17/bitstream/handle/123456789/7618/earth-map-huge.jpg?sequence=1"]] cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:10];
[NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error ) {
_image = [[UIImage alloc] initWithData:data];
if (_image != NULL) {
NSLog(#"Image id: %#", _image);
NSLog(#"Image size is: %#", NSStringFromCGSize(_image.size));
[self performSegueWithIdentifier:#"midnight" sender:self];
}
}else{
NSLog(#"Error is: %#",error);
}
}];
}
-(void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
ImageViewController *ivc = segue.destinationViewController;
ivc.receivedImage = _image;
}
In the ImageViewController, I created a property, receivedImage, and had just this code:
#interface ImageViewController ()
#property (weak,nonatomic) IBOutlet UIImageView *iv;
#end
#implementation ImageViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.iv.image = self.receivedImage;
}