I'm trying to wrap my head around a strange problem in my iPad app. It's a pretty simple app, only one root view controller with a little board game inside.
Sometimes, seemingly at random, the app freezes and I get a BAD_ACESS on a delegate reference I use in one of my classes. I've been solving BAD_ACCESS-problems for a long time, but this is very strange. The object the delegate is referring to is the root view controller, and that should never b released. I put a log line in the -(void)dealloc method and that never occurs. I even tried to over retain the object but it still disappears.
Even if I run the app in the profiler with NSZombie detection on, the profiler just stops when this happens. Doesn't show any results whatsoever.
Another strange thing I noticed was the memory address. If I log it like NSLog(#"%p", delegate); i get "0x1" as a result. A nil pointer is 0x0 so testing for if(delegate) does return true even though the object has vanished. And even if the object itself was deallocated, the memory address would still be intact?
The problem only occurs after some use, between like 15 and 45 sec.
Doed someone know how to tackle this problem? I'd be greatly thankful.
This is how the delegate is assigned. The _delegate is the root view controller which is always active.
-(id)initWithDelegate:(NSObject <TheGameDelegate>*)_delegate level:(int)_level;
{
self = [super init];
if(self != nil)
{
delegate = [_delegate retain];
...
Here is where it crashes:
-(void)countdown:(NSTimer*)timer;
{
time -= 1;
if(delegate) // this is always true
{
NSLog(#"%p", delegate); // this prints a normal memory address until right before crash when it prints "0x1"
[delegate theGameTick:self]; // accessing deleagte gives BAD_ACCESS
}
...
Thanks in advance!
imo, your delegate implementation is weird.Retaining delegates is not the good idea. Try adding to the header:
#property (nonatomic, assign) id delegate and then the method would look like
-(id)initWithDelegate:(id)_delegate level:(int)_level;
{
self = [super init];
if(self != nil)
{
self.delegate = _delegate;
...}
as well as in further methods you reference to self.delegate.(you can add the required protocol as well, i skipped it for clearer code)
Related
I'm working on an app that has a Main view that wants to spawn a child view when a button is touched. So when I receive the button event, the MainViewController spawns the child view by calling initWithNibName and storing the ChildViewController in an ivar. I then show the ChildView by attaching an animation and setting childVC.view.hidden = NO.
This works, but I noticed that the ChildViewController was never getting released after closing the ChildView. I realized that the ChildVC's retain count went from 1 to 2 when I first access the child view. So something in the nib loading guts appears to be retaining my ChildVC again (in addition to the initial retain I expect during object initialization).
Can somebody help me figure out why the ChildVC is getting retained the extra time, and how can I make sure that it gets fully released when I want to close the child view?
Edit: here's some code, only slightly simplified. These are methods on the parent view controller.
-(IBAction)onLaunchChildButtonTouched:(id)sender
{
m_childViewController = [[ChildViewController alloc] initWithNibName:#"ChildViewController" bundle:nil];
[m_childViewController setParentDelegate:self]; // this is a weak reference
// m_childViewController retain count here is 1, as expected
m_childViewController.view.hidden = YES;
// m_childViewController retain count is now 2, not expected
[self.view addSubview:m_childViewController.view];
[self addTransitionEntrDir:YES]; // code omitted
m_childViewController.view.hidden = NO;
}
-(void)onChildWantsToClose:(id)child
{
NSAssert( child == m_childViewController, #"unexpected childVC" );
// if child view is now hidden, we should remove it.
if( m_childViewController != nil && m_childViewController.view.hidden )
{
[m_childViewController.view removeFromSuperview];
[m_childViewController release]; m_childViewController = nil;
// BUG: m_childViewController retain count is still 1 here, so it never gets released
}
}
Without code it is difficult to say exactly, but are you sure you are not assigning your ChildVC to a retain property of some other object? This would explain the unexpected retain you see.
Sorry for the previous answer, where I tried to convey this same message but I mixed everything up.
OLD ANSWER:
keep in mind that the view property of a UIViewController is retained:
view
The view that the controller manages.
#property(nonatomic, retain) UIView *view
so, if you assign to it like this:
childVC.view = [[xxxxx alloc] initWithNibName:...];
this explains what you are seeing.
Use instead:
childVC.view = [[[xxxxx alloc] initWithNibName:...] autorelease];
I found the problem, the leaky ChildViewController was instantiating an object that retained a ref back to it.
The interesting part is that I wasn't simply forgetting to release this reference. I did have a call to release it, but that code was never running because it assumed that viewDidUnload would run and give me a chance to release everything, but it didn't. I put me deinit code inside dealloc instead, and it works now.
So here is my code:
-(void)setMovie:(NSURL *)movieLocal {
movie = movieLocal;
[self.movie retain];
...
}
And i get this error:
Potential leak of an object allocated on line 43
Line 43 is [self.movie retain];. Am i doing something wrong, and how can i get rid of this error?
There are a couple issues here:
The old value for movie is never released
'movie' and 'movieLocal' might point to the exact same object. If that is the case, you will call retain on movie/movieLocal without a subsequent balanced release call.
You might want to use the following:
-(void)setMovie:(NSURL *)movieLocal {
if (movie == movieLocal) {
return;
}
[movie release];
movie = [movieLocal retain];
//...
}
Here's the proper setter:
-(void)setMovie:(NSURL *)movieLocal {
if (movie != movieLocal) {
[movie release];
movie = movieLocal;
[movie retain];
}
}
However, if you declared your property (in .h file):
#propert (nonatomic, retain) NSURL *movie;
and synthesized it in .m file, by #synthesize movie;, than there is no need to explicitly override the setter - the code above will be generated automatically for you. So whenever you want to set your movie you'll just call self.movie = newMovie; which will be equivalent to calling [self setMovie:newMovie];.
For more info read a section "Declared Properties" in Learning Objective-C guide.
EDIT: to explain what was wrong with your setter.
-(void)setMovie:(NSURL *)movieLocal {
movie = movieLocal; // first line
[self.movie retain]; // second line
...
}
In 1st line you are assigning movie to point to movieLocal, but you don't release old NSURL object that movie was pointing to before assignment. This was way, your causing a memory leak - you abandon memory, so it can never be relinquished back by your app. In general, abandoning memory is an easy way to get you application terminated by iOS, when objects are big and often leaked.
In 2nd line you are calling you setMovie setter again as self.movie = syntax causes the runtime to call a setter for movie property. This time it'll cause an infinite loop.
I hope my wording was clear for you and my answer helpful.
I have an interface like this:
#interface MacCalculatorAppDelegate:NSObject
<UIApplicationDelegate> {
// ...
UIButton *operatorPressed;
NSString *waitingOperation;
}
And I am initializing waitingOperation variable in my implementation like this:
- (id)init {
if (self = [super init]) {
waitingOperation = #"not set";
}
return self;
}
And I want to reinitialize this variable in a function. This is calculator program and when user clicks on operators button the following function will be invoked:
- (IBAction)operatorPressed:(UIButton *)sender {
if([#"+" isEqual:operand]) {
waitingOperation = #"+";
}
}
But after the check in if statement, my program won't do anything and this happens when I am trying to reinitialize waitingOperation variable.
I am new to objective-c, please help me understand what's wrong here.
Thanks in advance.
There are several things to note here.
waitingOperation=#"not set";
As it stands, this will eventually crash your program. Objective C string literals are autoreleased instances of NSString, which means unless you assign it to a retained property or retain it manually, the memory will be deallocated, leaving a dangling pointer.
-(IBAction) operatorPressed:(UIButton *)sender {
Have you verified that this method is actually being called? You have to assign the IBAction in Interface Builder. Step through it in the debugger or use NSLog to verify that this method is being called.
if([#"+" isEqual:operand])
Where is operand coming from?
waitingOperation=#"+";
Same problem as above, this will get deallocated behind the scenes, leaving you with a dangling pointer.
Also, note that if you know that both variables are NSStrings, using isEqualToString: is faster than using isEqual:.
Finally, this stuff shouldn't be part of your app delegate. This is view controller logic.
If your operand is also a string, then check for isEqualToString instead of isEqual
Serious Problem here... i'm getting ECX_BAD_ACCESS if i try to NSLog an instance variable of my custom object. Following Function is called in my ViewController, payload holds String Data which is pulled from a url.
- (void) initVcardWithData:(NSString *)payload {
NSLog(#"1. initVcardWithData");
aVCard = [[vcardItem alloc] initWithPayload:payload];
VCardViewController *aVCardViewController = [[VCardViewController alloc] initWithVCard:aVCard];
[self presentModalViewController:aVCardViewController animated:YES];
[aVCard release];
}
So far so good. The initWithWithVCard function is as follows, theVCard and theVCardN are defined in #implementation and also set as a #property (nonatomic, retain) in (.h).:
-(id)initWithVCard:(vcardItem *)aVCard {
if(self = [super init]) {
theVCard = [aVCard retain];
theVCardN = [theVCard.PersonName retain];
}
NSLog(#"---- vCardViewController :: initWithVcard :: FirstName: %#", theVCard.PersonName.FirstName);
return self;
}
If i access the theVCardN object in my ViewController aVCardViewController within ViewDidLoad everything works like a charm. I set some labels with data from that object.
If i then try to access the instance variables from theVCardN within a function which is called from an IBAction which is connected to a button in View, i get an EXC_BAD_ACCESS error at the debugger console. The Function which tries to pull data from the instance variables is as follows:
-(IBAction)addressbookButtonTapped {
NSLog(#"RETAIN COUNT FOR theVCard: %i", [theVCard retainCount]);
NSLog(#"RETAIN COUNT FOR theVCardN: %i", [theVCardN retainCount]);
NSLog(#"Save to Adressbook: %#", theVCardN.FirstName);
//[self dismissModalViewControllerAnimated:YES];
}
The RetainCounter for theVCardN right before calling NSLog outputs "1". The NSLog Line then returns EXC_BAD_ACCESS in Debugger Console.
Any idea ?
Do not call -retainCount. Absolute retain counts are useless.
retainCount returns the absolute retain count of an object. The actual value will be an implementation detail that is very often completely out of your control as the system frameworks may do any number of things internally to cause the retain count to be modified in ways you don't expect.
It is useless for debugging and their are a wealth of tools that are specifically focused on tracking down these kinds of issues.
First, if there is a crash, there is a backtrace. Post it. Probably not that interesting in this case, but, still, always look to the backtrace to at least confirm that it is crashing where/how you think it is.
From the evidence posted, it sounds like theVCardN.FirstName is either set to garbage or the underlying string has been over-released. Turn on zombie detection mode and see if that is the case. Since it is crashing on FirstName, then show the code related to creating/storing the FirstName.
Also, instance variables and methods should always start with a lowercase letter; PersonName should be personName & FirstName should be firstName.
Maybe i'm reading the code wrong or misunderstanding your class structure, but it looks like you logging:
NSLog(#"Save to Adressbook: %#", theVCardN.FirstName);
Above, where you say it is still working, you are logging:
theVCard.PersonName.FirstName
Are you missing the "PersonName"? Meaning you should be logging:
NSLog(#"Save to Adressbook: %#", theVCardN.PersonName.FirstName);
Somewhere I was reading that - regarding low memory warnings and giving up an non-visible view with all it's subviews (= a whole nib, I think), you should do that:
-(void)dealloc {
[anView release], anView = nil;
[someImageView release], someImageView = nil;
[super dealloc];
}
rather than
-(void)dealloc {
[anView release];
[someImageView release];
[super dealloc];
}
What's the reason for grounding those pointers to nil (= "no object"), after I call release? Let me guess: Some other method could have -retain'ed the view for some reason (anyone any example for when this could happen?), then the didReceiveMemoryWarning thing happens, and you release a whole nib+view that's currently not visible (i.e. in a multiview-app). As soon as the user wants to see that view again, you would quickly load the nib again and then: It loads all views, connects the outlets, and BANG! Your other retain'ed view's are hanging now without any pointer somewhere lonely in the memory brick, causing a fat and deep memory leak until your app crashes.
Right/Wrong?
The principle is more general than UIView. indeed it is more general than Objective-C/Cocoa -release method. It is valid also with C malloc()/free() memory functions.
When you no longer need an object or any memory zone, first you release/free it. Then, to make sure that you won't use it again, you clear the means to access this object or memory zone by assigning a nil to an object or a NULL to a memory pointer.
Some other method could have -retain'ed the view for some reason
Unless you're invoking dealloc yourself, it's only called when the retain count becomes zero.
Note that in Objective-C sending a message to a nil "object" is (often) perfectly fine. Doing so will not make your program halt, but the message is simply ignored. However, you cannot send a message to a freed object, which would yield a crash.
So, the following would give you an error:
[anView release];
[anView doSomething];
But, this is in fact ok:
[anView release];
anView = nil;
[anView doSomething];
It's a matter of taste, but for the above, you might in fact prefer to crash your program, rather than wondering why doSomething is not executed...
See also Sending Messages to nil from Apple's Introduction to The Objective-C 2.0 Programming Language.
The -dealloc method is called when the object is freed and no other methods on the object will be executed after. Therefore, setting any instance variable to nil has no effect outside that object.
If you were releasing an object (without using a setter) somewhere else in the class, it would be important to set the instance variable to nil to prevent code elsewhere from sending a message to that address.
I use this pattern a lot:
- (void) showHelp: (id) sender
{
if (helpController == nil)
{
helpController = [[HelpController alloc] initWithNibName: #"Help" bundle: [NSBundle mainBundle]];
}
[self presentModalViewController: helpController animated: YES];
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
[helpController release];
helpController = nil;
}
Pretty much everywhere that I allocate a viewcontroller that is modal, or otherwise "temporary". This way, it hangs around if I need it again, but goes away if memory gets low.
rather than doing the expicit release and set to nil, if your accessors have properties associated with them yoc and do the following as a more concise method:
- (void) dealloc
{
self.retainedProperty1 = nil;
self.retainedProperty2 = nil;
self.copiedProperty = nil;
self.assignedProperty = nil;
}
this way you can have code that has less repetition since the synthesized code will take care of your releases for you.
Edit: i should point out that your properties can't be readonly or else you get compiler errors for obvious reasons :)