I'm building a UItableView with custom cells that displays images. The problem is that as I scroll down, the previous cells does not seem to do a dealloc and the device memory is filling up.
This is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
CustomTableCellView *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[CustomTableCellView alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:simpleTableIdentifier];
}
Article *tempArticle = [_objects objectAtIndex:indexPath.row];
cell.titel.text = tempArticle.title;
cell.subTitle.text = tempArticle.subTitle;
cell.category.text = tempArticle.category;
if(tempArticle.imageUrl){
[cell.image setImageWithURL:[NSURL URLWithString:tempArticle.imageUrl]];
} else {
cell.image.image = [UIImage imageNamed:#"video"];
}
return cell;
}
I'm 99% sure that the memory increase is due to the image loading & caching. The AFnetworking it's using a NSCache instance to cache the downloaded images, this cache is performed in memory and it will be drained when the system decide it has low memory, this is not an issue.
Also the imageNamed method caches the images that are loaded in the same way as AFNetworking.
In order to check if you have a leak or something is not working properly with your data, check if the memory increases after you scroll back up, if the memory stays the same, than you don't have a problem nor a leak (also you can check leaks with instruments), the cached images are the ones that cause the increase of memory.
Also remove the view controller (pop, remove) from the stack, this will cause some deallocations on the text, labels or other data (not images), if the memory decrease (it won't be a substantial decrease) then you are on the right way.
As stated above, you are setting the reuse identifier correctly.
Maybe it's filling the memory because you're using AFNetworking library and downloading the images for your cells? Define "filling up the memory" more exactly please.
Related
Hello colleagues Apple developers!
I am creating an iOS application for both iPhone & iPad. The performance of UITableView and UICollectionView is very important feature in my application.
After long time spent for optimising, human eye can't tell the difference while scroll happens. Although, profiler still finds some issues.
The first inefficient thing is dequeuing. I have a UITableViewCell object, which user interface is created by using .xib files and auto layout constraints. Although, the time profiler instrument complains about the performance while dequeuing this specific cell.
One more problem I can't understand is a NSString setting as UILabel's text. This setter method is executed in method - tableView:cellForRowAtIndexPath:.
One more problem, that might be related with previous one, is a UIImage setting as UIImageView's image. This method is also executed in method - tableView:cellForRowAtIndexPath: and doesn't trigger any download or etc.
UITableViewCell objects, described above, are default size & really simple. The content view of the cell contains only 2 subviews: UILabel and UIImageView.
Images are downloaded according to Apple's example.
// Asks the data source for a cell to insert in a particular location of the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
// Dequeues & initializes category table view cell.
CategoryTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CategoryTableViewCellIdentifier];
APICategory *category = [self.categories objectAtIndex:indexPath.row];
if (category.thumbnail)
{
[cell updateThumbnailWithCategory:category];
}
else
{
if (!tableView.dragging && !tableView.decelerating)
{
[category thumbnailWithSuccess:^(UIImage *thumbnail)
{
if ([tableView.visibleCells containsObject:cell])
{
[cell updateThumbnailWithCategory:category];
}
}
failure:^(NSError *error)
{
// Handle thumbnail receive failure here!
}];
}
[cell updateThumbnailWithPlaceholder];
}
cell.category = [self.categories objectAtIndex:indexPath.row];
return cell;
}
Specific images used in this case are .png images 300x300 resolution and 24 kb of size.
These are the problems related with the performance of UITableView and UICollectionView.
Can anyone explain me, what could be the reasons for any of those issues?
Also, is there a way to improve it?
I want to show some images that user saved in his Document directory in a uitableview. I use below simple code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"uploadHistoryCellfa";
BRUploadHistoryCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[BRUploadHistoryCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
//Some Other assignemt to UILables Here...
[cell.imagePreview setImage:[UIImage imageWithContentsOfFile:myImagePathInDocument]];
//Some other configurations here ....
return cell;
}
my question is that:
The images size is about 11-25 MB. and I will load one image at each cell. Does this code that I use potentially can cause memory leak or some thing like this?
11-25 MB is large file actually if you have UITableView you will have more then 5 images so it will become 11-25 (*5) so you should resize images while adding them on tableView, but if you will resize it in proccess of showing it will slow up your app. So you first step is to save resized image which will size of max 1 mb.
Second Step is to generate thumbnail and after that add thumbnail image on cell.
First Link: Resize Image
Second Link: Image Resizing Techniques
imageIO for my practice is fastest way to resize image because it uses CPU and user will not see slow scrolling problem.
After all steps done do not forget to use foreground thread and main thread for representing images on cell.
If only 1-2 cells are shown in screen at a time then it won't cause you much trouble.
As TableView will update the contents of already created cells with new Images. (Deque Reusable Concept)
But the better Approach will be to resize the Images first and then use.
I'm getting an unusual and constant build up of virtual memory as I navigate through my app, which is leading to boggy performance and eventually a memory pressure crash. App physical memory never goes past 10mb to 20mb, virtual memory however is peaking in the 200mb to 300mb range at the time of crash.
I'm using ARC.
Basically, I'm thinking the problem is the approach I've taken to my cellForRowAtIndexPath / itemForRowAtIndexPath methods, as the app is very intensely based on content provided through tableviews and collection views.
Basically, what I'm doing is dequeueing cells registered to custom XIB files, that do not have a class file (Which I think is the problem) and then referencing the objects of those dequeued cells through tags rather than them as a class and accessing their properties.
From my code, and from what I'm gathered, I'm assuming any time my collection views or tables reload, it's basically created additional cells where it doesn't need to as cells have already been created, so it's just overlaying the same content on the same cells? That or using UILabel *name = (UILabel *)etc.. is increasing the reference count in places it shouldn't be, which is causing memory usage to skyrocket as objects aren't being deallocated?
Here's a snippet of code from one of the most intensive portions of the app for creating items, is my logic / flow causing my virtual memory problems?
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:PRODUCTS_FEED_CELL_IDENTIFIER forIndexPath:indexPath];
NSDictionary *product = [self.productsFeedItems objectAtIndex:indexPath.row];
UIImageView *productThumbnailView = (UIImageView *)[cell viewWithTag:PRODUCTS_FEED_COLLECTION_ITEM_TAG_THUMBNAIL];
[productThumbnailView setImageWithURL:[APIController resourceUrlForUserId:[product objectForKey:#"userId"] resourceName:[product objectForKey:#"thumbnail"]]];
UILabel *productPriceLabel = (UILabel *)[cell viewWithTag:PRODUCTS_FEED_COLLECTION_ITEM_TAG_PRICE];
productPriceLabel.text = [NSString stringWithFormat:#"$%#", [product objectForKey:#"price"]];
NSString *likeImage = ([[product objectForKey:#"liked"] isEqualToString:#"1"]) ? #"feedLikeSelected.png" : #"feedLikeUnselected.png";
UIButton *productLikeButton = (UIButton *)[cell viewWithTag:PRODUCTS_FEED_COLLECTION_ITEM_TAG_LIKE];
[productLikeButton setImage:[UIImage imageNamed:likeImage] forState:UIControlStateNormal];
return cell;
}
Heap Allocations & Instruments Screenshot
The problem could come from the fact that the images you are trying to get from the server are huge. Check the sizes of the images you are setting with setImageWithURL:. Also, are you caching those images?
I'm using the setImageWithURL to lazily load images into my tableview cells but there is a problem. It loads the images fine but there seems to be a refresh problem on the device where the images only show when you scroll them out of the view and back in again.
It works perfectly in the simulator. The device is running iOS 5.1.1 on a iPhone 4S
This table is displayed in a UINavigation controller and as the images are cached I expect the images to appear pretty quickly when I revisit the screen. They do, but the show up at half their original size.
The images are 60x30 in size.
Is this an issue with loading images into a retina screen that they are half the size and what would cause this refresh problem?
Code snippet below...
- (UITableViewCell *)tableView:(UITableView *)tableview cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableview dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
...
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"transparent_shade"]];
...
return cell;
}
Thanks in advance for any insights
I would suggest loading/preparing the images before the cellForRowAtIndexPath method. The reason you are seeing the images after scrolling is because the images were previously downloading at the time this method was called, hence the files were not yet available.
Make a NSMutableArray inside your view's viewWillAppear method (override it if non-existent) for instance, and put all the images inside the array. Then, in the cellForRowAtIndexPath simply get the images from the array and assign them as needed; they should be displayed on demand, without delays.
Let us know how this works for you.
i have a tableview which has image and a text behind, i create the cells like below code :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
NSDictionary * companyProductRow = [DB getCompanyProductRow:[self.companyProductIDs objectAtIndex:indexPath.row]];
int companyProductID = [[companyProductRow objectForKey:#"ID"] intValue];
cell.tag = companyProductID;
cell.textLabel.text = [companyProductRow objectForKey:#"ImagePath"];
NSString* fullPath = [FileManager fullPath:companyProductID fairDirectory:[[self.currentFair objectForKey:#"ID"]intValue]];
[[cell imageView] setImage:[UIImage imageWithContentsOfFile:fullPath]];
return cell;
}
i read some tips about tableview performance on apple developer site but all of they said is :
Reuse cells. Object allocation has a performance cost, especially if the allocation has to happen repeatedly over a short period—say, when the user scrolls a table view. If you reuse cells instead of allocating new ones, you greatly enhance table-view performance.
Avoid relayout of content. When reusing cells with custom subviews, refrain from laying out those subviews each time the table view requests a cell. Lay out the subviews once, when the cell is created.
Use opaque subviews. When customizing table view cells, make the subviews of the cell opaque, not transparent.
the sample in apple site is the same, but i want to know is there any way to have better performance when scrolling on uitableview? (when we should read image from disk).
so thanks
Yes it is.
The first thing you could enhance is the image loading.
Try to avoid [UIImage imageWithContentsOfFile:fullPath] because it always loads the entirely image into the memory. This in fact is slow. Use [UIImage imageNamed:"YOUR_IMGAES_NAME"] instead because it caches the images after the first use or preload/store them directly in your fileManager.
The next step would be to set all views in the cell to nil (like the imageView) and draw all the contents by hand. The reason for this is that UIViews are pretty slow. If you have a lot of labels, images, etc. to display it happens to be much faster to draw everything by hand.
Your tableView:cellForRowAtIndexPath: looks like it's doing 2 disk reads for each cell, one from your [DB getCompanyProductRow:...] which I assume is a database fetch of some sort, and the second from the [UIImage imageWithContentsOfFile:...].
Find a way to load these information to memory, before tableView:cellForRowAtIndexPath: gets called (preferably before [tableview reloadData]).
Tips for large datasets:
If the data you need to fetch is too big for the memory to be loaded into an array, try to implement some paging mechanism so you just need to display a subset of that data. I would even recommend you use Core Data's NSFetchedResultsController instead, but that's if your database structure is compatible for object models.