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?
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'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.
I have an iOS app in which there is a collection view that that can have up to a couple hundred cells in it. Each cell has 5 views in it, 4 UILabels and 1 UIImageView. When I run the app normally the app uses absurd amounts of memory whenever I scroll through a couples of rows. As in about 5Mb of memory for 3 rows. I tried removing all the code in the cellForItemAtIndexPath such that the collection view controller looked like this:
#import "CollectionView.h"
#implementation CollectionView
- (NSInteger)collectionView:(UICollectionView *)view numberOfItemsInSection:(NSInteger)section {
return 100;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)cv cellForItemAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Story_Cell_Small";
Cell *cell = [cv dequeueReusableCellWithReuseIdentifier:CellIdentifier forIndexPath:indexPath];
return cell;
}
#end
Yet the app still uses absurd amounts of data upon scrolling. When I opened the storyboard view for the cell and removed all the views in the cell and again measured my apps memory usage in Instruments there was no memory usage upon scrolling through the empty cells. I could see that were in fact cells based on the scroll bar moving.
So the point is that the views in the cells are using up huge amounts of memory without me doing anything accept placing them in the cell in the apps storyboard.
My question is then, how can I fix this? Am I doing something absurdly wrong?
How big are the images in your cells? Images are what eat memory up. How are you loading the images? Using imageNamed: will cache the images in memory. If you use imageWithContentsOfFile:, this will not cache the contents.
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.
I have a UITableView with custom cells that were defined in the xib file, and am experiencing poor scrolling performance (choppy) on my device when the cells have a UISegmentedControl on them. NSLog statements reveal that the cells are being allocated and reused as they ought. My code for cellForRowAtIndexPath method is below. Connections are made in the xib as per Apple's documentation. (Scrolls smoothly in simulator btw)
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
[[NSBundle mainBundle] loadNibNamed:#"TableViewCell"
owner:self
options:nil];
cell = self.tvCell;
self.tvCell = nil;
}
cell.layer.shouldRasterize = YES; // build error is here
UILabel *lbl = (UILabel *)[cell viewWithTag:1];
[lbl setText:[NSString stringWithFormat:#"Q%i", indexPath.row+1]];
return cell;
}
Any drawing that a table cell has to do while it's being scrolled is going to cause performance issues; when you have a lot of subviews, there tends to be a lot of drawing going on, and that will—as you've observed—make your scrolling pretty choppy. There are a couple of ways to try to reduce that.
The first step is to make sure that your cells themselves, and as many of their subviews as possible, have their opaque properties set to YES. Opaque views don't have to get blended with the content underneath them, and that saves a lot of time.
You may also want to set your cells' layers to rasterize themselves, like this:
cell.layer.shouldRasterize = YES;
cell.layer.rasterizationScale = [UIScreen mainScreen].scale;
This will collapse your view hierarchy into one flat bitmap, which is the kind of thing Core Animation just loves to draw. Note that any animating views—activity indicators, for instance—will force that bitmap to be updated every time they change, i.e. a lot. In that case, you won't want the cell to rasterize everything; you might just use a subview with all of your relatively static views (e.g. labels) beneath another subview with any such dynamic content, and only have the first of those rasterized.
Make sure your identifier is 'MyIdentifier' in the xib. You'll get a good performance hit if it's not. I'm guessing that 'allocated and reused as they ought' means a few allocated on startup and no more allocated after. If that's true then you're probably all set.
Another way to improve performance is to construct your table view with code. It's a good deal faster than using xib's. When I construct table views I usually build them in IB, then copy over the frame values into code and construct in code.
Set aside some time to watch the WWDC 2010 performance video's. A lot of great information, I learn something new each time I watch them.