Search code examples
objective-ccocoansbutton

Can I pass the a reference of an NSWindow to a custom object and use that object to add an NSButton and Action?


I was wondering if it's possible to pass the reference of an NSWindow to a custom object and then use that object to to add an NSButton and associated action/selector for that button.

I seem to be running into issues when I attempt this. When I run the sample program and click on the button, the following runtime error occurs: Thread 1: EXC_BAD_ACCESS (code=1, address=0x18)

Here is my code:

//  AppDelegate.h

#import <Cocoa/Cocoa.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>
@property (assign) IBOutlet NSWindow *window;
@end

//  AppDelegate.m

#import "AppDelegate.h"
#import "CustomObject.h"
@implementation AppDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    CustomObject *test = [[CustomObject alloc]init];
    [test createButton:_window];
}
@end


//  CustomObject.h

#import <Foundation/Foundation.h>

@interface CustomObject : NSObject
{
    int test;
    NSButton *testButton;
}
- (IBAction)pressCustomButton:(id)sender;

-(void)createButton:(NSWindow*)win;
@end

//  CustomObject.m

#import "CustomObject.h"

@implementation CustomObject

-(IBAction)pressCustomButton:(id)sender
{
    NSLog(@"pressCustomButton");
}

-(void)createButton:(NSWindow*)win
{
    testButton = [[NSButton alloc] initWithFrame:NSMakeRect(100, 100, 200, 50)];

    [[win contentView] addSubview: testButton];
    [testButton setTitle: @"Button title!"];
    [testButton setButtonType:NSMomentaryLightButton]; //Set what type button You want
    [testButton setBezelStyle:NSRoundedBezelStyle]; //Set what style You want

    [testButton setTarget:self];
    [testButton setAction:@selector(pressCustomButton:)];
}
@end

Solution

  • First off, I assume you're using Automatic Reference Counting.

    When you click the button, the app attempts to call the pressCustomButton: method of the target of the button, which the instance of CustomObject set to be itself. However, that instance of CustomObject has already been deallocated.

    Take the following code:

    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
        CustomObject *test = [[CustomObject alloc]init];
        [test createButton:_window];
    }
    

    Once that method is finished being called, if you are using ARC, the CustomObject instance you created will be released. Since NSControl subclasses like NSButton don't retain their targets (to avoid retain cycles/strong reference cycles), this will also cause the CustomObject instance to be deallocated. This will cause any subsequent messages to that instance to produce unexpected results such as a crash.

    To prevent this, you'll need to keep the CustomObject instance around beyond the applicationDidFinishLaunching: method. There are a couple of ways of doing that, such as making it a property of the AppDelegate, or if you plan on having multiple objects, use an NSMutableArray to store them in.

    Something like the following:

    @interface AppDelegate : NSObject <NSApplicationDelegate>
    ....
    @property (nonatomic, strong) NSMutableArray *customObjects;
    @end
    
    //  AppDelegate.m
    
    #import "AppDelegate.h"
    #import "CustomObject.h"
    @implementation AppDelegate
    
    - (id)init {
        if ((self = [super init])) {
             customObjects = [[NSMutableArray alloc] init];
        }
        return self;
    }
    
    - (void)applicationDidFinishLaunching:(NSNotification *)aNotification
    {
        CustomObject *test = [[CustomObject alloc]init];
        [customObjects addObject:test];
        [test createButton:_window];
    }
    @end