NSView/NSViewController memory management - objective-c

In a very simple test app, I have an NSViewController (strongly retained) in the appdelegate. I put this NSView inside the contentView of my NSWindow (which I have set to Release on Close in Interface Builder). But, when I exit the app, the NSView's dealloc method is never called. I would have expected it to be called by the following flow - NSWindow dealloc -> removes content view -> removes all subviews.
Also, TestViewController is not dealloced, unless I set the strong reference to it, to be nil in AppDelegate's applicationWouldTerminate method. Again, I would have expected it to be dealloced. But, it looks like AppDelegate is never dealloced.
I must be missing something basic in my understanding of Objective-C memory management. Could it be because on Mavericks Apple does a force quit of apps and hence there is no cleanup? I would appreciate being pointed in the right direction on this. Thanks
My Code
#import "AppDelegate.h"
#interface TestView : NSView
#end
#implementation TestView
- (void)dealloc { NSLog(#"TestView - Dealloc"); }
#end
#interface TestViewController : NSViewController
#end
#implementation TestViewController
- (void)loadView { self.view = [[TestView alloc] init]; }
- (void)dealloc { NSLog(#"TestViewController - dealloc"); }
#end
#interface AppDelegate ()
#property (weak) IBOutlet NSWindow* window;
#property (strong) TestViewController* testViewController;
#end
#implementation AppDelegate
- (void)dealloc { NSLog(#"AppDelegate - dealloc"); }
- (void)applicationDidFinishLaunching:(NSNotification*)aNotification
{
// Insert code here to initialize your application
TestViewController* testViewController = [[TestViewController alloc] init];
self.testViewController = testViewController;
[self.window.contentView addSubview:testViewController.view];
}
- (void)applicationWillTerminate:(NSNotification*)aNotification
{
// Insert code here to tear down your application
// self.testViewController = nil;
}
#end

App termination doesn't bother cleaning up all of the individual objects. When the process terminates, the OS X kernel simply reclaims all of the resources used by the process. This is much faster.
From the Advanced Memory Management Programming Guide:
When an application terminates, objects may not be sent a dealloc message. Because the process’s memory is automatically cleared on exit, it is more efficient simply to allow the operating system to clean up resources than to invoke all the memory management methods.
If you have stuff that really needs to be done just before the app terminates, either put it in the app delegate's -applicationWillTerminate: method or observe the NSApplicationWillTerminateNotification notification posted by the application object. Also, you need to not opt-in to sudden termination or, if your app is generally opted in to it, temporarily disable it for as long as it has something it really needs to do at termination.

Related

setHidden with NSSlider doesn't work - Objective C

Hy guys, I'm new at ObjC and I'm still learning;
[sliderContrast setHidden:YES] (i also used slider.hidden = YES) doesn't make the slider invisible, instead it works fine with textfields. Do you know why?
I've also tried using property and synthesize but the result doesn't change
---Interface
#interface Controller : NSWindowController{
IBOutlet NSTextField *labelContrast;
IBOutlet NSTextField *valueContrast;
IBOutlet NSSlider *sliderContrast;
}
- (IBAction)changeContrast:(id)sender;
#end
---Implementation
#import "Controller.h"
#interface Controller ()
#end
#implementation Controller
- (void)windowDidLoad {
[super windowDidLoad];
[labelContrast setHidden:YES];
[valueContrast setHidden:YES];
[sliderContrast setHidden:YES];
}
- (IBAction)changeContrast:(id)sender {
}
#end
If you declare pointers for your objects but you don't allocate them yourself you can not set anything that is not there. Your setHidden: method calls end up in local void aka nowhere.
programmatically
If you go the coding way you would declare, allocate and initiate first. With
labelContrast = [NSTextField alloc] initWithFrame:NSMakeRect(x,y,w,h)];
before you call other methods of the object class (Or similar init methods).After that you can call methods on the object.
Almost all objects inherit an according -(instancetype)init or -(instancetype)initWith... method you can use. If there is no init method given, then there is another way to do it right and the moment to start reading again :).
With Interface Builder
By typing IBOutlet or IBAction in front of a declaration you just give a hint for Xcodes Interface Builder where to hook up and apply onto (associate) the placed object in (nib,xib,storyboard) with its object ID in the XML scheme to a reference in code.
So after you connected your code and the object in IB you can avoid allocation and init process for that particular object. Be aware that calling methods on objects that are not instanced yet is not working. Which is why you code in - (void)windowDidLoad, -(void)viewDidLoad or -(void)awakeFromNib because those are the methods that get called after "IB" has done its job for you.

NSApplicationDelegate application:openFile. Never reaches to openFile: function

I want to open a file dropping it on the app icon.
When I do it my app is opened so the file extension is well defined and related to my app.
But the application:openFile: function never is called. so I can't open the file dropped in my app.
I traced openFile: but never goes.
All the answers that I found are just to add in the delegate the openFile: and that's all but not in my case.
Any help will be very appreciate it. Thanks a lot in advance.
This is my environment.
The plist has got the extension of files to be opened. My app is opened when I drop the files.
I initialize my delegate at the beggining of the app,
mydelegate = [[MyController alloc] init];
And in the delegate,
in the include,
#interface MyController : NSObject <NSApplicationDelegate> {
#private
NSWindow *window;
}
#property (assign) IBOutlet NSWindow *window;
-(id) init;
-(BOOL) application: (NSApplication*)sharedApplication openFile:(NSString*) fileName;
#end
And in the .m file,
#implementation MyController
#synthesize window;
- (id)init{
self = [super init];
if (self) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(applicationWillFinishLaunching:)
name:NSApplicationWillFinishLaunchingNotification object:nil];
}
return self;
}
- (void) applicationWillFinishLaunching:(NSNotification *)aNotification{
NSLog(#"applicationWillFinishLaunching");
}
-(BOOL) application: (NSApplication*)sharedApplication openFile:(NSString*) fileName {
NSLog(#"openFile=%#", fileName);
return YES;
}
#end
At least in the code provided above, you are not explicitly setting the app's delegate to be an instance of MyController. Are you setting the delegate anywhere?
Immediately following [[MyController alloc] init], try this:
[[NSApplication sharedApplication] setDelegate: mydelegate];
Without making this connection, the app won't know who is supposed to handle delegate responsibilities.
OR
The most common way to handle drag and drop onto the dock icon is to simply implement:
-(BOOL)application:(NSApplication *)sender openFile:(NSString *)path
as part of the AppDelegate class that is auto-generated for you by Xcode when you start a project.
If you have an AppleEvent event handler listening to for 'odoc' Open Document apple events:
NSAppleEventManager.shared().setEventHandler(self,
andSelector: #selector(handle(event:replyEvent:)),
forEventClass: AEEventClass(kCoreEventClass),
andEventID: AEEventID(kAEOpenDocuments))
Then the handler will intercept the calls and the normal App Delegate methods will not be called.

Getting Key down event in an NSView controller

I'm trying to find a solution that allows me to get keydown events in a view controller.
I do not believe a view controller is part of the responder chain by default.
I would appreciate a sample of how to go about this. I have had trouble finding documentation I can understand on how to add the VC to the responder chain and get the events.
Thanks.
Miek
You can implement something like this:
-(void) globalKeyDown: (NSNotification *) notification
method in your controller class, and then just add the observer in awakeFromNib...or loadView method of your controller
- (void)awakeFromNib
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(globalKeyDown:)
name:#"my_keyEvent"
object:nil];
}
in your view class
-(void)keyDown:(NSEvent *)theEvent
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"my_keyEvent"
object:theEvent
userInfo:#{#"sender":self}];
}
NSViewController doesn't have a default way to do this. However, you can achieve this through subclassing NSView. Here is the basic idea:
If you create a view subclass, you can set your view controller as a delegate and create a delegate method that handles events.
You can declare a delegate protocol at the start of your view header.
Import your view header in the view controller header. Declare the view controller as implementing the protocol.
In your view keyDown send the event to the delegate.
Another way is to post NSNotifications in your keyDown and observe and handle the notifications in your view controller. Other ways also exist.
NSView Subclass with Delegate method explained
Here is the delegation example with an NSView subclass which declares a protocol in its header with one required method, an IBOutlet id property that conforms to the protocol. The NSView subclass calls this method to its delegate whenever it wants to. If the delegate is nil, that's fine in Cocoa. Also note, tangentially, I have added IB_Designable and IBInspectable to the view's color properties. This allows setting them in IB and requires the 10.10 SDK.
The app delegate has imported the NSView subclass in the AppDelegate.m implementation file and adopted the protocol in the AppDelegate class extension at the top of the .m file. In the #implementation section it also implements the method.
Also note in IB, I added an NSView to the window, then set its class to the custom NSView subclass in the inspector. Finally, I set its eventDelegate IBOutlet to the AppDelegate proxy in IB.
Custom NSView subclass interface
#import <Cocoa/Cocoa.h>
#protocol EventDelegatingViewDelegate <NSObject>
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent;
#end
IB_DESIGNABLE
#interface EventDelegatingView : NSView
#property IBOutlet id<EventDelegatingViewDelegate> eventDelegate;
#property IBInspectable NSColor *fillColor;
#property IBInspectable NSColor *strokeColor;
#end
Custom NSView subclass implementation
#import "EventDelegatingView.h"
#implementation EventDelegatingView
- (BOOL)acceptsFirstMouse:(NSEvent *)theEvent {return YES;}
// The following two methods allow a view to accept key input events. (literally they say, YES, please send me those events if I'm the center of attention.)
- (BOOL)acceptsFirstResponder {return YES;}
- (BOOL)canBecomeKeyView {return YES;}
- (void)drawRect:(NSRect)dirtyRect {
[super drawRect:dirtyRect];
[self.fillColor set];
NSRectFill(self.bounds);
[self.strokeColor set];
NSFrameRect(self.bounds);
}
// Notice these don't do anything but call the eventDelegate. I could do whatever here, but I didn't.
// The NICE thing about delgation is, the originating object stays in control of it sends to its delegate.
// However, true to the meaning of the word 'delegate', once you pass something to the delegate, you have delegated some decision making power to that delegate object and no longer have any control (if you did, you might have a bad code smell in terms of the delegation design pattern.)
- (void)mouseDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
- (void)keyDown:(NSEvent *)theEvent
{
[self.eventDelegate view:self didHandleEvent:theEvent];
}
#end
App Delegate (and eventDelegate!) implementation
#import "AppDelegate.h"
// Import the view class and if there were other files that implement any protocol
#import "EventDelegatingView.h"
// Declare protocol conformance (or more accurately, not only import that protocol interface, but say you're going to implement it so the compiler can nag you if you don't)
#interface AppDelegate ()<EventDelegatingViewDelegate>
#property (weak) IBOutlet NSWindow *window;
// For the simplest demo app we don't even need this property.
#property IBOutlet EventDelegatingView *eventDelegatingView;
#end
#implementation AppDelegate
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
}
- (void)applicationWillTerminate:(NSNotification *)aNotification {
// Insert code here to tear down your application
}
// It's all right here. Receive a reference to a view and a reference to an event, then do as you like with them.
#pragma mark - EventDelegatingViewDelegate
- (void)view:(NSView *)aView didHandleEvent:(NSEvent *)anEvent
{
NSString *interestingEventNote;
switch (anEvent.type) {
case NSKeyDown:
case NSKeyUp:
{
// For simplicity we won't try to figure out the modifier keys here.
interestingEventNote = [NSString stringWithFormat:#"%# key was pressed.", anEvent.charactersIgnoringModifiers];
}
break;
case NSLeftMouseDown:
{
interestingEventNote = [NSString stringWithFormat:#"Left mouse down at point %# in window", NSStringFromPoint(anEvent.locationInWindow)];
}
break;
default:
break;
}
NSLog(#"%# %# aView=%#\n note=%#", self, NSStringFromSelector(_cmd), aView, interestingEventNote?interestingEventNote:#"Nothing worth noting");
}
#end
And that's it for the power of delegation. Basically it's callbacks of sorts and is a great way to build a class to enable it to defer something elsewhere as wanted. Moving some business logic to the right place in a fairly lazy and open and loosely coupled way.
NOTE: My code example shows using the app delegate. But the principal is the same. A view controller is little more than a delegate and you can add as much or as little as you like.
In your NSWidow (or NSWindowController) class implementation set your view controller as the first responder:
[self makeFirstResponder:yourViewControllerInstance];
You must, of course, make your NSViewController class return YES to the acceptsFirstResponder message.

New NSWindow with NSTableview crashes

I have a very simple project. Extremely watered down. All it does is load some text into an NSTableView. That's it. But it's using a new window and controller, called "Revisions."
As soon as the new window becomes active, it crashes or just locks up. No errors in the console. If it sits in the background, behind the AppDelegate's window, it appears to load the information fine. I can see the table is populated perfectly. But as soon as I click on the window and make it active, it crashes/locks.
This is driving me nuts. I know it has to do with memory management, but I can't figure out where or how or why.
Note, I'm in XCode 4.2, where there's no more releasin' (unless I change some settings, of course).
All connections in
AppDelegate.h
#import <Cocoa/Cocoa.h>
#interface AppDelegate : NSObject <NSApplicationDelegate>
#property (assign) IBOutlet NSWindow *window;
#end
AppDelegate.m
#import "AppDelegate.h"
#import "Revisions.h"
#implementation AppDelegate
#synthesize window = _window;
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
Revisions *rev = [[Revisions alloc] initWithWindowNibName:#"Revisions"];
[rev loadWindow];
}
Revisions.h
#import <Cocoa/Cocoa.h>
#interface Revisions : NSWindowController
{
IBOutlet NSTableView *quicktimesList;
IBOutlet NSTableView *unusedDataList;
}
#end
Revisions.m
#import "Revisions.h"
#implementation Revisions
- (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.
}
- (NSInteger)numberOfRowsInTableView:(NSTableView *)aTableView
{
NSLog(#"Creating number of rows.");
return 10;
}
- (id)tableView:(NSTableView *)aTableView objectValueForTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex
{
NSLog(#"Starting Loop.");
NSString *words = [[NSString alloc] initWithFormat:#"Row %i", rowIndex];
NSLog(#"Looping %i", (int)rowIndex);
return words;
}
#end
Ok. I'm going to give you a couple of tips when dealing with potential memory leaks in Xcode 4.2.
When writing software for Mac it is advisable to enable garbage collection in your build settings. Just simply search for "garbage collection" in the search bar of your build settings and set it to "required".
If you have memory leaks in your project just press the "product" menu and hit "Analyze".This does as the menu item states, it analyses your project for potential memory leaks and helps you track them down.
Hope this helps!

Error when ViewController is implementing UITextFieldDelegate

When implementing the UITextFieldDelegate in my ViewController class, the following error is thrown when entering the first character in the text field:
-[MyViewController respondsToSelector:]: message sent to deallocated instance...
So, I tried creating a separate class (inheriting only NSObject) and implementing UITextFieldDelegate. Guess what, it worked perfectly. However, that introduces some other problems as I have to do a lot of ugly cross-class-communication that I'd like to avoid. Here's the relevant parts of my app delegate code:
#interface RMSAppDelegate : NSObject <UIApplicationDelegate,
UITabBarControllerDelegate>
#property (nonatomic, retain) UIViewController* myViewController;
#end
#implementation MyAppDelegate
#synthesize myViewController;
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
myViewController = [[MyViewController alloc]
initWithNibName:#"MyView" bundle:nil];
[self.window setRootViewController:myViewController];
[self.window makeKeyAndVisible];
return YES;
}
#end
.. and here's what is being displayed:
#interface MyViewController : UIViewController <UITextFieldDelegate>
#property (nonatomic, retain) IBOutlet UITextField* pinTextField;
- (void)viewDidLoad;
#end
#implementation MyViewController
#synthesize pinTextField;
- (void)viewDidLoad {
// DOES NOT WORK (WHY?)
//[pinTextField setDelegate:self];
// WORKS, BUT I'D LIKE TO AVOID
[pinTextField setDelegate:[[[MyTextFieldDelegate alloc] init] autorelease];
[pinTextField becomeFirstResponder];
[super viewDidLoad];
}
#end
And please, if you see any code (even off topic) that I could be doing better, leave a comment.
Since you asked for off-topic code comments: You forget to call [super viewDidLoad]. You also don't need to redeclare the prototype in order to override it. And the #synthesize textFieldDelegate is not valid, as you have no property in the class named textFieldDelegate. And your dealloc method is releasing an ivar named tfd which doesn't seem to actually exist in the class.
Your real problem is that you are not properly retaining the view controller at whatever point you allocate it. It may be that the view controller is being instantiated in a nib and associated with an ivar rather than a property declared retain, or is not being associated with anything. Or it could be that you are allocating it in code, adding its view as a subview of something, and then releasing it without ever retaining the view controller itself. Or it could just be that you are just releasing it when you shouldn't.
Your other class works specifically because you are leaking the object, so it never gets deallocated. The better solution, were you to go with this method, would be to store the object in an ivar when you allocate it and then release it (and set the ivar to nil) in both dealloc and viewDidUnload.
Okay, I finally solved this on my own. I have not changed the code. My NIB (.xib) was the culprit!
I thought that nested UIViewControllers was OK, and I still think they are in some cases (and maybe using another programmatic method). Anyway, I was initializing my class MyViewController with a NIB that in the Objects panel had a UIViewController as the first object.
I solved this by having the UIView as the first object in the Objects panel, and setting the File's Owner to be the UIViewController instead.
Correct code, incorrect NIB. Thank you for your help.