I am building a small iPhone only application (iOS SDK 6.0) and I am getting EXC_BAD_ACCESS exception in XCode.
The application has UITableView with custom UITableViewCell. I have implemented the UITableViewCell in a subclass (I call it MyCustomCell here) and added another parameter to the constructor:
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier cellImage:(UIImage *)image;
The UIImage is added to contentView of the TableCell in the method, with following code:
imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 50)];
imageView.image = image;
[self.contentView addSubview:imageView];
This function is called within the function cellForRowAtIndexPath of course:
static NSString *CellIdentifier = #"MyCustomCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier cellImage:self.cellImage] autorelease];
}
return cell;
The cellImage property of the ViewController (also the UITableViewDelegate) is declared as (it is also synthesized):
#property (nonatomic, retain) UIImage* cellImage;
The image is initialized in viewWillAppear method of ViewController with the following line:
cellImage = [UIImage imageNamed:#"my_image_resource.png"];
This code works correctly, but the EXC_BAD_ACCESS appears, when I customize the image with iOS 6.0 only function:
cellImage = [[UIImage imageNamed:#"my_image_resource.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 10, 0, 10) resizingMode:UIImageResizingModeStretch];
Debugging helped me to locate the line that triggers the error - it is in the initWithStyle method of MyCustomCell implementation:
imageView.image = image;
I have looked through many EXC_BAD_ACCESS errors, I also read the tutorial of why does this error appear, but up to this point, I am not deallocating anything. Some object is probably released at this point, but I cannot find which one exactly and how could I fix the problem.
I have also tried creating two properties in ViewController class, one to hold the original image and the other image with insets. It does not solve the problem, however.
Why is this an error? Why it is not working with image with insets? How could I fix this problem?
Thank you.
The error appears when you execute the statement imageView.image = image;. Thus, the object image does not exist when this statement is executed. It must have been released before, either explicitly by a [release image] (since you don't use ARC), or implicitly if image is autoreleased (e.g. if you created it with [UIImage imageNamed:...]).
I think the best were to convert your code ARC, as Aaron Wojnowski suggested. Otherwise, check if image can be released unexpectedly.
You've created a property for cellImage, but you're setting it using the instance variable instead of the setter which has been created for you. Since you're passing in an autoreleased object, there is a possibility that it's been released when you try to reference it next time. Try using this below:
[self setCellImage:[[UIImage imageNamed:#"my_image_resource.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 10, 0, 10) resizingMode:UIImageResizingModeStretch]];
Or with this:
self.cellImage = [[UIImage imageNamed:#"my_image_resource.png"] resizableImageWithCapInsets:UIEdgeInsetsMake(0, 10, 0, 10) resizingMode:UIImageResizingModeStretch];
With this code, the image will be retained for you inside of the setter, and next time you try to use it, the memory will still be allocated and you shouldn't have any issues.
Consider also using ARC, if possible, as much of this intricate memory management stuff is handled for you. http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html
Related
I have to 20-25 download images of 50 Kb- 2 Mb each and show them in a tableview.
I used ASIHTTPRequest asyn request to this. I observed that after some time the app gets stuck. This should not happen because I am using a async call. I thought something is wrong with ASIHTTPRequest and I observed that The didFinished selector gets called in the main thread. The only thing which I do is
-(void)didFinishedDownloadingImage:(ASIHTTPRequest*)request
{
NSData *responseData = [request responseData];
UIImage *image = [UIImage imageWithData:responseData];
[[data objectAtIndex:request.tag] setImage:image];
[self.tableView reloadData];
}
I don't think this should cause any problem. Also in cellforrowatindexpath I just do
- (UItableViewCell *)tableviewView:(UItableView *)tableview
cellForItemAtIndexPath:(NSIndexPath *)indexPath {
UserProfile * user = [data objecAtIndex:indexpath.row];
UITableViewCell *cell = [tableView
dequeueReusableCellWithReuseIdentifier:#"ProfileCell"
forIndexPath:indexPath];
if(cell == nil){
cell = [[UITableViewCell alloc] initWithStyle:UITableViewDefaultStyle];
}
NSString *fullname = [NSString stringWithFormat:#"%#\n%#",
user.firstname, user.lastname];
if(user.image != nil)
[cell.imageView setImage:user.image];
else{
[cell.imageView setImage:[UIImage imageNamed:#"placeholder.jpg"]];
}
[cell.label setText:fullname];
return cell;
}
But the app is slow and freezes for 1-2 sec which is a considerable amount of time.
I have seen apps which does this very smoothly. I tried using an image of fixed size 5Kb which has a very significance performance improvement with using the above code. I don't know why should that make a difference for big images in this case because all downloading is happening in other thread via ASIHTTP .
Please, replace your framework with AFNetworking.
You can simple use..
IImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0.0f, 0.0f, 100.0f, 100.0f)];
[imageView setImageWithURL:[NSURL URLWithString:#"http://i.imgur.com/r4uwx.jpg"] placeholderImage:[UIImage imageNamed:#"placeholder-avatar"]];
or... directly in TableViewCell
NSURL *url = [[NSURL alloc] initWithString:[movie objectForKey:#"artworkUrl100"]];
[cell.imageView setImageWithURL:url placeholderImage:[UIImage imageNamed:#"placeholder"]];
"In the second line, we tell the image view where the thumbnail is located by passing an NSURL and we pass in a placeholder image, which is shown as long as our request has not returned a response"
Thats all!
Here you have an tutorial about that http://mobile.tutsplus.com/tutorials/iphone/ios-sdk_afnetworking/
It's easy to make assumptions about the root cause of a laggy/slow application. Instead of guessing, why don't you test your suspicions? Profile your application with the Time Profiler instrument. It'll tell you which methods and functions your application is spending the most time in.
Here are some ideas until you have a chance to profile:
You might consider downloading the full-res images and creating thumbnails in the background and then caching them in an NSCache object. You can also run [UIImage imageWithData:responseData]; in a background thread. It's thread-safe until the point at which it interacts with the view hierarchy.
Selectively reloading a single cell should be faster than reloading the entire tableview, especially one with lots of images. Furthermore if you're doing all of the networking and processing on a background queue, there's no reason scrolling the tableview should be slow. Can you show us your entire implementation of the -cellForRowAtIndexPath: method? You've mentioned that you think setImage: is your slow point because rendering is slow. If you reload a single cell, only one cell needs to be rendered. If you reload the entire tableview, every cell must be re-rendered.
Happy November to all,
Well I tried Xcode Build and analyze on my project, and it showed some unusual leaks, which I couldn't quite accept with my knowledge of Objective C.
So I decided to put up a test project and ask here..
MemoryTestController.h
#interface MemoryTestController : UIViewController{
UIImageView *tstImageView;
}
#property(nonatomic,retain) UIImageView *tstImageView;
#end
MemoryTestController.m
#implementation MemoryTestController
#synthesize tstImageView;
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release];
}
-(void)dealloc{
[tstImageView release];
[super dealloc];
}
#end
When I try Build and analyze, clang static analyzer say
Potential leak of an object at line xx
And the culprit line is
self.tstImageView = [[UIImageView alloc]initWithFrame:<SomeFrame>];
I think I am releasing once for every time I am allocing/retaining. Am I missing something, or Static analyzer has some bugs?
EDIT : Is there any leak there?
Well I run the above project using Leak tool in instrument..It didn't show any leak even though I tried many times..Whom should I believe? Static analyzer or Leak instrument?
your problem is how you release it:
- (void)viewDidLoad{
[super viewDidLoad];
self.tstImageView = [[UIImageView alloc] //<==This object is leaking
initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
[tstImageView release]; // << here
}
you should do it this way:
- (void)viewDidLoad{
[super viewDidLoad];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
imageView.image = [UIImage imageNamed:#"SomeImage.png"];
self.tstImageView = imageView;
[imageView release];
[self.view addSubview:self.tstImageView];
}
The checker is correct because it cannot assume that the variable is identical to the one you set. Therefore, the form you use in the OP could introduce a reference count imbalance because the ivar's value may not be what you assigned to it by the time you message release upon the ivar.
These cases are not likely for a UIImageView, and quite unlikely in the context of your program, but these examples should give you an idea as to why the checker assumes that object->ivar associations shall not be trusted:
Between creation of the image view and the message to release it via the ivar, you have:
self.tstImageView = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView.image = [UIImage imageNamed:#"SomeImage.png"];
[self.view addSubview:tstImageView];
1) assignment of the image view via the setter
2) access of the image view via the getter
3) direct access of the ivar, when adding to self.view
the setter may have taken a copied or used a cached value. UIImageView is a bad example, but the checker does not know how types are generally passed around - even if it did, it would (at times) make unsafe assumptions.
the simplest example would be:
- (void)setName:(NSString *)inName {
NSString * prev = name;
if (inName == prev) return;
if (0 == [inName count]) name = #"";
else name = [inName copy];
[prev release];
}
the value held by the ivar could change in the meantime. not likely an issue in this case, but let's say that adding the image view as the subview could end up calling back and altering self in the process/effect of adding the subview, and replacing or removing the image view you passed. In that case, the variable view you passed would leak and the view it replaced it with would have a negative imbalance.
Neither of those are likely to happen in your example, but it does happen in real world programs, and the checker is correctly evaluating based on locality, not property (the checker can't assume much of what happens inside a method call). It also encourages one good idiomatic style in this case.
EDIT : Is there any leak there?
Well I run the above project using
Leak tool in instrument..It didn't shown any leak even though I tried
it many times..Whom should I believe? Static analyzer or Leak
instrument?
The static analyzer says there is a potential leak because it is unable to guarantee the reference/allocation it follows is correctly retained/released. You can guarantee that reference counting is correct and please the static analyzer by changing you program to look like I wrote it in my example.
The way you have written it has made it impossible for the analyzer to follow the reference.
If you have no leaks and no zombies, then there is not a leak. But the solution is easy to fix - and programs have a way of changing during development. It's much easier to use the form I posted so it is easier for the toolset and for you to verify the program is correct. The static analyzer is not always correct, but you should adjust your programs to please it because static analysis is very useful. The program I posted is also easier for a human to understand and confirm that it is correct.
when you declare a property with retain like this
#property(nonatomic,retain) UIImageView *tstImageView;
a setter is added that will incr the retainCount when you assign to the property. When you do as below the object you created has already a retainCount == 1
self.tstImageView = [[UIImageView alloc]
initWithFrame:<SomeFrame>];
so the tstImageView object has 2 in retainCount.
do instead
UIImageView* view = [[UIImageView alloc] initWithFrame:<SomeFrame>];
self.tstImageView = view;
[view release];
then, although unrelated to your leak when you release it write like this instead
self.tstImageView = nil;
since the setter will then will properly set the retainCount
UIImage *image = [[UIImage alloc] initWithData:data];
((YKSubCategory *)[_subCategories objectAtIndex:connection.tag - 1]).menuImage = image;
[image release];
Here is the code I've written. The data is exists and the designed image is created. (I've tested it with an imageview and the image appears).
The problem comes when I try to set the menuImage property. It will not be set. I don't know why. So the value of the menuImage property remains nil.
Here is the property definition:
#property (nonatomic, retain) UIImage *menuImage;
What can be the problem?
Edit:
I've divided the code for the request of walkytalky.
Here is the code I've written:
UIImage *image = [[UIImage alloc] initWithData:data];
YKSubCategory *subCategory = (YKSubCategory *)[_subCategories objectAtIndex:connection.tag - 1];
subCategory.menuImage = image;
[image release];
[_tableView reloadData];
So now the funny thing is that the subCategory variable is the variable what I expect. Then after I set the menuImage property then the variable for this property is set. I can see in the debugger, but in the array isn't set. What is this?
(i) Have you #synthesize-d menuImage?
(ii) Can you break up the setter line to first get the YKSubCategory object and test it exists and is what you expect?
YKSubCategory* target = (YKSubCategory*)[_subCategories objectAtIndex:connection.tag - 1];
NSLog("subcategory target = %#", target);
// other tests here
target.menuImage = image;
Otherwise, I don't think we have enough info to solve this. What does menuImage look like immediately after setting?
EDIT: when you say "in the array isn't set", do you mean that the actual object in the array differs from the one you've just set right there and then? Or that when you come back to the array when loading the table data the menuImage is no longer set? Or that it just doesn't show in the table?
The first seems impossible on the face of it, so let's think about the second and third.
If menuImage has been reset by the time you come back to it, there must be some code setting it somewhere. This may not go through the property accessor, but for starters you might explicitly implement the setter to log the changes:
- (void) setMenuImage:(UIImage*)newImage
{
if ( newImage != menuImage )
{
NSLog(#"Changing menuImage from %# to %#", menuImage, newImage);
[newImage retain];
[menuImage release];
menuImage = newImage;
}
}
Another possibility is that the actual YKSubCategory object in the array has changed, or even that the arrays are different, so check that the pointers are the same in both locations.
On the other hand, if the menu image ivar is set, but it's not showing up in the table, you need to check your drawing code.
this code works fine until I start scrolling:
- (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;
}
Route *r = [data routeForDay:day index:indexPath.row];
cell.textLabel.text = r.name;
cell.imageView.image = r.image;
return cell;
}
It works perfectly for every row until I scroll down, when it crashes with objc_msgsend on cell.imageView.image = r.image; I've confirmed that nothing is nil, I've even checked the retainCount for everything involved. I'm completely at a loss, any ideas? Thanks.
Edit:
I solved the problem, but do not understand how the change in code makes the bug go away, so I'd appreciate a hint if anyone knows.
This is how the image was initially created, in the Route init method. image became deallocated when the table was scrolled, in my tableview controller.
NSString *imagePath = [[NSBundle mainBundle] pathForResource:[dict valueForKey:#"image"] ofType:#"png"];
image = [UIImage imageWithContentsOfFile:imagePath];
When I changed the second line to
image = [[UIImage alloc] initWithContentsOfFile:imagePath];
it worked fine.
I'm just a little confused and unhappy.
Your bug is most definitely a retain/release problem. Try to turn on zombies. If you don’t know how to do that, see below and read this tech note.
I assume one of the objects pointed to by data, r, or r.image is not retained properly.
One more thing: don’t look at retainCount before understanding memory management (and especially autorelease pools) in depth. Otherwise you’ll only be confused by the returned value.
How to enable zombies:
Choose Project > Edit Active Executable to open the executable Info window.
Click Arguments.
Click the add (+) button in the “Variables to be set in the environment” section.
Enter NSZombieEnabled in the Name column and YES in the Value column.
Make sure that the checkmark for the NSZombieEnabled entry is selected.
Edit:
Congratulations on finding the bug. The only pice of the puzzle you’re still missing is understanding Cocoa’s memory management. I recommend that you read the official documentation, as it is concise and easy to read.
In short: imageWithContentsOfFile: returns an autoreleased object, while initWithContentsOfFile: returns a retained object. But again: read the docs, or you’ll continue having memory errors.
Im developing an app for an iPhone and I found that the following code is causing the memory allocation to increment.
-(UIImage *)createRecipeCardImage:(Process *)objectTBD atIndex:(int)indx
{
[objectTBD retain];
// bringing the image for the background
UIImage *rCard = [UIImage imageNamed:#"card_bg.png"];
CGRect frame = CGRectMake(00.0f, 80.0f, 330.0f, 330.0f);
// creating he UIImage view to contain the recipe's data
UIImageView *imageView = [[UIImageView alloc] initWithFrame:frame];
imageView.image = rCard;
[rCard release];
imageView.userInteractionEnabled = YES;
float titleLabelWidth = 150.0;
float leftGutter = 5.0;
float titleYPos = 25.0;
float space = 3.0;
float leftYPos = 0;
// locating Title label
float currentHeight = [self calculateHeightOfTextFromWidth:objectTBD.Title :titleFont :titleLabelWidth :UILineBreakModeWordWrap];
UILabel *cardTitle = [[UILabel alloc]initWithFrame:CGRectMake(leftGutter, titleYPos, titleLabelWidth, currentHeight)];
cardTitle.lineBreakMode = UILineBreakModeWordWrap;
cardTitle.numberOfLines = 0;
cardTitle.font = titleFont;
cardTitle.text = objectTBD.Title;
cardTitle.backgroundColor = [UIColor clearColor];
[imageView addSubview:cardTitle];
[cardTitle release];
leftYPos = titleYPos + currentHeight + space;
// locating brown line
UIView *brownLine = [[UIView alloc] initWithFrame:CGRectMake(5.0, leftYPos, 150.0, 2.0)];
brownLine.backgroundColor = [UIColor colorWithRed:0.647 green:0.341 blue:0.122 alpha:1.0];
[imageView addSubview:brownLine];
[brownLine release];
leftYPos = leftYPos + 2 + space + space + space;
// creating the imageView to place the image
UIImageView *processPhoto = [[UIImageView alloc] initWithFrame:CGRectMake(leftGutter, leftYPos, 150, 150)];
if((uniqueIndex == indx) && (uniqueImg.imageData != nil))
{
if([uniqueImg.rcpIden isEqualToString:objectTBD.iden])
{
objectTBD.imageData = [NSString stringWithFormat:#"%#", uniqueImg.imageData];
[recipesFound replaceObjectAtIndex:indx withObject:objectTBD];
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
[imageData release];
processPhoto.image = rcpImage;
[rcpImage release];
}
}
else if(objectTBD.imageData != nil)
{
NSData * imageData = [NSData dataFromBase64String:objectTBD.imageData];
UIImage *rcpImage = [[UIImage alloc] initWithData:imageData];
processPhoto.image = rcpImage;
[rcpImage release];
[decodedBigImageDataPointers addObject:imageData];
}
else
{
UIImage * rcpImage = [UIImage imageNamed:#"default_recipe_img.png"];
processPhoto.image = rcpImage;
[rcpImage release];
}
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 1
[imageView addSubview:processPhoto];
NSlog(#" Process Photo Retain Count %i", [processPhoto retainCount]); // this prints a 2!!!!
//[processPhoto release]; // this line causes an error :(
// converting the UIImageView into a UIImage
UIGraphicsBeginImageContext(imageView.bounds.size);
[imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[objectTBD release];
for(UIView *eachSubview in imageView.subviews)
{
[eachSubview removeFromSuperview];
NSLog(#"each subview retainCount %i despues", [eachSubview retainCount]);
// here I find that the processPhoto view has a retain count of 2 (all other views have their retain count in 1)
}
return viewImage;
}
When I checked at the instruments object allocation I found that the "GeneralBlock-9216" growing up.
Breaking down the row I found that every time I call this code, one instance of:
2 0x5083800 00:18.534 ImageIO initImageJPEG
is being allocated. Checking the call stack, the following line is highlighted:
UIImage * objImage = [UIImage imageWithData:imageData];
Any help to find what the error is?
As TechZen said, the imageWithXXX: methods cache the image inside of them while you run the program (though you release the instances after using). I recommend initWithXXX: and release API sets instead of imageWithXXX:.
Well, if you embed several debug log on your source code, check how many times is it called, and check the retain count of the instances.
As far as I can explain, that is all.
I hope you will solve the problem.
Does anyone have an answer for this? It's tearing me apart trying to figure out why this image information keeps lingering. I've tried every solution.
The situation:
Images get downloaded and stored to the device, then loaded with imageWithContentsOfFile (or even initWithContentsOfFile, which doesn't help either). When the view goes away, the images don't, but they don't show up as leaks, they're just this initImageJPEG Malloc 9.00 KB that never goes away and keeps ramping up.
UPDATE: I believe I've figured this out: Check to make sure everything is actually getting dealloc'd when you're releasing whatever the parents (and/or grandparents) and etc of the images are. If the parents don't get deallocated, they never let go of their children images, and whatever data was in those images sticks around. So check retain counts of parent objects and make sure that everything's going away all the way up whenever you release the view at the top.
A good way to check for this is to put NSLogs into custom classes' dealloc methods. If they never show up, that object isn't going away, even though the reference to it might, and it (and whatever its subviews and properties are) will never ever disappear. In the case of images, this means a pretty sizable allocation every time that object is generated and never deallocated. It might not show up in leaks, especially if the parent of the topmost object you're thinking you're releasing but actually aren't persists and doesn't itself ever deallocate.
I hope this helps. It'll be useful to take some time to read through your code with a fine-toothed comb to make sure you're allocating and releasing things properly. (Search for "alloc]", start at the top of the file, and work your way down to make sure you're releasing and that the release isn't inside of some if() or something.)
Also, running "Build and Analyze" might lock up your machine for a bit, but its results can be really helpful.
Good luck!
I think you're seeing UIImage cacheing images. There used there used to be a method something like initWithData:cache that let you turn the cacheing off. Now I think the system always caches the images automatically behind the scenes even after you've deallocted the specific instances.
I don't think its an error on your part. I think it's the system keeping data around in the OpenGl subsystem. Unless it causes a major leak, I don't think it is a problem.