In a project I'm working on (I picked up this code and I've been trying to debug it), I have a function that gets called by an observer. The observer calls a method that updates data to be put on a screen. While this update is happening (it takes a few seconds for the updates to occur), a user can press the 'Back' button on the navigation bar, which causes a dealloc call to occur. While the method is running, the dealloc call releases all of the ivars, which eventually causes EXC_BAD_ACCESS when the method attempts to access the ivars. The structure of the update method is also enclosed with a #synchronized block.
- (void)update {
#synchronized(self){
// some code here...
// Also access ivars here.
}
}
What can be done to tell the controller to finish the method first before deallocating? I've tried running a while loop with a condition in the dealloc, but that doesn't seem efficient. It also never fully executes if the controller is released, and stays in a deadlock. I feel like the solution is simple, but my brain is fried from a long day at work and I can't think about it.
You can call retain on self to ensure the reference count does not reach zero while running a longer running method; and avoid the dealloc that way:
- (void) update {
[self retain]
// do work ...
[self release]
}
If you put the work you need to do in a block, the compiler will automatically retain all the objects referenced inside the block (including self). For example:
- (void)update {
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
// some code here, access ivars, do whatever you want
}];
}
The update method will immediately return and the block will be scheduled to run on the main run loop (no secondary threads are involved here).
If the update work is a long-running task, you can use a background queue instead of the main one, and then (inside that block) schedule another block to run on the main queue and interact with the UI when the work is done.
Since you are talking about the NavigationBar my guess is that what is happening is that your NavigationController is releasing its reference to your UIViewController class, which in turn is holding the ivars. One way around it is to retain the objects inside the viewcontroller until the calcuations are made, as the other answers suggest. However if the calculations you are doing are made to calculate what data should be shown in that view, the next time you open the same view, you will reload the view again from the beginning.
What I would do is that I would keep a reference to the ViewController that you are working with outside of the navigationcontroller, so that it does not get released when the user presses the back button.
Then later, if the user comes back to the same view, you will already have the data loaded. Just push it again using the same reference.
Example:
#interface YourRootViewController : UIViewController {
YourNextControllerClass *nextController;
}
#property (nonatomic, retain) YourNextControllerClass *nextController;
#end
In the ViewDidLoad of your top ViewController:
self.nextController = [[[YourNextControllerClass alloc] initWithNibName:#"YourNextControllerNib" bundle:nil] autorelease];
When you want to show the view:
[myNavigationController pushViewController:nextController animated:YES];
If the user presses the back button, the viewcontroller will not be released, so when you push it again, everything will be there as you left it.
The solution depends on whether you want to keep that object alive to the end of the method or not. However, you also use #synchronized (self): I would be very afraid what happens if self gets deallocated and the #synchronized tries to remove the lock on self, so in this case I would try to keep it alive. (However, #synchronised and then running lots of code is not a good idea in my opinion; #synchronised should be used for the smallest amount of code possible to avoid deadlocks).
With ARC, keeping the object alive within the method is simple.
typeof (self) myself = self;
will create a strong reference to self. And better to use myself in the method body instead of self.
If you don't want to keep the object "self" alive, which would be the usual case:
__weak typeof (self) weakSelf = self;
Then wherever you want to make sure that self is still there you write
typeof (self) strongSelf = weakSelf;
if (strongSelf != nil) {
}
Related
I am new to cocoa, since I am programming cocoa app I always get confused how to release an object which tells its delegate that I am done. And the listener tries to release it.
So I have an AppController which lunches a updateCheckWindowController and also acts as a delegate to it so that when updateCheckWindow ends it can take further action (in my case release it). The UpdateCheckWindowController calls the delegate when window ends, so that call goes to AppController's method which tries to release the calling Object updateCheckWindowController.
Since AppController was the only one retaining it, calling a release should destroy the updateCheckWindowController , but updateCheckWindowController object is currently on the callStack because it is the one calling delegate method on AppController and AppController tries to release it. Its kind of circular call
How does it work in objective-c ? this looks like a pretty bad pattern unlike there is a fair justification for that. Or is my way of doing it is incorrect ?
You're judgement is probably right that there is a better pattern to use that avoids the circular call altogether.
If you have a view controller that is presented for a short period of time and then should be released when dismissed, you can use this method of UIViewController:
presentViewController:animated:completion:
You can autorelease the view controller you are presenting before you present it. Calling this method will retain it. When the controller should be dismissed, it can tell its delegate and the delegate can call
dismissViewController:animated:completion.
When the delegate dismisses it then it will be released.
Is this the situation or is it more complicated?
This could be as simple as using autorelease instead of release. Autorelease often doesn't perform an actual release until the call stack is unwound.
The call stack does not pose any problems here. As long as UpdateCheckWindowController does not do anything after the call after which it's released, you'll be fine.
See bbrame's answer for how you might organize your controller life cycles better. I will just explain the memory management consequences of your original architecture.
So, if I got your situation right, you have something like the following. It's going to work fine even if the object may be released before returning back to someMethod provided that you're not referencing self in any way after the delegate method call.
Note that your view controller is probably still referenced by UIKit by the time delegate method returns. That is, if that view controller is still part of the navigation stack. In this case, you shouldn't worry about it being released too early, it'll only happen after your methods return and the control goes back to the run loop.
#interface AppController: NSObject
#property (nonatomic, strong) UpdateCheckWindowController *ctrl;
- (void)updateCheckWindowController;
- (void)iAmDoneReleaseMe;
#end
#implementation AppController
- (void)updateCheckWindowController {
UpdateCheckWindowController *ctrl = [[UpdateCheckWindowController alloc] initWithDelegate:self];
// ...
// Retain the controller
self.ctrl = ctrl;
}
- (void)iAmDoneReleaseMe {
self.ctrl = nil;
}
#end
#interface UpdateCheckWindowController: NSObject
// Make it weak so we don't have a retain cycle
#property (nonatomic, weak) AppController *delegate;
#end
#implementation UpdateCheckWindowController
- (void)someMethod
{
// do all the finishing work here
// ...
// now call the delegate that will release us
[self.delegate iAmDoneReleaseMe];
// don't do anything else here and you'll be fine
}
#end
We are subclassing UITabBarController as well as UITabBarControllerDelegate to handle certain events concerning tab switches.
Now in our custom tab bar controller we have:
- (id)initCustomTabBarController {
self = [super init];
if(self) {
[self setDelegate:[[CustomTabBarControllerDelegate alloc] init]];
// ...
}
return self;
}
Since we transitioned the project to ARC, the delegate is released to early which causes a tab switch run into a deallocated instance.
The property is defined as assign in UITabBarController.h - which I obviously have no influence on.
What can I do to make the delegate object "live" longer than for the init method?
The way you have done it, it is expected that the delegate will not outlive the object, because it is weak. Remember, you created the object, it's up to you to hold on to it.
However - the pattern that you are using is incorrect.
The point of a delegate, is that it provides method implementations to a class that a class can't add for itself, because it doesn't have enough information. For example, a table view delegate. A table view, in order to be generic, cannot know how many rows or sections to display, so it asks it's delegate to supply this information.
In your case, you have an object that is creating it's own delegate. In which case, why bother having a delegate at all? Just implement the methods in the class.
Yes this would be normal under ARC, since no reference to it is made (aka strong propteries) it should be released at the end of cycle.
Just make a property in the class where you assign the CustomTabBarControllerDelegate take make it strong. Then assign this property to the delegate.
In non ARC the way you have set it up you could have create a memory leak.
I'm currently learning objective-c and I'm currently training with NSTableView.
Here is my problem :
I have linked my tableview to my controller through Interface Builder so that it has a datasource, I have implemented NSTableViewDataSource protocol in my controller and I have implemented both -(NSInteger) numberOfRowsInTableView: and -(id) tableView:objectValueForTableColumn:row: methods.
I have created a raw business class ("person") and I succeeded to display its content into my NSTableView.
But then, I put some NSLog in my dealloc methods to see whether the memory was freed or not and it seems that my array as well as my "person" instances are never released.
here is my dealloc code in the controller:
-(void)dealloc
{
NSLog(#"the array is about to be deleted. current retain : %d",[personnes retainCount]);
[personnes release];
[super dealloc];
}
and in my "person" class
-(void) dealloc
{
NSLog(#"%# is about to be deleted. current retain : %d",[self prenom],[self retainCount]);
[self->nom release];
[self->prenom release];
[super dealloc];
}
When these deallocs are supposed to be called in the application lifecycle? Because I expected them to be called at the window closure, but it didn't.
In the hope of beeing clear enough,
Thanks :)
KiTe.
I’m assuming you’re never releasing the window controller object that owns the (only) window. As such, the window controller and every top level object in the nib file are retained throughout the application lifecycle, including the window (and its views).
Since the window controller exists throughout the application lifecycle, it isn’t released, hence its -dealloc method is never called. And, since the controller -dealloc method is never called, its personnes array isn’t released.
The personnes array owns its elements. Since the array isn’t released, neither are its elements, hence the -dealloc method of the corresponding class/instances is never called.
Don't ever use retainCount. The results are misleading at best. If you practice proper memory management practices, you'll be fine. Have you had any memory issues/crashes?
I have a couple of questions relating to UIViewController:
1) When are each of the methods called for UIViewController? Specifically, the difference between viewDidLoad, viewDidUnload, and dealloc.
2) What's the difference, in general, when setting a pointer equal to nil and releasing the pointer? I know that in viewDidUnload you're supposed to set it equal to nil but in dealloc call release.
UPDATE: Sorry, just realized the question is misleading. Instead of dealloc, I meant -- when is initWithNibName:bundle: and release called? Just once by IB, right?
Setting a pointer to nil doesn't release the memory that it points to.
When you do something like
self.pointer = nil;
it's usually a case that the property has a retain attribute. When this is the case, setting the property to nil will indirectly cause a
[pointer release];
pointer = nil;
In the case of the view controller methods, viewDidLoad is called when your view is loaded, either from a nib, or programatically. More specifically, it's called just after -loadView is called. You shouldn't need to call loadView manually, the system will do it. The viewDidUnload method is called in the event of a memory warning and your view controller's view is not onscreen. Subsequently, loadView and viewDidLoad will get called again on demand.
The dealloc method, as normal, is called when your object's retain count reaches 0.
pointer = nil; // just clears the variable in which you store the pointer, but does not free memory.
[pointer release]; // just frees the object (memory), but does not clear the variable used to point to it.
self.pointer = nil; // sets the variable to nil. Also releases the object ONLY if pointer is a #property(retain) ivar.
One easy way to see when various methods are called is to do this in your UIViewController:
- (void)viewDidLoad
{
NSLog(#"MyViewController::viewDidLoad");
[super viewDidLoad];
// the rest of your viewDidLoad code, here.
}
// Etc., for the other methods of interest.
NOTE: much can be gleaned from overriding retain & release to log and then following along in the debugger.
First of all, i have never seen so many memory issues in my app since i started placing "self" everywhere after reading an article about how memory behaves in obj-C. Now, im getting all kinds of issues (reveals the sloppiness of my coding). Granted I am a newbie at Objective-C, i'll admit i have never had so much issues with memory management before in my life. But i reckon it takes practice to get used to this.
Now, on to my question.
I have a class interface property (self.todoCreate) that holds a reference to the above controller. This controller is navigated to by pressing a button.
#property (nonatomic, retain) TodoTaskCreateController *todoCreate;
The code below are the snippets that cause the navigation view change:
TodoTaskCreateController *viewController = [[TodoTaskCreateController alloc]
initWithNibName:#"TodoTaskCreateController"
bundle:[NSBundle mainBundle]];
self.todoCreate = viewController;
[viewController release];
// slide-in todoCreate controller.
if (self.navigationController != nil && self.todoCreate != nil) {
[self.navigationController pushViewController:self.todoCreate animated:YES];
}
So here is my problem:
The first time i run this it works.
Once I'm on the second view screen, I navigate back to the main view.
And if i try to navigate again a 2nd time, then the app crashes, right where self.todoCreate is being assigned viewController.
Note that within the main view's viewDidAppear method, I call [self.todoCreate release].
Can anyone explain this?
PS - No wonder so many iPhone apps randomly crash.
todoCreate is a property, which means when you assign a value to it, it invokes a method called setTodoCreate which looks something like:
- (void) setTodoCreate:(Foo*) newVal
{
[todoCreate release]; // release the previous object
todoCreate = [newVal retain]; // point to new object, and also retain it
}
Now your viewDidAppear method is releasing self.todoCreate at which point the retain count of todoCreate is 0. When you create a new TodoTaskCreateController and assign it to self.todoCreate another release is performed, but this time on an object with retain count of 0.
Instead of [self.todoCreate release], you should be using self.todoCreate = nil.
You probably shouldn't be continually destroying and creating your TodoTaskCreateController either.
Your comment regarding application crashing is most likely due to developers not testing their application to see if it handls memory warnings properly. In the simulator there is a menu option to simulate this, and your application should "survive" the warning in all of its views.
I'm not going to speak to soon, but it APPEARS that i have resolved the crash by simply adding the viewController to the autorelease pool, and then removing all manual occurances of its release.
Now does it matter if my #property for createTodo is defined as (nonatomic, retain) as opposed to (nonatomic, assign)?