What I call 'release' instead of 'close' on a NSWindow? - objective-c

When I'm done with a popup NSWindow, I invoke the following lines of code:
if (imageDroppedActionPopup) {
[[self window] removeChildWindow:imageDroppedActionPopup];
[imageDroppedActionPopup orderOut:nil];
[imageDroppedActionPopup close];
imageDroppedActionPopup = nil;
}
I'm now wondering if I can replace
[imageDroppedActionPopup close];
with
[imageDroppedActionPopup release];
It seems to work correctly. But I was wondering if it is correct.
The reason I'm doing this, is that if I use close for some reason the app focus come back to the main document window, and not the current NSWindowController window, which is the above mentioned popup parent.

close removes the window from screen, release decreases object's retain counter and may or may not cause it to be deallocated.
It seems to work because being deallocated implies being removed from screen, but these methods can never replace one another. Even if you are the only owner you can never be sure that release will cause immediate deallocation (and therefore nor immediate disappearance of the window), because the window might be temporary retained by someone else.
When you need the window to close, call close. And you must only call release on objects which you have retained, allocated or copied, strictly according to memory management rules.
Considering that replacing close with release did not cause crash, I think you need both.

You don't need to use close. Calling orderOut: will close the window. You should do this before removing it as a child window. This will prevent the window ordering problems that you're seeing.
Just do this:
if (imageDroppedActionPopup)
{
[imageDroppedActionPopup orderOut:nil];
[[self window] removeChildWindow:imageDroppedActionPopup];
[imageDroppedActionPopup release];
imageDroppedActionPopup = nil;
}
Note that I'm still calling release on the window, before setting it to nil.

Related

Opening Modal Sheets with 10.9

I have an app that uses multiple Modal Sheets for data entry. The methods in opening the modal sheets worked fine, and still work fine, but they have been deprecated and I fear they will soon not work with future releases of Xcode. Here, Apple points out how to use modal sheets,
- (void)showCustomSheet: (NSWindow *)window
// User has asked to see the custom display. Display it.
{
if (!myCustomSheet)
//Check the myCustomSheet instance variable to make sure the custom sheet does not already exist.
[NSBundle loadNibNamed: #"MyCustomSheet" owner: self];
[NSApp beginSheet: myCustomSheet
modalForWindow: window
modalDelegate: self
didEndSelector: #selector(didEndSheet:returnCode:contextInfo:)
contextInfo: nil];
// Sheet is up here.
// Return processing to the event loop
}
but with the release of Xcode 5.1, they identify that the loadNibNamed method has been deprecated and that we should use a similar function referencing top-level objects.
The problem I am having, is changing this:
[NSBundle loadNibNamed:#"OrderDetailsWindow" owner:self];
into this.
NSArray *array;
[[NSBundle mainBundle]loadNibNamed:#"OrderDetailsWindow" owner:self topLevelObjects:&array];
This method call does in fact open the modal sheet. However, at the end of my method that opens the modal sheet, Xcode hangs-up with this error.
0x7fff8c33b097: andl 24(%r11), %r10d Thread1: EXC_BAD_ACCESS (code:EXC_I386_GPFLT)
I'm not sure what this is telling me. It doesn't give me any information in the debug area. Could this have to do with the topLevelObjects array not being released properly? Any thoughts on how to make this work a little more smoothly? Apple's out-of-date library is driving me nuts!
Yes, Apple's documentation is a mess. The "Sheet Programming Topics" document has not been updated since 2009.
You don't show the full code after the change but my guess is that your problem is with the memory management of your NIB's objects.
From the documentation of the new loadNibNamed:owner:topLevelObjects:
Unlike legacy methods, the objects adhere to the standard cocoa memory
management rules; it is necessary to keep a strong reference to them
by using IBOutlets or holding a reference to the array to prevent the
nib contents from being deallocated.
Outlets to top-level objects should be strong references to
demonstrate ownership and prevent deallocation.
You have the NSArray that holds the top level objects inside your method. Once the execution leaves this method, the NSArray will be derefernced and released and so are all your top level objects if those are not strongly referenced anywhere else.
You need to either connect your top level objects in the NIB to outlets in your Window Controller or keep the NSArray as a member variable of your Window Controller instance, so it doesn't get released once your sheet showing method exits. And make sure that myCustomSheet properly declared and connected from the sheet's NIB.
Also, [NSApp beginSheet:] is deprecated as well, you now call beginSheet on an instance of NSWindow.
I always use a NSWindowController subclass with a custom delegate for my sheets:
From the window that wants to display the sheet:
_myModalController = [[MyModalController alloc] init];
_myModalController.delegate = self;
[_myModalController beginSheet:self.window];
Then within the modal window controller, I have:
- (id)init {
self = [super initWithWindowNibName:#"MyModalWindow" owner:self];
return self;
}
- (void)beginSheet:(NSWindow *)mainWindow {
[NSApp beginSheet:[self window]
modalForWindow:mainWindow
modalDelegate:self
didEndSelector:#selector(_didEndSheet:returnCode:contextInfo:)
contextInfo:nil];
}
- (void)endSheet:(NSWindow *)mainWindow {
[NSApp endSheet:[self window]];
[[self window] orderOut:mainWindow];
}
This appears to avoid the whole issue of loadNibNamed: becoming deprecated.

Closing Window and Releasing NSWindowController

I have a relatively-lengthy task. So I bring up a separate window (NSWindowController) from AppDelegate to show progress. It goes like
//AppDelegate.m
if (self.progresswindow == nil) {
self.progresswindow = [[ProgressController alloc] initWithWindowNibName:#"ProgressController"];
}
[progresswindow showWindow:self];
//[[progresswindow window] setReleasedWhenClosed:NO];
[NSApp runModalForWindow:progresswindow.window];
When a task is complete, the progress window will close itself.
//ProgressController.m
[NSApp stopModal];
[self close];
It works fine. But when I click on a button to start another session of a task with the same window, the application won't run a task although it opens. It appears that the last instance hasn't be released. The progress window has the following lines.
- (void)windowDidLoad {
NSLog(#"Hey!");
}
And NSLog won't be called for the 2nd time. I wonder what I'm doing wrong? Calling setReleasedWhenClosed from AppDelegate has no effect. I have the Release When Closed checkbox enabled, anyway. I read something like I need to observe NSWindowWillCloseNotification the progress window in a different topic so that I can release it when it closes. But I'm using ARC. So I can't manually release it, can I? Meanwhile, if I open Apple's sample (TableViewPlayground), it seems that they use this notification. Furthermore, I've read this topic and this topic. But I don't know what the problem is.
I appreciate any advice. Thank you for your time.
Release the Progress-Window-Controller.

Modal view using UIModalPresentationFormSheet stays in memory (ARC)

I'm presenting a modal view with the UIModalPresentationFormSheet presentation style.
MPMediaItemCollection *albumItem = [self.albums objectAtIndex:index];
AlbumViewController *destination = [[AlbumViewController alloc] initWithAlbum:albumItem];
destination.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentModalViewController:destination animated:NO];
When the user taps outside the modal view, it is closed. However, it's still in memory, it's not released.
What's the correct way to deal with this?
Edit: is seems there's something inside AlbumViewController that's not being released properly. A bug on my end.
Yes, this is correct. ARC will take care of deallocating the memory when it is needed.
From here:
ARC deallocs any object to which there are no more strong references. So to dealloc something, simply set all the variables pointing to it to nil and make sure the object is not involved in any circular reference.

ViewController respondsToSelector: message sent to deallocated instance (CRASH)

Ok, here is the deal, I hate putting out questions about my debugging and crashes. Because I usually handle them myself, but I just cannot get my way around this, even after viewing multiple questions already.
Ok so here is the problem, I find my app randomly on and off crashing with this stack trace:
*** -[ViewController respondsToSelector:]: message sent to deallocated instance 0x1e5d2ef0
Where ViewController can vary, sometimes the place where my code crashes, has NO relevance to that particular ViewController and doesn't own or call it.
Also, to get that console trace, I have enabled Zombies, otherwise I would get no console print at all, I would only get: objc_msgSend, which I know means I am messaging something that is released. But I cannot find where that is... I am really stuck! Usually I always debug my crashes, so I am really stuck on this.
Again, this crashes in different places at different times, on and off. And the place it crashes has almost no relevance to the ViewController. And I find this very confusing.
Do you need any of my code? I have a lot of files and since it is crashing in different places, distributing my code will be a mess!
I have tried to add symbolic breakpoints with no luck, and Zombies is not available on the Instruments application for iOS. I cannot run my app on the simulator as it has unsupportive architecture frameworks for it.
Thanks everyone...
Use Instruments to track down deallocated instance errors. Profile your application (Cmd ⌘+I) and choose Zombies template. After your application is running, try to crash it. You should get something like that:
Click on the arrow next to address in the popover to show object that was called after it was deallocated.
You should see now every call that has changed retain count of this object. This could be because sending directly retain/release messages as well as draining autorelease pools or inserting into NSArrays.
RefCt column shows retainCount after action was invoked and Responsible Caller shows class name and method in which it was performed. When you double click on any retain/release, instruments will show you line of code where this was performed (If this isn't working, you can examine call by selecting it and choosing its counterpart in Extended Detail pane):
This will let you examine all the retainCount lifecycle of object and probably you'll find your problem right away. All you got to do is find missing retain for latest release.
had a similar problem. In my case a viewController needed to get navigationController events, so it was registering as the navigation controller delegate:
self.navigationController.delegate = self;
The crash occurs when that controller was dealloc'ed but was still the delegate for the view controller. Adding this code in dealloc had no effect:
-(void) dealloc
{
if (self.navigationController.delegate == self)
{
self.navigationController.delegate = nil;
}
because at the point that dealloc is called, the view controller has already been removed from the view hierarchy, so self.navigationController is nil, so the comparison is guaranteed to fail! :-(
The solution was to add this code to detect the VC leaving the view hierarchy just before it actually does so. It uses a method introduced in iOS 5 to determine when the view is being pop'ed and not pushed
-(void) viewWillDisappear:(BOOL) animated
{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController])
{
if (self.navigationController.delegate == self)
{
self.navigationController.delegate = nil;
}
}
}
No more crashes!
For anyone who can't solve it, here are some other techniques:
https://stackoverflow.com/a/12264647/539149
https://stackoverflow.com/a/5698635/539149
https://stackoverflow.com/a/9359792/539149
https://stackoverflow.com/a/15270549/539149
https://stackoverflow.com/a/12098735/539149
You can run Instruments in Xcode 5 by clicking the project popup->Edit Scheme...Profile ->Instrument and choose Allocations or Leaks, then profile your app, then stop Instruments, click the info button in Allocations and "Enable NSZombie Detection".
However, for the messages that come directly from the com.apple.main-thread, this probably won't reveal anything.
I banged my head on this for over two hours and the answer turned out to be an over-release, which I discovered by commenting out a copy of my project by brute force until I found the culprit:
[viewController release];
viewController = NULL;
The problem is that release doesn't set the variable to NULL.
That means that setting it to NULL calls release again, decrementing the refcount and freeing the memory immediately until later when the variables that reference viewController are finished with it.
So either enable ARC or make sure your project consistently uses release or NULL but not both. My preference is to use NULL because then there is no chance of referencing a zombie but it makes finding where objects are released more difficult.
I had met the same problem in iOS yesterday. I have made IAP in App "About" subview, and I have added Transaction Observer in "About" viewDidLoad. When I purchase for the first time, no problem, but after I back to main window and enter about subview to purchase again, the problem "message sent to deallocated instance" happened, and the App crashed.
- (void)viewDidLoad
{
[[SKPaymentQueue defaultQueue] addTransactionObserver:self]; object:nil];
}
After I remove Transaction Observer in dealloc, the problem is solved.
- (void)dealloc
{
// Even though we are using ARC, we still need to manually stop observing any
// NSNotificationCenter notifications. Otherwise we could get "zombie" crashes when
// NSNotificationCenter tries to notify us after our -dealloc finished.
[[SKPaymentQueue defaultQueue] removeTransactionObserver:self];
}
I had a very similar issue and I figured out it was due to navigation controller delegates set.
The below solved my issue,
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (self.navigationController.delegate != self) {
self.navigationController.delegate = self;
}
}
-(void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
if (self.navigationController.delegate == self) {
self.navigationController.delegate = nil;
}
}
Had the same problem in OS X.
To solve this not enough - (void)dealloc method as #SoftwareEvolved already said. But unfortunately - (void)viewWillDisappear is available only on version 10.10 and later.
I introduced custom method in my NSViewController subclass where set all the zombie-dangerous references to nil. In my case that was NSTableView properties (delegate and dataSource).
- (void)shutdown
{
self.tableView.delegate = nil;
self.tableView.dataSource = nil;
}
That's all. Each time I'm about to remove view from the superview need call this method.
I had the same Problem.It was difficult to find which delegate cause issue, because it does not indicate any line or code statement So I have try some way, Maybe it becomes helpful to you.
Open xib file and from file's owner, Select "show the connections inspector" right hand side menu. Delegates are listed, set them to nil which are suspected.
(Same as my case)Property Object like Textfield can create issue, So set its delegates to nil.
-(void) viewWillDisappear:(BOOL) animated{
[super viewWillDisappear:animated];
if ([self isMovingFromParentViewController]){
self.countryTextField.delegate = nil;
self.stateTextField.delegate = nil;
}
}

How to release a "PopUp" view"?

I have this class that shows a popup.
I do a alloc-init on it and it comes up.
DarkVader* darkPopUp = [[DarkVader alloc] init:theButton helpMessage:[theButton.titleLabel.text intValue] isADay:NO offset:0];
It shows itself and if the user presses Ok it disappears. When do I release this?
I could do a [self release] in the class when the OK button is pressed. Is this correct?
If I do this the Analyzer says it has a retain count of +1 and gets leaked in the calling function.
If I release it just after the alloc-init the Analyzer says it has a retain count of +0 and i should not release it.
DLog(#"DarkVader retain count: %i", [darkPopUp retainCount]);
says it has a retain count of 2. I'm confused.
In short my question is: How do I release an object that gets initialized does some work and ends but no one is there to release it in the calling function.
My suggestion would be to use
[self autorelease];
when the view is closing itself. Although if you look at various standard views, then all implement callbacks to a delegate that becomes responsible for closing them; this let's the launching object be responsible for releasing the view as well. You also don't make it clear how your view (or is it a view controller) is displayed.
You could do something similar to what existing Cocoa Touch classes does. For example, see how you show an UIAlertView:
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"title" message:#"message" delegate:nil cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
[alert release];
It's quite clear that UIAlertView does a [self retain], or more likely, gets retained when added as a subview somewhere on the screen somewhere in the show method.
There are some Cocoa Touch classes that indeed (just as Paul mentioned) do not support this way of release at once, but instead calls a delegate method and excepts the receiver to release it.
I'd say the answer is, if your DarkVader is an UIView, you should let the subview-retain take care of the retain count. If it's a UIViewController or a custom helper class, you have a few options, the delegate way being a simple and straight forward one.
If you want a custom pop-up in the style you described you should probably already be subclassing UIAlertView to begin with. Then you can use it's already implemented retain/release functionality.