Reducing lag when downloading large amount of data from webpage - objective-c

I am getting data via RSS feeds and displaying each article in a table view cell. Each cell has an image view, set to a default image. If the page has an image, the image is to be replaced with the image from the article. As of now, each cell downloads the source code from the web page, causing the app to lag when I push the view controller and when I try scrolling.
Here is what I have in the cellForRowAtIndexPath: method.
NSString * storyLink = [[stories objectAtIndex: storyIndex] objectForKey: #"link"];
storyLink = [storyLink stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *sourceCode = [NSString stringWithContentsOfURL:[NSURL URLWithString:storyLink] encoding:NSUTF8StringEncoding error:&error];
NSString *startPt = #"instant-gallery";
NSString *startPt2 = #"<img src=\"";
if ([sourceCode rangeOfString:startPt].length != 0) { //webpage has images
// find the first "<img src=...>" tag starting from "instant-gallery"
NSString *trimmedSource = [sourceCode substringFromIndex:NSMaxRange([sourceCode rangeOfString:startPt])];
trimmedSource = [trimmedSource substringFromIndex:NSMaxRange([trimmedSource rangeOfString:startPt2])];
trimmedSource = [trimmedSource substringToIndex:[trimmedSource rangeOfString:#"\""].location];
NSURL *url = [NSURL URLWithString:trimmedSource];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
cell.picture.image = image;
Someone suggested using NSOperationQueue. Would this way be a good solution?
EDIT:
dispatch_queue_t someQueue = dispatch_queue_create("cell background queue", NULL);
dispatch_async(someQueue, ^(void){
NSError *error = nil;
NSString * storyLink = [[stories objectAtIndex: storyIndex] objectForKey: #"link"];
storyLink = [storyLink stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSString *sourceCode = [NSString stringWithContentsOfURL:[NSURL URLWithString:storyLink] encoding:NSUTF8StringEncoding error:&error];
NSString *startPt = #"instant-gallery";
NSString *startPt2 = #"<img src=\"";
if ([sourceCode rangeOfString:startPt].length != 0) { //webpage has images
// find the first "<img src=...>" tag starting from "instant-gallery"
NSString *trimmedSource = [sourceCode substringFromIndex:NSMaxRange([sourceCode rangeOfString:startPt])];
trimmedSource = [trimmedSource substringFromIndex:NSMaxRange([trimmedSource rangeOfString:startPt2])];
trimmedSource = [trimmedSource substringToIndex:[trimmedSource rangeOfString:#"\""].location];
NSURL *url = [NSURL URLWithString:trimmedSource];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^(void){
cell.picture.image = image;
});
});
dispatch_release(someQueue);
return cell;
}

For whatever that might be causing the lag, put it in the background thread. When you are ready to update your UI, update it on the main thread. Maybe you can give GCD at try -
dispatch_queue_t someQueue = dispatch_queue_create("cell background queue", NULL);
dispatch_async(someQueue, ^(void){
NSURL *url = [NSURL URLWithString:trimmedSource];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *image = [UIImage imageWithData:data];
dispatch_async(dispatch_get_main_queue(), ^(void){
cell.picture.image = image;
});
});
dispatch_release(someQueue);
EDIT:
When you have some process that might slow down your app, you want to put it in a background queue. To put them in background queue, you use dispatch_async:
dispatch_aysnc(someQueue, ^(void){
});
where someQueue is the background queue you created and want work to be done. However, anything related to the UI needs to be on the main queue, or else some funky stuff can happen. That is why within that dispatch_async, we put another dispatch_async. This time, we specify the UI (cell.picture.image) needs to be updated on the main queue: dispatch_get_main_queue().
Hope this clears it up a bit.

Related

How to display RSS feed images in DetailViewController IOS?

I am displaying cinema gossips in UITableview then select the cell its displays Detailviewcontroller . Successfully Get the data in UITableview, In DetailviewController UIImageview and UITextview, Get the Description Successfully but Images not displayed. This is my code please check once's
This is URL NSURL *url = [NSURL URLWithString:#"http://www.cinejosh.com/rss-feed/3/andhra-latest-gossips-news.html"];
This is my code. IS any problem in my code. please check once's
- (void)loadingImageUrl
{
NSData *gossipsHtmlData = [NSData dataWithContentsOfURL:gossipsUrl];
TFHpple *gossipsParser = [TFHpple hppleWithHTMLData:gossipsHtmlData];
NSString *gossipsXpathQueryString = #"//td//img";
imageGossipsNodes = [gossipsParser searchWithXPathQuery:gossipsXpathQueryString];
for (TFHppleElement *element in imageGossipsNodes) {
NSString *imageUrlString = [NSString stringWithFormat:#"http://www.cinejosh.com%#", [element objectForKey:#"src"]];
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:imageUrlString]];
imageview.image = [UIImage imageWithData:imageData];
}
}
Its doesn't call for loop.

How to retrieve images from server asynchronously

i have one NSMutableArray with some image url's. The images have sizes between 12KB to 6MB. I use AsycImageView class and implement but when large images are downloading application get crashed, I gave 6*1024*1024 (6MB) for maxsize in that class, increase time interval 60.0 sec to 180.o sec, but there is no use. I'm getting an error "Received memory warning" and when app crash automatically connection remove from device, but in simulator there is no crash.
Use GCD for lazy loading.
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^{
NSString *strURL = url here;
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
UIImage *image = nil;
if(data)
image = [UIImage imageWithData:data];
dispatch_sync(dispatch_get_main_queue(), ^{
//now use image in image View or anywhere according to your requirement.
if(image)
yourImgView = image
});
});
you can do this using multiThreading. Here is a code
- (UIImageView *)getImageFromURL:(NSDictionary *)dict
{
#ifdef DEBUG
NSLog(#"dict:%#", dict);
#endif
UIImageView *_cellImage = nil;
_cellImage = ((UIImageView *)[dict objectForKey:#"image"]);
NSString *strURL = [dict objectForKey:#"imageurl"]);
NSData *data = [NSData dataWithContentsOfURL:[NSURL URLWithString:strURL]];
#ifdef DEBUG
NSLog(#"%i", data.length);
#endif
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *dataFilePath = [NSString stringWithFormat:#"%#.png", [documentsDirectory stringByAppendingPathComponent:[dict objectForKey:#"imageid"]]];
if (data) // i.e. file exist on the server
{
[data writeToFile:dataFilePath atomically:YES];
_cellImage.image = [UIImage imageWithContentsOfFile:dataFilePath];
}
else // otherwise show a default image.
{
_cellImage.image = [UIImage imageNamed:#"nouser.jpg"];
}
return _cellImage;
}
And call this method in cellForRowAtIndexPath like this:
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:imageURL, #"imageurl", self.imgPhoto, #"image", imageid, #"imageid", nil];
[NSThread detachNewThreadSelector:#selector(getImageFromURL:) toTarget:self withObject:dict];
The code will start getting images in multiple threads and will save image locally to document folder. Also the image will not download again if already exists with the same name. Hope this helps
You could download image asynchronously using GCD. Use the following code,
__block NSData *imageData;
dispatch_queue_t myQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, NULL);
dispatch_async(myQueue, ^{
//load url image into NSData
imageData = [NSData dataWithContentsOfURL: your_image_URL];
if(imageData) {
dispatch_sync(dispatch_get_main_queue(), ^{
//convert data into image after completion
UIImage *img = [UIImage imageWithData:imageData];
//do what you want to do with your image
});
} else {
NSLog(#"image not found at %#", your_image_URL);
}
});
dispatch_release(myQueue);
For further info, see dispatch_queue_t
I would recommend a drop in replacement API SDWebImage it provides a category for UIImageVIew with support for remote images coming from the web. you can also have a placeholder image till the images are downloaded Asynchronously
. Its easy to use and saves a lot of work
You're problem seems to be more of a memory usage issue than it is a performance issue.
If you really want to download image asynchronously I would recommend you use The UIImageView category from AFNetworking which has been fully tested and is very well maintained.
However here you run into memory warnings on your device (which obviously holds much less memory than your simulator which runs on your Mac).
So I would use first the static analyzer:
to see if leaks are present and then run a Leaks Instrument to track it down.
Hope this helps.

defaultRepresentation fullScreenImage on ALAsset does not return full screen image

In my application I save images to an album as assets. I want also to retrieve them and display them in full screen. I use the following code :
ALAsset *lastPicture = [scrollArray objectAtIndex:iAsset];
ALAssetRepresentation *defaultRep = [lastPicture defaultRepresentation];
UIImage *image = [UIImage imageWithCGImage:[defaultRep fullScreenImage]
scale:[defaultRep scale] orientation:
(UIImageOrientation)[defaultRep orientation]];
The problem is that the image returned is nil. I have read at the ALAssetRepresentation reference that when the image does not fit it is returned nil.
I put this image to an UIImageView which has the size of the iPad screen. I was wondering if you could help me with this issue?
Thank you in advance.
I'm not a fan of fullScreenImage or fullResolutionImage. I found that when you do this on multiple assets in a queue, even if you release the UIImage immediately, memory usage will increase dramatically while it shouldn't. Also when using fullScreenImage or fullResolutionImage, the UIImage returned is still compressed, meaning that it will be decompressed before being drawn for the first time, thus on the main thread which will block your UI.
I prefer to use this method.
-(UIImage *)fullSizeImageForAssetRepresentation:(ALAssetRepresentation *)assetRepresentation
{
UIImage *result = nil;
NSData *data = nil;
uint8_t *buffer = (uint8_t *)malloc(sizeof(uint8_t)*[assetRepresentation size]);
if (buffer != NULL) {
NSError *error = nil;
NSUInteger bytesRead = [assetRepresentation getBytes:buffer fromOffset:0 length:[assetRepresentation size] error:&error];
data = [NSData dataWithBytes:buffer length:bytesRead];
free(buffer);
}
if ([data length])
{
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
NSMutableDictionary *options = [NSMutableDictionary dictionary];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceShouldAllowFloat];
[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailFromImageAlways];
[options setObject:(id)[NSNumber numberWithFloat:640.0f] forKey:(id)kCGImageSourceThumbnailMaxPixelSize];
//[options setObject:(id)kCFBooleanTrue forKey:(id)kCGImageSourceCreateThumbnailWithTransform];
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
if (imageRef) {
result = [UIImage imageWithCGImage:imageRef scale:[assetRepresentation scale] orientation:(UIImageOrientation)[assetRepresentation orientation]];
CGImageRelease(imageRef);
}
if (sourceRef)
CFRelease(sourceRef);
}
return result;
}
You can use it like this:
// Get the full image in a background thread
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
UIImage* image = [self fullSizeImageForAssetRepresentation:asset.defaultRepresentation];
dispatch_async(dispatch_get_main_queue(), ^{
// Do something with the UIImage
});
});

Cacheing UIImage as NSData and getting it back

I'm trying to cache images I load from Flickr. If I load the same image, I'm hoping to use the cached version instead. For some reason, when I download an image from the internet, it works, but if I load it from my cache, it displays a blank image. I checked cacheData, and it has the same amount of bits as the image I put in, so it appears loading the file is working.
Here is how I cache images:
+ (void)cachePhoto:(NSData *)photo withKey:(NSString *)key {
if (photo) {
NSArray * urlArray = [fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask];
NSURL * targetDirectory = (NSURL *)[urlArray objectAtIndex:0];
targetDirectory = [targetDirectory URLByAppendingPathComponent:key];
[photo writeToURL:targetDirectory atomically:YES];
[cachedPhotos addObject:key];
NSLog(#"target url %#", targetDirectory);
}
}
+ (NSData *)photoInCache:(NSString *)key {
if ([cachedPhotos containsObject:key]) {
NSString * path = [[cacheDirectory URLByAppendingPathComponent:key] path];
NSLog(#"path: %#", path);
return [fileManager contentsAtPath:path];
} else {
return nil;
}
}
And my code to get it back:
NSData * cacheData = [PhotoCache photoInCache:key];
if (cacheData) {
self.imageView.image = [UIImage imageWithData:cacheData];
[spinner stopAnimating];
NSLog(#"used cached image");
} else {
dispatch_queue_t downloadQueue = dispatch_queue_create("get photo from flickr", NULL);
dispatch_async(downloadQueue, ^{
NSData * imageData = [[NSData alloc] initWithContentsOfURL:url];
dispatch_async(dispatch_get_main_queue(), ^{
[spinner stopAnimating];
self.imageView.image = [UIImage imageWithData:imageData];
[PhotoCache cachePhoto:imageData
withKey:key];
});
});
}
I figured it out - I was loading the image into the imageView in my prepareForSegue, where the ImageView was not yet loaded. I loaded the image in viewDidLoad and it worked. I also had to use UIImagePNGRepresentation, like suggested in the comments.

Memory leak issue while downloading large number of images

I am trying to downloading more then 600 images in loop with a progress meter on the top of the screen to the user. I blocked my screen with a fade layer for showing activity and progress.
I am getting the memory warning message in between and app getting crashes.
My steps to reach the loop are :
On app delegate, I check first core data table for all rows which is having "0" value in isImageAvailable bool field.
If shows me some count (say 600), and I show and alert with YES and NO option.
On YES : [self performSelector:#selector(myDownload:) withObject:nil afterDelay:0.2];
in myDownload
NSOperationQueue *queue = [NSOperationQueue new];
// Create our NSInvocationOperation to call loadDataWithOperation, passing in nil
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(startUpdatingRecords:) object:nil];
// Add the operation to the queue
[queue addOperation:operation];
[operation release];
[queue release];
in startUpdatingRecords :
-(void)startUpdatingRecords:(id)sender
{
[self performSelectorInBackground:#selector(updateProgressMeter:) withObject: [NSString stringWithFormat:#"%d",self.loopStartIndex]];
// Variable declarations
CGSize newSizeLarge ;
NSPredicate *predicate;
NSMutableArray *MatchingID;
Image_DB *data;
// Cache Directory path
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSData *responseData; // = [[NSData alloc]init] ;
NSURL *url = [[[NSURL alloc]init] autorelease];
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc]init] autorelease];
UIImage *imgSelected_Large = [[[UIImage alloc]init] autorelease];
// Loop through all IDs
for (int i = 0; i < [self.arrayOfID count]; i++) //for (int i = loopStart; i < loopEnd; i++)
{
if (self.abortDownload)
{
break;
}
NSString *documentsDirectory = [[[NSString alloc] initWithFormat:#"%#",[paths objectAtIndex:0]] autorelease];
documentsDirectory = [paths objectAtIndex:0];
documentsDirectory = [documentsDirectory stringByAppendingFormat:#"/ImageFolder"]; // Image folder path
myClass *classObj = [self.arrayOfID objectAtIndex:i];
NSString *strURl = [[[NSString alloc] initWithFormat:#"%#%#", self.MyURL,recipeObj.recipeImageStr] autorelease];
//NSLog(#"URL = %#",strURl);
url = [NSURL URLWithString:strURl];
request = [NSMutableURLRequest requestWithURL:url];
responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:NULL error:NULL]; // Get Image Data into NSData
//imgSelected_Large = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:strURl]]];
NSLog(#"Download Count = %d",i+1);
if (responseData != nil)
{
imgSelected_Large = [UIImage imageWithData:responseData];
// Resizining image
newSizeLarge.width = 320;
newSizeLarge.height = 180;
imgSelected_Large = [self imageWithImage:imgSelected_Large scaledToSize:newSizeLarge]; // New sized image
NSData *dataPhoto; // no need to release it because UIImageJPEGRepresentation gives autoreleased NSData obj.
dataPhoto = UIImageJPEGRepresentation(imgSelected_Large, 0.6); // Set new image representation and its Compression Quality
documentsDirectory = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:#"Image_%d", classObj.nodeID]];
[dataPhoto writeToFile:documentsDirectory atomically:YES]; //Write file to local folder at default path
predicate = [NSPredicate predicateWithFormat: #"(image_ID = %d )",recipeObj.nodeID];
MatchingID = [CoreDataAPIMethods searchObjectsInContext:#"Image_DB" :predicate :#"image_ID" :YES :self.managedObjectContext];
// Setting flag variable for available image
for (int j = 0; j< [MatchingID count]; j++)
{
//Assign the Authors Records in Class Object and save to Database
data = (Image_DB*) [MatchingID objectAtIndex:j];
// data.image_large = dataPhoto; // Code for storing BLOB object to DB
data.extra_1 = #"1";
//NSLog(#"Flag updated");
}
}
// Exit out code
if ( i == [self.arrayOfID count] - 1 || i == [self.arrayOfID count]) // Its the last record to be stored
{
NSError *error;
if (![self.managedObjectContext save:&error])
{
// Handle the error...
NSLog(#"Error in updating %#",error);
}
self.isUpdateImageCalled = NO;
[self performSelectorOnMainThread:#selector(removeProgressMeter) withObject:nil waitUntilDone:NO];
}
// Update UI screen while in downloading process
[self performSelectorInBackground:#selector(updateProgressMeter:) withObject:[NSString stringWithFormat:#"%d",self.loopStartIndex+i+1]];
}
}
If I didn't release responseData then my app shows me memory warning and got crashed. If I released then, [NSConcreteMutableData release]: message sent to deallocated instance 0x1e931de0 error occures.
How to refine my code. Can any one suggest me on my code and rework and make a refined code.
Please please help me out.
Your responseData returned by sendSynchronousRequest is autoreleased thus you shouldn't release it yourself. For the first sight I don't see a memory leak in your code. It is possible that your application actually uses too much memory, without leaking it. Try to place an autorelease pool inside your for cycle:
for (...) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// your original code with a lot of autoreleased objects
[pool release];
}
If you wrap your code within an autorelease pool, all objects that are sent the autorelease message inside the wrap will be actually released when the pool itself is released: this way you purge the memory in every for cycle.
See also Using Autorelease Pools in the doc, it specifically mentions that you should use them in the case "if you write a loop that creates many temporary objects".