I am having a problem with memory management in objective C. Ive been reading through the Advanced Memory Management Programming Guide but I cannot find a solution to my problem with the possible exception of abandoning ARC altogether and managing the memory manually.
Here is the problem:
I have a Controller class that Ive made, that holds information on what to do at specific times.
The Controller class tells the rest of the app to play a video (for example). The video plays fine. When the video finishes playing, the Controller class knows what to do next.
Unfortunately the Controller class is released and deallocated by ARC almost as soon as the video starts playing. So by the time the video ends, the app calls the Controller class to see what it should do next, and the whole thing crashes. I get an EXC_BAD_ACCESS because the class is no longer in memory.
I get that ARC is releasing my Controller class because after it has told the video to start playing, its not doing anything. But I want to keep hold of that class until I need it again.
I am declaring this class as a property, like so:
#property (strong, nonatomic) Controller * controller;
But despite this, ARC keeps releasing the class as soon as its not doing anything.
EDIT:
Ive moved this property into the App Delegate. But ARC is still releasing it. I cant turn this into a Singleton, as I need the potential to have multiple copies of this class.
How can I stop ARC releasing objects when I dont want it to??
Is it possible to keep an object in memory while its not doing anything?
Is this possible at all? Or should I abandon ARC and just do memory management manually?
Use a singleton pattern so that Controller looks after its own lifetime and exists app-wide. This shared instance will exist from when it's first requested until the app terminates and ARC will not release it arbitrarily.
Controller.h:
#interface Controller : NSObject
+ (Controller *)sharedInstance;
#end
Controller.m:
#import "Controller.h"
static Controller *_instance = nil;
static dispatch_once_t _onceToken = 0;
#implementation Controller
+ (Controller *)sharedInstance {
dispatch_once(&_onceToken, ^{
_instance = [[Controller alloc] init];
});
return _instance;
}
// You could add this if you want to explicitly destroy the instance:
+ (void)destroy {
_instance = nil;
_onceToken = 0;
}
Your controller is getting dealloc'ed when the detailViewController is dealloc'ed. Hence, you must move the handle of your controller and define in it the any of the following :
MasterViewController or your application's RootViewController
OR
AppDelegate
OR
Create a singleton as answered by "trojanfoe"
I stumbled upon this case several times when working with UITableViews. I created a private #property (strong) id *yourObjectRetain and assigned my object to it. An array for multiple objects will also work.
Related
I am having a problem with memory management in objective C. Ive been reading through the Advanced Memory Management Programming Guide but I cannot find a solution to my problem with the possible exception of abandoning ARC altogether and managing the memory manually.
Here is the problem:
I have a Controller class that Ive made, that holds information on what to do at specific times.
The Controller class tells the rest of the app to play a video (for example). The video plays fine. When the video finishes playing, the Controller class knows what to do next.
Unfortunately the Controller class is released and deallocated by ARC almost as soon as the video starts playing. So by the time the video ends, the app calls the Controller class to see what it should do next, and the whole thing crashes. I get an EXC_BAD_ACCESS because the class is no longer in memory.
I get that ARC is releasing my Controller class because after it has told the video to start playing, its not doing anything. But I want to keep hold of that class until I need it again.
I am declaring this class as a property, like so:
#property (strong, nonatomic) Controller * controller;
But despite this, ARC keeps releasing the class as soon as its not doing anything.
EDIT:
Ive moved this property into the App Delegate. But ARC is still releasing it. I cant turn this into a Singleton, as I need the potential to have multiple copies of this class.
How can I stop ARC releasing objects when I dont want it to??
Is it possible to keep an object in memory while its not doing anything?
Is this possible at all? Or should I abandon ARC and just do memory management manually?
Use a singleton pattern so that Controller looks after its own lifetime and exists app-wide. This shared instance will exist from when it's first requested until the app terminates and ARC will not release it arbitrarily.
Controller.h:
#interface Controller : NSObject
+ (Controller *)sharedInstance;
#end
Controller.m:
#import "Controller.h"
static Controller *_instance = nil;
static dispatch_once_t _onceToken = 0;
#implementation Controller
+ (Controller *)sharedInstance {
dispatch_once(&_onceToken, ^{
_instance = [[Controller alloc] init];
});
return _instance;
}
// You could add this if you want to explicitly destroy the instance:
+ (void)destroy {
_instance = nil;
_onceToken = 0;
}
Your controller is getting dealloc'ed when the detailViewController is dealloc'ed. Hence, you must move the handle of your controller and define in it the any of the following :
MasterViewController or your application's RootViewController
OR
AppDelegate
OR
Create a singleton as answered by "trojanfoe"
I stumbled upon this case several times when working with UITableViews. I created a private #property (strong) id *yourObjectRetain and assigned my object to it. An array for multiple objects will also work.
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
Early warning - code sample a little long...
I have a singleton NSMutableArray that can be accessed from anywhere within my application. I want to be able to reference the NSMutableArray from multiple NIB files but bind to UI elements via NSArrayController objects. Initial creation is not a problem. I can reference the singleton NSMutableArray when the NIB gets loaded and everything appears fine.
However, changing the NSMutableArray by adding or removing objects does not kick off KVO to update the NSArrayController instances. I realize that "changing behind the controller's back" is considered a no-go part of Cocoa-land, but I don't see any other way of programmatically updating the NSMutableArray and letting every NSArrayController be notified (except it doesn't work of course...).
I have simplified classes below to explain.
Simplified singleton class header:
#interface MyGlobals : NSObject {
NSMutableArray * globalArray;
}
#property (nonatomic, retain) NSMutableArray * globalArray;
Simplified singleton method:
static MyGlobals *sharedMyGlobals = nil;
#implementation MyGlobals
#synthesize globalArray;
+(MyGlobals*)sharedDataManager {
#synchronized(self) {
if (sharedMyGlobals == nil)
[[[self alloc] init] autorelease];
}
return sharedMyGlobals;
}
-(id) init {
if(self = [super init]) {
self.globals = [[NSMutableArray alloc] init];
}
return self
}
// ---- allocWithZone, copyWithZone etc clipped from example ----
In this simplified example the header and model for objects in the array:
Header file:
#interface MyModel : NSObject {
NSInteger myId;
NSString * myName;
}
#property (readwrite) NSInteger myId;
#property (readwrite, copy) NSString * myName;
-(id)initWithObjectId:(NSInteger)newId objectName:(NSString *)newName;
#end
Method file:
#implementation MyModel
#synthesize myId;
#synthesize myName;
-(id)init {
[super init];
myName = #"New Object Name";
myId = 0;
return self;
}
#end
Now imagine two NIB files with appropriate NSArrayController instances. We'll call them myArrayControllerInNibOne and myArrayControllerInNib2. Each array controller in the init of the NIB controller sets the content of the array:
// In NIB one init
[myArrayControllerInNibOne setContent: [[MyGlobals sharedMyGlobals].globalArray];
// In NIB two init
[myArrayControllerInNibTwo setContent: [[MyGlobals sharedMyGlobals].globalArray];
When each NIB initializes the NSArrayController binds correctly to the shared array and I can see the array content in the UI as you would expect. I have a separate background thread that updates the global array when content changes based on an external event. When objects need to be added in this background thread, I simply add them to the array as follows:
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
This is where things fall apart. I can't call a willChangeValueForKey and didChangeValueForKey on the global array because the shared instance doesn't have a key value (should I be adding this in the singleton class?)
I could fire off an NSNotification and catch that in the NIB controller and either do a [myArrayControllerInNibOne rearrangeObjects]; or set the content to nil and reassign the content to the array - but both of these seems like hacks and. moreover, setting the NSArrayController to nil and then back to the global array causes a visual flash within the UI as the content is cleared and re-populated.
I know I could add directly to the NSArrayController and the array gets updated, but I don't see a) how the other NSArrayController instances would be updated and b) I don't want to tie my background thread class explicitly to a NIB instance (nor should I have to).
I think the correct approach is to either fire off the KVO notification somehow around the addObject in the background thread, or add something to the object that is being stored in the global array. But I'm at a loss.
As a point of note I am NOT using Core Data.
Any help or assistance would be very much appreciated.
Early warning - answer a little long…
Use objects that model your domain. You have no need for singletons or globals, you need a regular instance of a regular class. What Objects are your storing in your global array? Create a class that represents that part of your model.
If you use an NSMutableArray as storage it should be internal to your class and not visible to outside objects. eg if you are modelling a zoo, don't do
[[[MyGlobals sharedMyGlobals].globalArray] addObject:tomTheZebra];
do do
[doc addAnimal:tomTheZebra];
Dont try to observe a mutable array - you want to observe a to-many property of your object. eg. instead of
[[[MyGlobals sharedMyGlobals].globalArray] addObserver:_controller]
you want
[doc addObserver:_controller forKeyPath:#"animals" options:0 context:nil];
where doc is kvo compliant for the to-many property 'anaimals'.
To make doc kvo compliant you would need to implement these methods (Note - you don't need all these. Some are optional but better for performance)
- (NSArray *)animals;
- (NSUInteger)countOfAnimals;
- (id)objectInAnimalsAtIndex:(NSUInteger)i;
- (id)AnimalsAtIndexes:(NSIndexSet *)ix;
- (void)insertObject:(id)val inAnimalsAtIndex:(NSUInteger)i;
- (void)insertAnimals:atIndexes:(NSIndexSet *)ix;
- (void)removeObjectFromAnimalsAtIndex:(NSUInteger)i;
- (void)removeAnimalsAtIndexes:(NSIndexSet *)ix;
- (void)replaceObjectInAnimalsAtIndex:(NSUInteger)i withObject:(id)val;
- (void)replaceAnimalsAtIndexes:(NSIndexSet *)ix withAnimals:(NSArray *)vals;
Ok, that looks pretty scary but it's not that bad, like i said you don't need them all. See here. These methods dont need to be part of the interface to your model, you could just add:-
- (void)addAnimal:(id)val;
- (void)removeAnimal:(id)val;
and write them in terms of the kvc accessors. The key point is it's not the array that sends notifications when it is changed, the array is just the storage behind the scenes, it is your model class that send the notifications that objects have been added or removed.
You may need to restructure your app. You may need to forget about NSArrayController altogether.
Aaaaaannnnnyyywaaayyy… all this gets you nothing if you do this
[[[MyGlobals sharedMyGlobals].globalArray] addObject:theNewObject];
or this
[doc addAnimal:tomTheZebra];
from a background thread. You can't do this. NSMutableArray isn't thread safe. If it seems to work then the best that will happen is that the kvo/binding notification is delivered on the background as well, meaning that you will try to update your GUI on the background, which you absolutely cannot do. Making the array static does not help in any way i'm afraid - you must come up with a strategy for this.. the simplest way is performSelectorOnMainThread but beyond that is another question entirely. Threading is hard.
And about that static array - just stop using static, you don't need it. Not because you have 2 nibs, 2 windows or anything. You have an instance that represents your model and pass a pointer to that to you viewControllers, windowControllers, whatever. Not having singletons/static variables helps enormously with testing, which of course you should be doing.
main.m
#import <Cocoa/Cocoa.h>
int main(int argc, char *argv[])
{
return NSApplicationMain(argc, (const char **) argv);
}
CoolClass.h
#import <Cocoa/Cocoa.h>
#interface CoolClass : NSObject <NSApplicationDelegate> {
}
- (void) applicationDidFinishLaunching : (NSNotification *) aNotification;
#end
CoolClass.m
#import "CoolClass.h"
#implementation CoolClass
- (void) applicationDidFinishLaunching : (NSNotification *) aNotification {
NSLog(#"THIS SHOULD BE PRINTED TO THE DEBUG CONSOLE");
}
#end
I tried this with "applicationWillFinishLaunching" as well, but still no luck. Any help at all would be appreciated. Thanks.
What you're missing is that adopting the protocol makes objects of kind CoolClass ready to function as delegates of any NSApplication object (provided you follow through on the declaration and implement all required methods of the protocol). Declaring conformance to the protocol also prevents compiler warnings when you set instances of the class as an application's delegate.
But for a specific application object (say, the shared NSApplication object that Cocoa creates for you) to know to send messages from the protocol to a specific CoolClass object, you must set the object you want to receive those messages as the specific application object's delegate.
What this means is that some time before the messages you want to receive would be sent by the application, something needs to instantiate a CoolClass object - call it c - and tell the application, "Hey, your delegate is c over here, so send delegate messages to the little feller from now on."
What that boils down to is that these lines of code must execute before the application finishes launching:
CoolClass *c = [[CoolClass alloc] init];
[[NSApplication sharedApplication] setDelegate:c];
The easiest way to have this happen is to let Interface Builder do the work for you: let the MainMenu nib instantiate your CoolClass and also set the cool class object as the application's delegate when the nib is loaded, as others have suggested.
To do so, open MainMenu.xib. Drag a Custom Object into the xib and change its class to CoolClass in the inspector. Ctrl-drag (or right-click drag) from the application object in the xib to the CoolClass object and choose "delegate". Save, build, and run.
You should define your CoolClass as applications delegate in Interface Builder (Ctrl+Drag from App instance to your CoolClass instance
applicationDidFinishLaunching is an instance method, not a class method. That is, you'll need an instantiation of your class to receive that message. Plus, it can't be just -any- instantiation; your application needs to know about your instantiation and know that it's supposed to send delegate messages to it. The easiest and most common way to do this is...
First, you'll instantiate your CoolClass. Open your application's MainMenu.nib file in Interface Builder. Drag an "Object" (it'll look like a blue cube) out of the Library window. Select it and use the Identity tab of the Inspector to change its class from NSObject to CoolClass. Now, you have an instance of your CoolClass.
Now, you'll set that instance as the application's delegate. Control-drag from "Application" (still in Interface Builder) to your new instance of CoolClass. A window will pop up (showing outlets of Application that could be connected to your object). Choose "delegate". Now your application has an instance of your CoolClass set as its delegate, and thus, your applicationDidFinishLaunching will run.
I appreciate you may be trying to learn from scratch, but why did you not just create a new project using one of the XCode templates? It sets all this up for you to begin with. Life involves enough debugging without having to add more atop it!
To start at the beginning: your call to NSApplicationMain should be wrapped in an NSAutoreleasePool. You will be in trouble if you don't do that.
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)?