After reading:
Memory management of a view controller in Objective-c
and
Does UIView's addSubview really retain the view?
I wrote the following code to toggle a subview:
#synthesize switchableView, viewSelector, currentSubview;
//...
if(switchableView.subviews.count != 0)
[[switchableView.subviews objectAtIndex:0] removeFromSuperview]]
self.currentSubview = (veiwSelector.selectedSegmentIndex == 0) ?
[ViewA new] : [ViewB new];
[switchableView addSubview:currentSubview.view];
//[currentSubview release]; //<---crashes if I uncomment this line
It seems to run fine if I comment out that release line, but I can't wrap my head around why. Here's the way I understand what happens and maybe someone can tell me where I go wrong:
So lets consider currentView:
A gets alloc-ed by the 'new' message--retain count=A:1
A gets retained by the setter--retain count=A:2
A's view gets (supposedly) retained--retain count=A:2.1
next time through...
A's subview gets released count=A:2
B gets alloc-ed by the 'new' message--retain count=B:1, A:2
A gets autoreleased by the setter-- B:1, A:1
B gets retained by the setter--B:1, A:1
nothing ever gets rid of A?
So should I change my code, or am I wrong about the way memory management works in this language...or both?-
Ok, step one, ignore the retainCount. It's one of those things Apple should rename to something like lsdjiofsudfoiwjeriowhfiuwhrteiuhweifhsdjkfhsiurwoieuriosfho so people won't guess it's name, and not list it in the documentation. For your purposes, it's entirely useless, so ignore it.
Now that I've said that, let's consider something: addSubview: DOES retain its argument, and removeFromSuperview releases the receiver.
Finally, it's hard to tell what currentSubview is. It has a view property which would lean towards a VC, however, the way you're using it by itself, would indicate its a normal view. Perhaps you can clarify so I can continue my answer.
Your understanding of retain and release is correct, as is your code. That suggests that the problem lies outside of the code you've posted. For example, you would have this problem if your currentSubView property was defined as assign instead of retain.
You code is not structured well, however. This would be much clearer:
self.currentSubView = [[ViewA new] autorelease];
Furthermore, view controllers are meant to be cached, not created and released each time the user toggles a display. Typically, you create your view controllers beforehand, and access their .view property when necessary to display the view. UIViewController will automatically deallocate non-visible views in low memory conditions, and re-allocate their view when the .view property is accessed.
Change the release line to
self.currentSubview = nil;
and I think you'll be fine. You're releasing, but not setting the property to nil. So, when it gets re-assigned next time through, release will be called again on it. But you already released it so... boom.
Related
I presume this isn't a common problem, which makes it a bit more difficult to answer. Any help is appreciated though.
I need to call this delegate a number of times in my app, and i noticed that after a number of times, the delegate starts to come back as NULL (and hence stops responding). I put an nslog everywhere the delegate gets called, so i know that at this point, it's fine:
UIImage *image = [self.delegate largeThumnailForMediaAtIndex:indexPath.row];
Then the next time this line gets called, the delegate is set to NULL. No lines around it call or set the delegate. I put an NSLog on the setDelegate method too, and that didn't get called before it changed to NULL.
Any code you might need to see, let me know. Any ideas you want me to try out, let me know about that too.
Thanks for your help.
EDIT: Bizarre, but might help to lead to a solution. I put an NSTimer scheduledTimer.. in the class which gets made the delegate, and got it to fire that once a second so I could see if it turned null at any point. The result i got, however, was that this time it didn't turn null. It returned all of the delegate methods. When i took the timer out, it goes back to returning NULL. Obviously having a timer in there is an odd workaround 'solution'. I'm hoping this rings a bell for someone and gives them a clue to where the problem might lie?
EDIT 2: I've solved this problem by, instead of using this code in my AppDelegate:
JCreateViewController *create = [[JCreateViewController alloc] init];
[create.navigationBar addLeftButtonWithTitle:#"Back" type:JButtonTypeArrow];
create.navigationBar.title = #"Entry #17";
[self.window addSubview:create.view];
Declaring it in my header file, then using this:
self.create = [[JCreateViewController alloc] init];
[self.create.navigationBar addLeftButtonWithTitle:#"Back" type:JButtonTypeArrow];
self.create.navigationBar.title = #"Entry #17";
[self.window addSubview:self.create.view];
I don't understand why this makes a difference though. I'd love to know, if anybody does know?
Looks like you're using ARC. Whatever this object is, nothing owns it. Nothing has a strong reference to it, so it gets released, and then at some point it gets deallocated.
JCreateViewController *create = [[JCreateViewController alloc] init];
This is a local variable. When the variable goes out of scope at the end of the method, you can't access that object anymore. Under MRR, this would be a leak. Under ARC, the object is going to die, just like it was in an autorelease pool.
self.create = [[JCreateViewController alloc] init];
By creating a property (presumably strong) and putting the controller into that property, you've given whatever self is an owning reference to the controller. It will now live as long as the property isn't reassigned or set to nil.
The timer fixed things because the timer retains its target (which I believe was the controller (your question is rather unclear)), and the timer itself is retained by the run loop. So the run loop keeps the repeating timer alive and the timer kept your controller alive.
In short, make sure something owns this object and it'll stick around.
I will try and explain this as best as possible if I have this code here
ViewTwoController *home = [[ViewTwoController alloc] initWithNibName:#"contentscreen" bundle:nil];
[self presentModalViewController:home animated:YES];
[home release];
I will start a new .m and .h class. What I would like to try and do however is when this is called, have the .m and .h class where it was called from running in the background so I do not lose data.
The best example I can think of is with Android. If you begin a new class, and don't add the finish() statement in the class the call was made from, the previous class runs behind the current class (that was pushed to the front) and maintains all the data it originally had, so if you hit a return button, you will see the information you had moments ago. Is this possible? I can try add more detail if people cannot understand what I am trying to do.
You need to understand a objects life cycle a little better.
An object is brought into existence generally with a 2 part process.
allocation - (grabbing the memory for the object and its members)
initialization - (setting the object up for use)
This can be combined into single step with the +new class method which combines alloc and init.
lets introduce an example class called MyClass and an object of that class called myObject. By convention classes start with uppercase letters and objects start with lowercase letters. So Without further ado, some code:
MyClass * myObject;
this this makes an object pointer, but doesn't allocate any memory for it or direct the pointer to reference anything.
myObject = [[MyClass alloc] init];
this actually creates an instance of MyClass, passes the -init message to it, then assigns the return value of the init message to myObject. At this point the reference count of this object is 1.
myObject can potentially go out of scope but that alone doesn't free the memory that was allocated during the alloc step.
in order to free that memory a release message will need to be passed to the object.
[myObject release];
The effect of release is to decrement the reference count, if the reference count is already 1 then the object will then be passed the -dealloc indicating that it is actually being freed.
back to your question... essentially [self presentModalViewController:home animated:YES]; ends up calling -retain on home, so that it will not be destroyed until you dismiss the modal view controller. In affect when you call release or autorelease you aren't dealloc'ing the object, just telling the object:
"Hey, I don't need you anymore, and if no one else does either then free up all the memory that you grabbed earlier".
Your problem has nothing to do with "class running in the background" but more with how you manage your data.
When you present a modal view controller, its parent (the view controller you presented it from) isn't destroyed (unless you specifically release it, which would probably crash your app later). So if you're wondering whether its still in memory; it is. As for tasks still running, it depends on what those tasks are. For example, you can still send it messages (call methods) and it will gladly receive those messages from your or from a delegate and perform whatever action it has to while it's off-screen.
Hope that helped.
In this case you are presenting new view controller. The main thread will be in the new controller presented. If you want something to run in background in the previous view controller then you can create a background thread. This can be done using [self perfomselectorInThebackground ... ] Or some other methods like GCD. (The main thing is you should not block Main thread)
This is a very very... very odd bug. It's hard to describe the exact project I have, but I will try and create a simpler representation of the classes I have. It goes like this:
Assume I have a navigation controller as my top view controller. Inside it, at one moment in time I have a UIViewController, let's say a ContactsScreenController. The view for this contains multiple UITableView that each is controlled by a separate object of type MyTableController (delegate&datasource). I do this by keeping an array of controllers
// This is the interface for my screen controller. An object of this type goes in a top-
// level navigation controller
// MainScreenController.h
#interface ContactsScreenController : UIViewController
NSMutableArray* tableControllers;
#end
// MainScreenController.m
- (UITableViewCell*)cellForRowAtIndexPath..something..
{
// Here what I do is create a new controller if needed, and add it to tableControllers
// Memory allocations & releases are good because I checked with instruments
}
#define SAFE_DEL(x) { if (x != nil) { [x release]; x = nil; } }
- (void)dealloc
{
SAFE_DEL(tableControllers);
[super dealloc];
}
Now, MyTableController is a more complicated object as it handles fetching data from a web service, but basically what I do is I want to make sure that when the object is deleted, I cancel any pending data requests, like this:
// MyTableController.m
- (void)dealloc
{
[globalDataProvider cancelRequestsForController:self];
// release other objects i might have
[super dealloc];
}
OK, so this is my objects setup. The crash occurs when I am deleting the object tableControllers. It decrements the retainCount for my MyTableController objects and it reaches 0 (checked using Instruments). But for some UNKNOWN reason, I get calls for cancelRequestsForController, AFTER the retain count has been zero.
Obviously, I get a EXC_BAD_ACCESS.
Before you start thinking it's a problem with my retain/release pairs, the application runs perfectly if I am releasing the main screen controller while the inner tables are static. As soon as the are scrolling and I hit the Back button in the navigation controller I experience the bug.
I've checked using instruments the entire history of retain count changes for my inner controllers and it is good (no unusual stuff). When the bug occurs, my last entry in the history is from QuartzCore run_animation_callbacks with a retain count of -1.
Any ideas? :)
PS: As a quick solution to get the project going, I've moved the cancelRequestsForController in a separate method and I'm manually calling it for each object in tableControllers before the release. This way I am sure that there will be no calls after the release, no matter the state of the tableview.
- (void)dealloc
{
for (TableController* c in tableControllers)
[c cancelRequests];
SAFE_DEL(tableControllers);
[super dealloc];
}
But I don't like this solution for several reasons.
Thanks to everybody for the answers/comments.
The problem was generated by a call to [super dealloc] in an object, before I got the chance to release my objects. This caused a lot of crazy stuff to happen. I moved the [super dealloc] at the end of my dealloc method (after my releases) and it works fine now.
The SAFE_DEL macro is unnecessary and makes the code less readable. Simply do:
[someObject release], someObject = nil;
It won't matter if someObject is already nil and it makes the code more directly readable.
As soon as the are scrolling and I hit
the Back button in the navigation
controller I experience the bug.
Any time you have non-memory management logic, you have fragility. Namely, when dealloc is being executed, it is quite likely because there is an entire sub-graph of objects in your application that are being deallocated. Since deallocation order is largely non-deterministic, you can't safely trigger any complex behavior in dealloc without risk that you are going to message an already deallocated object.
The best solution would be to get that cancellation mechanism out of dealloc!
Two things to check:
1. does your globalDataProvider (or any other class for that matter) have a reference to your controller (i.e. to call it back with data?) Your retain counts could be zero but for the wrong reason. How are you allocating the array, as well as each controller in the array? Do you have #properties?
2. In your executables properties Arguements screen, set NSZombieEnabled=YES That will tell you exactly which object is being called when it has a zero retain count.
HTH,
-Mike
I wanted to clarify something:
I.e when I add a UIVIEW To i.e another UIVIEW programatically.
i.e [theview addsubview:thechildview];
I need to release thechildview because theview increments the retain count.
Right?
So basically if I have a loop and I am using that loop to initialise subviews and add the subviews to a view:
for(int i=0;i<10;i++){
UIView *child = [UIview alloc]init.....
[parent addSubview:child];
[child release];
}
I still need to release the child right ? So that would mean the child view still has a retain count of 1 right ?
The thing is whenever I call the release on the child after adding , the child gets deallocated after a while.
Can you guys confirm am doing the right thing by releasing the child after it is added ?
thanks.
You should release it somewhere, correct. You can hold on it and release it later, but it is your responsibility to do it somewhen.
The parent view will retain/release its childs on its own. So if the parent view gets dealloc'ed, it will release its child views.
If a child view is removed from its superview, it will also be released.
I need to release thechildview because theview increments the retain count.
Right
Wrong.
You need to release theChildView if you own it and if you have finished using it. Whether theview chooses to retain it or not is completely irrelevant.
So if we look at your loop
for(int i=0;i<10;i++){
UIView *child = [UIview alloc]init.....
[parent addSubview:child];
[child release];
}
the code is correct because at the beginning of the loop block you alloc the child which means you own it and at the end of the loop it is about to go out of scope which means you no longer need it so you must release (or autorelease) it.
What happens in between is totally irrelevant and you should not assume anything about what any particular method on another object does (except that it adheres to the Memory Management Rules).
I still need to release the child right ? So that would mean the child view still has a retain count of 1 right ?
You cannot assume anything about the retain count. parent might cause the object to be retained more than once if it hands it off to other objects. It might choose to copy it instead of retaining it. If it doesn't need to keep a reference it might not retain it at all.
Can you guys confirm am doing the right thing by releasing the child after it is added ?
No, you are doing the right thing by releasing it when you have finished using it.
I'm doing this in dealloc of a view
[baseTable release];
In the header file, I declare it like this:
IBOutlet UITableView *baseTable;
....
#property(nonatomic, retain) UITableView *baseTable;
For some reason, I get a "EXC_BAD _ACCESS" in the dealloc. When I comment out the above line, all is well. How can I determine what specifically is going on with the UITableView and release?
If you want to find out the exact reason for the EXC_BAD_ACCESS bug, enable NSZombie, so that any time you call any method on a deallocated object, it tells you exactly what object and what method it is.
To enable NSZombie:
Expand the Executables section in the "Groups and Files" window on the left.
Open the properties of the executable.
Find the "Arguments" section (It's on the bottom half of the screen, I forget what tab it's in)
Add a new argument "NSZombieEnabled" with value "YES"
To disable it, either delete the value or uncheck it if you may want to turn it on again later. Be sure to not leave it on, because it doesn't actually deallocate anything when enabled!
My guess is that you are releasing baseTable one too many times somewhere, have a look for a place where you are releasing it with out retaining.
You must have one and only one release for every retain, start from there and see how you go. The tricky bit is making sure that wherever you pass the baseTable object the release/retain match up. So it won't be as simple as a grep on [ baseTable release ] and count them unfortunately :)
Sounds like you're over-releasing baseTable. It's hard to say where that might be happening without seeing more of your code. Are you giving ownership of that table to the autorelease pool at any point? When you autorelease an object, you're transferring ownership to the autorelease pool and you need to be sure to abandon the object (and possibly nil out its instance variable).
You need to examine every use of baseTable and make sure that any object that might assume ownership of the table retains it before releasing it. Also remember that you may be referencing the table object through an alias as a parameter to a UITableViewDelegate or UITableViewDataSource method.
when you are using the property Retain put the check if
if(self.tableView!=nil)
{
self.tableView = nil;
}
in dealloc.In this way you are checking whether the table view is nil and is it is nil you are making it into nil.