I just moved from ASIHTTP to AFNetworking library. I am also using SDURLCache library from Olivier Poitrey and Peter Steinberg. I want to cache all the images that I gonna use in my application. For these, I tried this:
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
for(NSURLRequest *imageRequest in imageRequestArray){
AFURLConnectionOperation *operation2 = [[[AFURLConnectionOperation alloc] initWithRequest:imageRequest] autorelease];
[queue addOperation: operation2];
[operation2 waitUntilFinished];
}
And when I need to show images, I am doing this for each image:
NSURL *url = [NSURL URLWithString:[cat imagePath]];
NSURLRequest *imageRequest = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:30.0];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:imageRequest
imageProcessingBlock:nil
cacheName:#"nscache"
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image){
//NSData *responseData = [request responseData];
imageView.alpha = 0.0;
[imageView setImage:image forState:UIControlStateNormal];
[UIView beginAnimations:#"ToggleViews" context:nil];
[UIView setAnimationDuration:1.0];
imageView.alpha = 1.0;
[UIView commitAnimations];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error){}
];
[operation start];
After a while, application gives memory warning, then shuts down. For that, I did the following:
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
[[NSURLCache sharedURLCache] removeAllCachedResponses];
NSLog(#"Memory Warning...");
}
It clears the cache(what I don't what) but after a while, application closes again.
What should I do for that?
The problem is likely caused because ARC doesn't automatically get set up on queues or on background threads. I had the same issue and solved it with the following article:
https://agilewarrior.wordpress.com/2012/04/09/memory-leaks-with-arc/
Related
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.
i want to use afnetworking method to download and show image into imageview.
another step is i want to save all image in case of user open application in offline mode.
there is a few article on SO that give method on how to use UrlCache and cache variable in url header.
with background of developing in android is there any way to make Afnetworking works like Imageloader
solution in android ? (saving file into program or document folder and retrive in offline mode)
this is my code for showing images in uicolleciotn view :
if (![appRecord.thumb_url isEqualToString:#""])
{
[myCell.imageView setImageWithURL:[NSURL URLWithString:appRecord.thumb_url] placeholderImage:[UIImage imageNamed:#"white.png"]];
__weak UIImageView *weakImageView = myCell.imageView;
NSURLRequest *request1 = [NSURLRequest requestWithURL:[NSURL URLWithString:appRecord.thumb_url]];
[myCell.imageView setImageWithURLRequest:request1
placeholderImage:[UIImage imageNamed:#"white.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
UIImageView *strongImageView = weakImageView; // make local strong reference to protect against race conditions
if (!strongImageView) return;
[UIView transitionWithView:strongImageView
duration:0.3
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
strongImageView.image = image;
}
completion:NULL];
}
failure:NULL];
}
after changing the code due to luvacu suggestion :
adding this to header :
#property (nonatomic, strong) NSURLRequest *strongRequest1;
and :
__block UIImageView *weakImageView = myCell.imageView;
NSURLRequest *request1 = [NSURLRequest requestWithURL:[NSURL URLWithString:appRecord.thumb_url]];
__block NSURLRequest *weakRequest1 = request1;
[myCell.imageView setImageWithURLRequest:request1
placeholderImage:[UIImage imageNamed:#"white.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
UIImageView *strongImageView = weakImageView; // make local strong reference to protect against race conditions
if (!strongImageView) {
return;
}
[UIView transitionWithView:strongImageView
duration:0.3
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
strongImageView.image = image;
}
completion:NULL];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSURLRequest *strongRequest = weakRequest1;
UIImageView *strongImageView = weakImageView;
UIImage *cachedImage = [[UIImageView sharedImageCache] cachedImageForRequest:strongRequest];
if (cachedImage) {
[UIView transitionWithView:strongImageView
duration:0.3
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
strongImageView.image = cachedImage;
}
completion:NULL];
}
}];
still no iamge apear after making program offline.
AFNetworking's category UIImageView+AFNetworking includes a NSCache for each image URL request. Try to load a cached image for that request, when the request fails.
__block UIImageView *weakImageView = myCell.imageView;
NSURLRequest *request1 = [NSURLRequest requestWithURL:[NSURL URLWithString:appRecord.thumb_url]];
__block NSURLRequest *weakRequest1 = request1;
[myCell.imageView setImageWithURLRequest:request1
placeholderImage:[UIImage imageNamed:#"white.png"]
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
UIImageView *strongImageView = weakImageView; // make local strong reference to protect against race conditions
if (!strongImageView) {
return;
}
[UIView transitionWithView:strongImageView
duration:0.3
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
strongImageView.image = image;
}
completion:NULL];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSURLRequest *strongRequest = weakRequest1;
UIImage *cachedImage = [[UIImageView sharedImageCache] cachedImageForRequest:strongRequest]
if (cachedImage) {
[UIView transitionWithView:strongImageView
duration:0.3
options:UIViewAnimationOptionTransitionFlipFromRight
animations:^{
strongImageView.image = cachedImage;
}
completion:NULL];
}
}];
Also, you are loading the image twice. Remove the first one:
[myCell.imageView setImageWithURL:[NSURL URLWithString:appRecord.thumb_url] placeholderImage:[UIImage imageNamed:#"white.png"]];
When I start a request with the NSURLRequestReturnCacheDataElseLoad policy I expect to get the result from the NSURLCache if any, no matter how old it is. However, the system always tries to reach the server the request points to and returns an error if the server doesn't answer.
I use the UIImageView category of AFNetworking for the request.
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReturnCacheDataElseLoad timeoutInterval:60.0];
__weak __typeof(self) weakSelf = self;
NSLog(#"[%#] %#", request.URL, [[NSURLCache sharedURLCache] cachedResponseForRequest:request]); // this returns an instance of NSCachedURLResponse!
[self setImageWithURLRequest:request placeholderImage:nil success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
__typeof(weakSelf) self = weakSelf;
self.image = image;
} failure:NULL];
This will not set the image even if asking the NSURLCache directly will return a valid NSCachedURLResponse.
The app is running on iOS6 only, so there should be no problems with on-disk cache as far as I know?!
Any help is appreciated! Thanks!
This is a know issue. Please refer this discussion on AFNetworking github page for a workaround.
How do you download images in order with AFNetworking? An by "in order", I also mean executing the success blocks in order.
Initially I thought it would be enough to use a NSOperationQueue and set each AFImageRequestOperation as a dependency of the next one. Like this:
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
_images = [NSMutableArray array];
}
AFImageRequestOperation *previousOperation = nil;
for (NSInteger i = 0; i < _imageURLs.count; i++) {
NSURL *URL = [_imageURLs objectAtIndex:i];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[_images addObject:image];
NSLog(#"%d", i);
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
if (previousOperation) {
[operation addDependency:previousOperation];
}
previousOperation = operation;
[_downloadQueue addOperation:operation];
}
}
This prints i in order when the images are downloaded. However, when the requests are already cached, the success blocks are processed out of order. I suspect this is a NSOperation limitation, not AFNetworking.
Am I missing something?
As a workaround, I store the images in a dictionary and process them in order each time a request succeeds. Like this:
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
_images = [NSMutableArray array];
_imageDictionary = [NSMutableDictionary dictionary];
}
AFImageRequestOperation *previousOperation = nil;
for (NSInteger i = 0; i < _imageURLs.count; i++) {
NSURL *URL = [_imageURLs objectAtIndex:i];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
AFImageRequestOperation *operation = [AFImageRequestOperation
imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
[_imageDictionary setObject:image forKey:#(i)];
[self processImages];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {}];
if (previousOperation) {
[operation addDependency:previousOperation];
}
previousOperation = operation;
[_downloadQueue addOperation:operation];
}
}
- (void) processImages
{
for (NSInteger i = _images.count; i < _imageURLs.count; i++) {
UIImage *image = [_imageDictionary objectForKey:#(i)];
if (!image) break;
[_images addObject:image];
NSLog(#"%d", i);
}
}
This always prints i in order.
Your solution will work fine, here is another way to do it:
For the "perfect" UX you should issue all operations in parallel and process images by order as they come (don't wait if you don't have to).
(error handling is done differently here)
You could try this (untested, and you can better design the model [don't just use arrays like this]):
- (void) processImage:(UIImage*)image
{
//do something with the image or just [_images addObject:image]
}
- (void) downloadImages
{
{ // Reset
[_downloadQueue cancelAllOperations];
_downloadQueue = [[NSOperationQueue alloc] init];
}
__block NSMutableArray* queue = [[NSMutableArray alloc] initWithCapacity:[_imageURLs count]];
for (NSURL* url in _imageURLs) {
__block NSLock* lock = [[NSLock alloc] init];
__block NSMutableArray* container = [NSMutableArray new];
[lock lock];
[queue addObject:#[lock,container,url]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
void(^compBlock)(NSURLRequest *request,
NSHTTPURLResponse *response,
UIImage *image) = ^(NSURLRequest *request,
NSHTTPURLResponse *response,
UIImage *image)
{
[container addObject:image];
[lock unlock];
};
NSOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:compBlock
failure:compBlock];
[_downloadQueue addOperation:operation];
}
__block __weak id weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (NSArray* arr in queue) {
NSLock* lock = arr[0];
[lock lock];
NSMutableArray* container = arr[1];
if ([container count]) {
[weakSelf processImage:container[0]]; //might want to call this on main thread
} else {
//error on url = arr[2]
}
[lock unlock];
}
});
}
I just recently switched to AFNetworking to handle all my networking within my app. However, it now appears to be blocking the main thread so my MBProgressHUD won't spin until after the operation finishes and my pullToRefreshView will also not animate until after the operation. How would I fix this?
- (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; {
// Call the refreshData method to update the table
[dataController refreshData];
}
- (void)refreshData {
NSURLRequest *request = [NSURLRequest requestWithURL:[FCDataController parserURL]];
NSLog(#"URL = %#", request);
AFXMLRequestOperation *operation = [AFXMLRequestOperation XMLParserRequestOperationWithRequest:request
success:^(NSURLRequest *request, NSHTTPURLResponse *response, NSXMLParser *XMLParser) {
_calls = [[NSMutableArray alloc] init];
XMLParser.delegate = self;
[XMLParser parse];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, NSXMLParser *XMLParser) {
if ([delegate respondsToSelector:#selector(refreshDataDidFailWithError:)]) {
[delegate refreshDataDidFailWithError:error];
}
}];
[operation start];
}
By default, AFNetworking calls the success/failure blocks on the main thread (after the network operation runs on a background thread). This is a convenience for the common case where your code just needs to update the UI. If you need to do some more complex operation with the results (like parsing a big XML document), then you can specify some other dispatch queue on which your callback should be run. See the documentation for more.
Update (11 Feb 2016): AFNetworking has changed quite a bit in the nearly three years since I posted this answer: AFHTTPRequestOperation doesn't exist any more in the current version (3.0.4). I've updated the link so it's not broken, but the way you'd accomplish something similar these days is likely quite different.
Where is the MBProgressHUD being called? Are you using SSPullToRefresh or some other implementation. I'm writing very similar code on a current project and its working great.
- (BOOL)pullToRefreshViewShouldStartLoading:(SSPullToRefreshView *)view {
return YES;
}
- (void)pullToRefreshViewDidStartLoading:(SSPullToRefreshView *)view {
[self refresh];
}
- (void)refresh {
NSURL* url = [NSURL URLWithString:#"some_url_here"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
AFJSONRequestOperation* operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) {
// consume response
[_pullToRefreshView finishLoading];
[self.tableView reloadData];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error, id JSON) {
}];
[operation start];
My guess is that - (void)pullToRefreshViewShouldRefresh:(PullToRefreshView *)view; { is being called from a background thread.