At various points during my application's workflow, I need so show a view. That view is quite memory intensive, so I want it to be deallocated when it gets discarded by the user. So, I wrote the following code:
- (MyView *)myView {
if (myView != nil)
return myView;
myView = [[UIView alloc] initWithFrame:CGRectZero]; // allocate memory if necessary.
// further init here
return myView;
}
- (void)discardView {
[myView discard]; // the discard methods puts the view offscreen.
[myView release]; // free memory!
}
- (void)showView {
view = [self myView];
// more code that puts the view onscreen.
}
Unfortunately, this methods only works the first time. Subsequent requests to put the view onscreen result in "message sent to deallocated instance" errors. Apparently, a deallocated instance isn't the same thing as nil. I thought about putting an additional line after [myView release] that reads myView = nil. However, that could result in errors (any calls to myView after that line would probably yield errors).
So, how can I solve this problem?
Setting myView to nil is the correct thing to do here. Not doing so is what's yielding errors, because it undetectably refers to a deallocated object. Your code tests for nil to see if it needs to create a new view, so you should set the variable appropriately.
You're going to have problems because your not using accessors. You need to define a property for the view. Then whenever you refer to view use the self-dot notation. If you do this then simply setting the view property to nil like this:
self.myView=nil;
... will trigger its release automatically.
However, this is bad way to manage the view especially if you load it from nib. The view most likely is a required property of a controller object. Setting it to nil invites crashes.
The better way is to have the view controller handle memory issues. On the iPhone you can put memory management code in viewDidDisappear: or didReceiveMemoryWarning. In any case you don't kill the view as long as the controller is alive but instead release the memory intensive parts of the view e.g. images. This leaves the view as a lightweight shell object. Then in 'viewWillAppear` you load the memory intensive parts back in.
However, the best way to handle this problem is to actually pop the view controller from a navigation stack. At that point the view controller cleans up after itself automatically.
Related
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
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.
I've subclassed some UITextField and added some custom properties.
In a UITableViewController, in the ViewDiDLoad I init them, and in the cellForRowAtIndexPath I add them to the cell with [cell.contentView addSubview:customTextField];
Each cell has a different customTextField as all of them are very different.
Where I should call the [customTextField release] ?
After I add them to the cell view ?
If for example I call [self.tableView reloadData] my customTextField are going to be added again to the cell, so maybe I should change my approach in doing this ?
thanks for the orientation ...
regards,
r.
You release an object when you no longer have any interest in it. This happens for many reasons; it might be because you've finished with the object, or have passed the control over the object lifetime to another object. It might be because you're about to replace the object with a fresh instance, it might be because you (the owner object) are about to die.
The last one seems to be relevant in your case. You create these object in viewDidLoad and repeatedly need them (i.e. to add them to cells) until your object is no longer functioning. In this case, just as you create them in viewDidLoad, you can release them in viewDidUnload.
Edit: I should really mention autorelease, but this isn't relevant in this instance. Memory management is best handled with a notion of 'owner' - the person who creates something (or retains it) should be responsible for deleting it (or releaseing in ObjC parlance). autorelease handle some cases where you need to give an object to an alternate owner, having previously owned it yourself (typically via a method return). If you are the creator, you can't just release it before returning it to the new owner, as it will be deleted before the new owner has a chance to stake an interest in it. However, you can't just not release it; it will leak. As such, the system provides a big list of objects that it will release on your behalf at some point in the future. You pass your release responsibility to this list, then return the object to the new owner. This list (the autorelease pool) ensures your release will occur at some point, but gives the new owner a chance to claim the object as theirs before it's released.
In your case, you have a clear interest in owning the objects for the lifetime of your view controller - you need to, at any time, be able to add them to view cells in response to a table data reload. You're only done with them when your view controller dies, so the viewDidUnload (or possibly dealloc) is the only sensible place to release them.
I always release my controls directly after I added them to a view using addSubView. When I work with tables, I also initialize them in the cellForRowAtIndexPath method.
Therefor the object stays alive the shortest time.
Adam Wright explains the theory of this very well, but let me give you some practice. You're thinking about this problem far too hard, and that almost always leads to bugs. There is a simple solution that solves this problem almost every time: Retain all ivars using accessors; don't retain non-ivars.
.h
#interface ... {
UITextField *_customTextField;
}
.m
#property (nonatomic, readwrite, retain) UITextField *customTextField;
...
#synthesize customTextField=_customTextField;
-(void)viewDiDLoad {
self.customTextField = [[[UITextField alloc] init....] autorelease];
}
...
- (void)dealloc {
// I do not recommend accessors in dealloc; but everywhere else I do
[_customTextField release]; _customTextField = nil;
}
Never access your ivars directly, except in dealloc (even that is controversial and some people recommend self.customTextField = nil; in dealloc; there are arguments either way). But never assign your ivars directly. If you will follow this rule, you will find that most of your memory problems go away.
The safest way to handle object ownership is to autorelease the view directly after initialization:
FooTextField* textField = [[[FooTextField alloc] init] autorelease];
[myCell.contentView addSubview:textField];
Adding the text field to a superview (the UITableViewCell's contentView) retains it. This way you don't have to care about releasing the view afterwards.
There seems to be a resentment against autorelease in the iPhone developer community. In my opinion, this resentment is unfounded. Autoreleasing an object adds very little overhead to the program if the objects lives longer than the current pass through the run loop.
Every object in Obj-C has a reference counter (retainCount), and when this counter goes to 0 the object is deallocated. When you allocate and initialize an object, the reference count is set to 1 - but you can retain it as many times you want to.
UITextField *textField = [[UITextField alloc] init]; // Reference counter = 1
[textField retain]; // Reference counter = 2
[textField retain]; // Reference counter = 3
The opposite of retain is release, which subtracts from the reference counter;
...
[textField release]; // Reference counter = 2
[textField release]; // Reference counter = 1
You can always get the reference counter of your objects;
printf("Retain count: %i", [textField retainCount]);
The method addSubview of UIView does retain your passed in sub view - and when it's done with it it releases it. If you need your UITextField later, in another scope (when the UIView is done with it and has released it) - you should not release it after you've added it to the super view. Most of the time you actually don't need to hold on to a reference, so you should release it after you've added it to the super view. If you dont - you can release it in the dealloc method of your scope.
Take a look at UITableView -dequeueReusableCellWithIdentifier: and -initWithStyle:reuseIdentifier:.
In -tableView:cellForRowAtIndexPath:, use -dequeueReusableCellWithIdentifier: and check if the result is nil. If it is, instantiate a new cell with -initWithStyle:reuseIdentifier:.
Send -autorelease to your customTextField upon creation and adding to the respective cell.
You should not add subview in cellForRowAtIndexPath! This will slow down the view as you add a subview each time the cell is displayed. Try using custom UITableViewCell class for that purpose.
Here is a perfect solution of UITableView customization
http://cocoawithlove.com/2009/04/easy-custom-uitableview-drawing.html
works jut perfectly
So I modified Apple's PageControl example to dynamically load various navigation controllers (along with their root view controllers) into the scroll view. I also added a technique that attempts to unload a navigation controller when it's no longer needed. I've only been at ObjC for a little over a month, so I'm not sure if I'm doing the unloading correctly. Please see my code below, followed by my questions.
First I create a mutable array and fill it with nulls, just like Apple does:
// Create dummy array for viewControllers array, fill it with nulls, and assign to viewControllers
NSMutableArray *array = [[NSMutableArray alloc] init];
for (unsigned i = 0; i <= kNumberOfPages; i++)
{
[array addObject:[NSNull null]];
}
self.viewControllers = array;
[array release];
...Later, I fill the array with UINavigationController objects like so (this is just partial code, please excuse the missing parts...the main idea is that I alloc a couple of things, assign them and then release):
id controller = [[classForViewController alloc] initWithNibName:NSStringFromClass(classForViewController) bundle:nil];
navController = [[UINavigationController alloc] initWithRootViewController:controller];
[controller release];
[self.viewControllers replaceObjectAtIndex:page withObject:navController];
[navController release];
...Finally, if a page doesn't need to be loaded anymore I do this:
[self.viewControllers replaceObjectAtIndex:i withObject:[NSNull null]];
Questions:
My understanding is that once I replace the navigation controller in my viewControllers array with null, the array releases the navigation controller. Thus the navigation controller's retain count hits zero and it no longer takes up memory. Is this correct?
What about the root view controller inside the navigation controller? Do I need to do anything with it or does it get released automatically once the navigation controller's retain count hit zero?
Thanks!
Yes. Any object put into a collection is sent a retain message. Likewise any object removed from a collection is sent a release message, the cause of the removal is irrelevant.
Yes, all objects will release all the objects it owns when they are released.
This all boils down to the simple principle of ownership that Cocoa defines:
You own the object if you received it as return value by calling a method that:
Is named alloc or new.
Contains the word copy, such as copy and mutableCopy.
You own the object if you call retain.
You may only call release and autorelease on objects you own.
You must release all owned objects in your dealloc methods.
There is just one exception; delegates are never owned. This is to avoid circular references and the memory leaks they cause.
As a side effect this also means that when you yourself are implementing a method, you must return an auto released object unless you are implementing new, or a method with copy in it's name. Objects returned as out arguments are always autoreleased.
Follow this strictly and Objective-C can be treated as if it is garbage collected 95% of the time.
Presumably yes, once your retain count reaches zero (however or whenever that happens) your object will receive the dealloc message. You can put a breakpoint to ensure that is happening. Instruments comes with a Leaks utility that should help you find memory problems, it's a great tool and I suggest using it frequently.
I'm not quite sure what you mean by "do anything to it". I presume you mean release it. The general pattern is that if you alloc or retain, you release. You can roughly guess if there is going to be a problem if your allocs and retains outnumber your releases (or vice versa, you don't want to double release).