I have a button with IBAction, which shows another window:
-(IBAction)someButtonClick:(id)sender
{
anotherView = [[NSWindowController alloc] initWithWindowNibName:#"AnotherWindow"];
[anotherView showWindow:self];
}
I worry about memory management in here. I allocate an object in this IBAction and don't released it. But how can i do it? If i released this object after showing, window will closing immediately.
The view is stored in an instance variable and you have access to it anywhere in your class. Release it in the code that dismisses the view.
Since anotherView is an instance variable you can release it in your dealloc method. But then you still have a memory leak, since every time your button is clicked a new instance of the window controller is created, but only the last one can be freed. You really should use accessors for this. Here is my suggestion:
- (NSWindowController *) anotherView;
{
if (nil == anotherView) {
anotherView = [[NSWindowController alloc] initWithWindowNibName:#"AnotherWindow"];
}
return anotherView;
}
- (void) setAnotherView: (NSWindowController *) newAnotherView;
{
if (newAnotherView != anotherView) {
[anotherView release];
anotherView = [newAnotherView retain];
}
}
- (void) dealloc;
{
[self setAnotherView: nil];
[super dealloc];
}
- (IBAction) someButtonClick: (id) sender;
{
[[self anotherView] showWindow: self];
}
If you use a Objective-C 2.0 property you don't have to write the setter.
And also you should rename your instance variable, the name should reflect what it is. And a View is not a Window Controller.
Related
I am trying to draw something in my custom view, but not sure why drawRect could not access its instance data. Here is the steps I tried.
Create a Mac OS X app, with using storyboard checked.
In the storyboard, delete the view, then add a new custom view under the view at the same place. (I tried if the view is not deleted, same).
Assign EEGView class to the newly added custom view.
then run. From the log information, you will notice that the drawRect could not access the instance data although the instance variables get initialized and updated.
In viewCtroller.m
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
myView = [[EEGView alloc] init];
//[self.view addSubview:myView];
//Start Timer in 3 seconds to show the result.
NSTimer* _timerAppStart = [NSTimer scheduledTimerWithTimeInterval:2
target:self
selector:#selector(UpdateEEGData)
userInfo:nil
repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:_timerAppStart forMode:NSDefaultRunLoopMode];
}
- (void)UpdateEEGData
{
// NSLog(#"UpdateEEGData.....1");
// myView.aaa = 200;
// myView.nnn = [NSNumber numberWithInteger:myView.aaa];
// make sure this runs on the main thread
if (![NSThread isMainThread]) {
// NSLog(#"NOT in Main thread!");
[self performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:TRUE];
}else
{
[self.view setNeedsDisplay:YES];
}
NSLog(#"UpdateEEGData.....2");
[myView setAaa:400];
myView.nnn = [NSNumber numberWithInteger:myView.aaa];
// make sure this runs on the main thread
if (![NSThread isMainThread]) {
// NSLog(#"NOT in Main thread!");
[self performSelectorOnMainThread:#selector(updateDisplay) withObject:nil waitUntilDone:TRUE];
}else
{
[self.view setNeedsDisplay:YES];
}
}
-(void)updateDisplay
{
[self.view setNeedsDisplay:YES];
}
In my custom view class EEGView.m
#implementation EEGView
#synthesize aaa;
#synthesize nnn;
-(id)init{
self = [super init];
if (self) {
aaa = 10;
nnn = [NSNumber numberWithInteger:aaa];
NSLog(#"init aaa: %i", aaa);
NSLog(#"init nnn: %i", [nnn intValue]);
}
return self;
}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
// Drawing code here.
NSLog(#"drawRect is here");
NSLog(#"drawRect aaa: %i", aaa);
NSLog(#"drawRect nnn: %i", [nnn intValue]);
}
#end
Did I miss anything? Tested in Xcode 7.2 & 7.2. But if I leave the 'using storyboard' unchecked, it works.
Or is it a Xcode bug?
Any advice appreciated.
Thanks in advance.
If you've added EEGView view on storyboard, you shouldn't be also instantiating one in viewDidLoad. You've alloc/init'ed a new one, but it bears no relationship to the one that the storyboard created for you. So, the one created by the storyboard has drawRect called, but you're setting the properties in the separate instance that you created in viewDidLoad which was never added to the view hierarchy (and thus never will have its drawRect called).
When the storyboard instantiates the view controller's view, it will instantiate your EEGView for you. All you need to do is to hook up an IBOutlet for this view in order to get a reference to it from your view controller. (For example, you can control drag from the EEGView in the storyboard scene to the #interface for the view controller that you've pulled up in the assistant editor.)
I'm trying to understand memory management to do some better apps, but was stopped at one point :
I use some UIButtons. So I alloc them, work with them etc. But i need to release them at one moment.
So I implement deallocmethod for all the object which are usefull all the time the UIViewController is on screen, and which need to be released when it desappeard. So in my UIViewController I implement :
-(void)dealloc
{
NSLog(#"Dealloc call");
[myButton release];
.... //Some objects release
[super dealloc];
}
But i never see the Dealloc call printed, so I think that it doesn't passed by the dealloc method when the UIViewController desappeard.
So, how does it work ? / What is false ?
Thanks !
EDIT : method to change of viewController :
-(void)myMethod
{
if (!nextviewcontroller)
{
nextviewcontroller = [[NextViewController alloc]init];
}
UIView *nextView = nextviewcontroller.view;
UIView *actualView = actualviewcontroller.view;
[actualviewcontroller viewWillAppear:NO];
[nextviewcontroller viewWillDisappear:NO];
[actualView removeFromSuperview];
[self.view addSubview:nextView];
[actualviewcontroller viewDidAppear:NO];
[nextviewcontroller viewDidDisappear:NO];
}
I want to create a similar class to UIAlertView which doesn't require a strong ivar.
For example, with UIAlertView, I can do the following in one of my UIViewController's methods:
UIAlertView *alertView = [[UIActionSheet alloc] initWithTitle:nil
message:#"Foo"
delegate:nil
cancelButtonTitle:#"Cancel"
otherButtonTitles:nil];
[alertView show];
... and the actionSheet will not be dealloced until it is no longer visible.
If I were to try to do the same thing:
MYAlertView *myAlertView = [[MYAlertView alloc] initWithMessage:#"Foo"];
[myAlertView show];
... the myAlertView instance will automatically be dealloced at the end of the current method I am in (e.g. right after the [myAlertView show] line).
What is the proper way to prevent this from happening without having to declare myView as a strong property on my UIViewController? (I.e. I want myView to be a local variable, not an instance variable, and I would like the MYAlertView instance to be in charge of its own lifecycle rather than my UIViewController controlling its lifecycle.)
Update: MYAlertView inherits from NSObject, so it cannot be added to the Views hierarchy.
UIAlertView creates a UIWindow, which it retains. The alert view then adds itself as a subview of the window, so the window retains the alert view. Thus it creates a retain cycle which keeps both it and its window alive. UIActionSheet works the same way.
If you need your object to stay around, and nothing else will retain it, it's fine for it to retain itself. You need to make sure you have a well-defined way to make it release itself when it's no longer needed. For example, if it's managing a window, then it should release itself when it takes the window off the screen.
If you add it as a subview of another view it will be retained. When the user selects and action or dismisses it, then it should call self removeFromSuperview as it's last act.
I've done my own AlertView with a little trick.
Just retain the object himself and release it on action. With this, you can call your custom alert vies as native one.
#import "BubbleAlertView.h"
#import <QuartzCore/QuartzCore.h>
#interface BubbleAlertView ()
...
#property (nonatomic, strong) BubbleAlertView *alertView;
...
#end
#implementation BubbleAlertView
...
- (id)initWithTitle:(NSString*)title message:(NSString*)message delegate:(id)delegate cancelButtonTitle:(NSString*)cancelButtonTitle okButtonTitle:(NSString*) okButtonTitle
{
self = [super init];
if (self)
{
// Custom initialization
self.alertView = self; // retain myself
//More init stuff
}
return self;
}
...
//SHOW METHOD
- (void)show
{
// We need to add it to the window, which we can get from the delegate
id appDelegate = [[UIApplication sharedApplication] delegate];
UIWindow *window = [appDelegate window];
[window addSubview:self.view];
// Make sure the alert covers the whole window
self.view.frame = window.frame;
self.view.center = window.center;
}
- (IBAction)btPressed:(id)sender
{
//Actions done
[UIView animateWithDuration:0.4f animations:^{
self.vContent.alpha = 0.f;
} completion:^(BOOL finished) {
[self.view removeFromSuperview];
self.alertView = nil; // deallocate myself
}];
}
You need to retain it somehow until it is released.
I do not really understand why you cannot implement it as subclass of UIView. Then you could use the view hierarchy as the keeper of a strong reference (retain +1). But you will have good reasons for not doing so.
If you don't have such a thing then I would use an NSMutableArray as class varialbe (meaning statc). Just declare it in the #interface block and initialize it with nil:
#interface
static NSMutableArray _allMyAlerts = nil;
provide an accessor.
-(NSMutableArray *) allMyAlerts {
if (_allMyAlerts == nil) {
_allMyAlerts = [[NSMutableArray alloc] init];
}
return _allMyAlerts
}
Within the init method do the following:
- (id) init {
self = [super init];
if (self) {
[[self allMyAlerts] addObject:self];
}
}
You will invode some method when the alert is dismissed.
- (void) dismissAlert {
// Do your stuff here an then remove it from the array.
[[self allMyAlerts] removeObject:self];
}
You may want to add some stuff to make it mutli threading save, which it is not. I just want to give an example that explains the concept.
allMyAlert could be an NSMutableSet as well. No need for an array as far as I can see. Adding the object to an array or set will add 1 to the retain count and removing it will reduce it by 1.
In my main ViewController I have the following code:
- (IBAction)listFunctions:(id)sender //button clicked
{
FunctionListController *functionListController = [[FunctionListController alloc] initWithWindowNibName:#"FunctionList"];
NSWindow *functionListWindow = [functionListController window];
[NSApp runModalForWindow: functionListWindow];
NSLog(#"done");
}
FunctionListController is the File's Owner of FunctionList.nib and a subclass of NSWindowController and implements the protocol NSWindowDelegate.
Here is the implementation of FunctionListController:
#implementation FunctionListController
- (id)initWithWindow:(NSWindow *)window
{
self = [super initWithWindow:window];
if(self)
{
// Initialization code here.
}
return self;
}
- (void)windowDidLoad
{
[super windowDidLoad];
// Implement this method to handle any initialization after your window controller's window has been loaded from its nib file.
self.window.delegate = self;
}
- (void)windowWillClose:(NSNotification *)notification
{
[NSApp stopModal];
}
#end
When the modal window is closed, the NSLog(#"done"); runs and displays, however after listFunctions is done, I get a EXC_BAD_ACCESS error.
With NSZombiesEnabled I get the error [NSWindow _restoreLevelAfterRunningModal]: message sent to deallocated instance.
Edit:
I am using ARC.
Try [functionListWindow setReleasedWhenClosed:NO] and hold a strong reference to your window until closing.
In your listFunctions method, you first create a FunctionListController object:
- (IBAction)listFunctions:(id)sender //button clicked
{
FunctionListController *functionListController = [[FunctionListController alloc] initWithWindowNibName:#"FunctionList"];
which is referenced through a local variable; it will be released at the end of the scope (the method itself);
you then get a reference to the functionListController window and run it as a modal:
NSWindow *functionListWindow = [functionListController window];
[NSApp runModalForWindow: functionListWindow];
This object will be retained by the NSApp.
However, the method exits (runModalForWindow will not block your thread) and functionListController is deallocated:
NSLog(#"done");
}
so you get a dangling reference and a modal window owned by an object which is not longer there. Hence, then crash.
Simply, make functionListController a strong property of your class and it will work.
Your new listFunctions would look like:
- (IBAction)listFunctions:(id)sender //button clicked
{
self.functionListController = [[FunctionListController alloc] initWithWindowNibName:#"FunctionList"];
...
Your functionListWindow is a local variable in your listFunctions: method. When that method finishes executing, you will lose any strong reference you have to that object, so nothing will own it and it will be deallocated. When your modal window actually closes, it tries to send the appropriate message to its delegate, however this no longer exists.
Have you tried making functionListWindow an instance variable on your main view controller?
I have one viewController called "setTimeViewController". I have another view controller called "setEventViewController." users tap a row in a table in setTimeViewController and are sent to setEventViewController that only contains a UIPickerView and a save button. When the user taps the save button, it takes them back to setTimeViewController.
I want the value that is chosen in that picker from setEventViewController to be able to be accessed from setTimeViewController but is returning (null) to me. I have declared a NSString *theVariable in .h setEventViewController which is the variable I am trying to retrieve from the other view controller and retained its property but it is still null.
I have done a test (NSLog the variable) from viewDidDisappear in setEventViewController to see if it is null when the view is disappearing but it works as it should.
Here is my code, if anyone can help me I would forever be grateful. Thank you!
setEventViewController.m
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
theVariable= [currentItemsInPicker objectAtIndex:row];
//[theVariable retain]; //Tried this as well but did not work
}
-(IBAction) Save
{
//simply dismisses the view controller back to setTimeViewController. Have also tried to set another NSString equal to theVariable but this did not work.
[self.navigationController popViewControllerAnimated:YES];
}
setTimeViewController
-(void) retrieveTheEvent
{
setEventViewController *eventViewController= [[setEventViewController alloc] init];
NSString *testString= eventViewController.theVariable;
NSLog (#"the event is %#", testString); //shows null
}
You are allocating different object of setEventViewController in retrieveTheEvent if I am not wrong. You are facing this problem because this newly allocated object is different than you have pushed.
Instead of use the same object that you have push to navigation controller.
One solution:
Create global object your setEventViewController(i.e. I mean create iVar for it) and use same reference to push view controller in didSelectRow. And use same iVar for accessing your theVariable.
Add below code in setTimeViewController.h
setEventViewController *eventViewController;
Please also create property for it.
Now in setTimeViewController.m
Now use existing reference of setEventViewController to push view controller. like
eventViewController= [[setEventViewController alloc] init];
[self.navigationController pushViewController: eventViewController animated:YES];
Change this method
-(void)retrieveTheEvent
{
NSString *testString= eventViewController.theVariable;
NSLog (#"the event is %#", testString); //shows null
}
Adding another solution to Armaan's list.
Create a delegate in setEventViewController.m and pass "theVariable" to setTimeViewController.m before calling
[self.navigationController popViewControllerAnimated:YES];
I'm giving an example.
setEventViewController.h
#protocol setEventViewControllerDelegate;
#interface setEventViewController : UIViewController
{
NSString* theVariable;
id<setEventViewControllerDelegate> delegate;
}
#end
#protocol setEventViewControllerDelegate <NSObject>
#optional
-(void)theVariableChanged:(NSString*)theNewValue;
#end
setEventViewController.m
#synthesize delegate;
- (void)pickerView:(UIPickerView *)pickerView
didSelectRow:(NSInteger)row inComponent:(NSInteger)component
{
theVariable= [currentItemsInPicker objectAtIndex:row];
// this is where the new value is passed to setTimeViewController
if(delegate && [delegate respondsToSelector:#selector(theVariableChanged)])
{
[delegate theVariableChanged:theVariable];
}
}
-(IBAction) Save
{
[self.navigationController popViewControllerAnimated:YES];
}
-(void) dealloc
{
// very important
self.delegate = nil;
}
setTimeViewController.h
#import "setEventViewController.h"
#interface setTimeViewController : UIViewController <setEventViewControllerDelegate>
{
// your members
}
#end
setTimeViewController.m
-(void)openSetEventView
{
setEventViewController *eventViewController= [[setEventViewController alloc] init];
// set the delegate
eventViewController.delegate = self;
[self.navigationController pushViewController: eventViewController animated:YES];
[eventViewController release];
}
// get the new value here
-(void)theVariableChanged:(NSString*)theNewValue
{
NSLog (#"the event is %#", theNewValue);
}
Check out Singletons,
They can be your best friend when doing something like this.
Singletons link here
I tried all different suggestion to pass NSString from one viewController to the other, including customer initialise method, and change the property to strong, retain, non of them work for me.
At last singleton to share the data across the project fixed the problem. http://www.galloway.me.uk/tutorials/singleton-classes/
Thanks #David Evans