I'm trying to develop a simple application, which upon clicking a menu item, it shows a window containing an NSTableView.
The problem is that the app crashes just after NSTableView displaying the data. Full stack trace:
* thread #1: tid = 0x2107, 0x00007fff943bce90 libobjc.A.dylib\`objc_msgSend + 16,
stop reason = EXC_BAD_ACCESS (code=13, address=0x0)
frame #0: 0x00007fff943bce90 libobjc.A.dylib`objc_msgSend + 16
Since I'm using ARC, I should exclude any reference counting issue; but maybe I'm creating the controller (needed to create the window) in a bad way, and it's being free'd erroneously.
This is the code of the AppController that create and shows the window:
- (IBAction)showPreferences:(id)sender {
if(!preferencesWindow) {
preferencesWindow = [[[PreferencesWindowController alloc]
initWithWindowNibName:#"PreferencesWindow"] window];
}
[preferencesWindow makeKeyAndOrderFront:sender];
}
This code in PreferencesWindowController implements the dataSource protocol (needed by the NSTableView).
- (int)numberOfRowsInTableView:(NSTableView *)tabView {
return 1;
}
- (id)tableView:(NSTableView *)tabView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(int)row {
NSString *val = [NSString stringWithFormat:#"%#[%d]", [tableColumn identifier], row];
return val;
}
It's not causing the crash per se. But if I remove the PreferencesWindowController from NSTableView's dataSource, it doesn't crash, so it should be somewhat related.
Where's the mistake?
EDIT: using the profiler (Instruments) with the zombies preset, I can see there's an object whose reference counter goes negative:
but anyway, the stack is outside the code I wrote. I can't put a breakpoint there, and I can't see which is the object being released twice (or I should say I don't know how to)
The line preferencesWindow = [[[PreferencesWindowController alloc] initWithWindowNibName:#"PreferencesWindow"] window] looks suspicious, because while you are referencing the window itself with a strong reference, it looks like you're letting ARC release the PreferencesWindowController.
Try storing the PreferencesWindowController object in its own strong variable/property and let me know.
Related
I'm wondering what's the correct way to spawn a bunch of windows from a callback, basically i'd like to hit a combination like Ctrl+Shift + Cmd + + and create a new window, which is not bound to the app delegate. Currently i have the following code in my AppDelegate.m:
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
NSLog(#"Finished");
[NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^(NSEvent *event) {
NSUInteger ctrlPressed = [event modifierFlags] & NSEventModifierFlagControl;
NSUInteger shiftPressed = [event modifierFlags] & NSEventModifierFlagShift;
NSUInteger cmdPressed = [event modifierFlags] & NSEventModifierFlagCommand;
NSUInteger EqButton = 0x30;
if (ctrlPressed && shiftPressed && cmdPressed) {
if ([event keyCode] & EqButton) {
__strong ETTimerController *controller = [ETTimerController new];
[controller showWindow: self];
}
}
}];
}
This snippet creates a controller and a bound nib file, but i can't see the window, i assume it's collected by the ARC. How can i retain create and retain a new window without storing the reference within the AppDelegate instance?
Not only the window, but I think the window controller is being released here once the block exits.
Store it in a strong property of AppDelegate:
#property (nonatomic, strong) timerController *ETTimerController;
Then, instantiate it like this:
self.timerController = [ETTimerController new];
[self.timerController showWindow: self];
EDIT: As mentioned in the comments, the window controller reference needs to be owned by some persistent object (or be a static variable somewhere) otherwise it goes out of scope as soon as the method exits and ARC deallocates the object.
If you can't afford a dedicated property in the AppDelegate class (because you need to have a variable number of different window controllers, etc.), then you will need to consider what is the expected lifetime of each controller, and store a reference accordingly:
If your window controller is reused, i.e. its window is repeatedly shown and hidden but you only need at most one, make it a singleton and the static reference to the shared instance will take care of retaining it.
If you are building a document-based app, each instance of your NSDocument subclass will create all its window controllers within the makeWindowController() method, and retain them inside an array property.
If none of the above apply, you will need to think of something else that -again- will vary depending on your needs and specifications.
Closed. This question needs debugging details. It is not currently accepting answers.
Edit the question to include desired behavior, a specific problem or error, and the shortest code necessary to reproduce the problem. This will help others answer the question.
Closed 8 years ago.
Improve this question
If I pass testString2 to the next view controller (where the string is assigned to a property of a NSManagedObject subclass), the app crashes soon after with a BAD_ACCESS error. I was able to determine the string was turning into a zombie a while after attaching it to the managed object and also assigning it to a class member of the receiving view controller, to try and eliminate this problem. However, it doesn't turn into a zombie until well after it has been assigned as described.
IF, however, I send testString instead of testString2 to the next view controller, no crashes and everything is happy. Incidentally, newKw is text from a text field, but using strings retrieved from a dictionary gives the same result. I have also tried using [NSString stringWithString:newKw] and other NSString methods in an attempt to create a brand new string, and I get the same result then too.
If I send nil instead of sending any string, there are no errors.
The managed object is never lost or corrupted if I pass testString. But if I pass testString2, it appears when the variable turns into a zombie, it takes out the managed object too because everything in the description is gone and is not shown as a fault. Following some other advice I have seen, I set a breakpoint for malloc_error_break, and in my log I see this:
Power Passage(3734,0x2dae1a8) malloc: *** error for object 0x9aa67b0: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Here is where it is passed from the VC where the string originated:
if (proceed) {
NSString *testString = #"testing123";
NSString *testString2 = newKw;
[self.navigationController pushViewController:[[NewKeywordSummary alloc] initWithKeyword:testString2] animated:YES];
Here is the property where the pointer is getting stored in the managed object:
#property (nonatomic, retain) NSString * newKeyword;
Here is the method newKw is being sent to:
-(instancetype)initWithKeyword:(NSString *)kw
{
if (self = [self init]) {
//Create a new request
kwReq = [ppKeywordRequest keywordRequestInContext:editingContext];
newKw = kw;
kwReq.newKeyword = newKw;
}
return self;
}
Then the kwReq object is passed to the next VC:
-(void)viewBtnHandler:(UIButton *)btn
{
if (btn == addNewTagBtn) {
[self.navigationController pushViewController:[[TagSummaryVC alloc] initWithKeywordRequest:kwReq tagSubmission:nil] animated:YES];
}
}
and it goes here:
-(instancetype)initWithKeywordRequest:(ppKeywordRequest *)req tagSubmission:(ppTagSubmission *)t
{
if (self = [super init]) {
delegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
user = delegate.user;
phrases = [delegate.languagePhrases objectForKey:#"TagSummaryVC"];
if (req) {
editingContext = req.managedObjectContext;
kwReq = req;
}
else {
editingContext = [[NSManagedObjectContext alloc] init];
[editingContext setPersistentStoreCoordinator:[delegate.localDataContext persistentStoreCoordinator]];
delegate.editingContext = editingContext;
}
if (!t) {
ts = [ppTagSubmission tagSubmissionInContext:editingContext];
newTS = YES;
}
else
ts = (id)[editingContext objectWithID:[t objectID]];
self.navigationItem.backBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:#""
style:UIBarButtonItemStyleBordered
target:nil
action:nil];
}
return self;
}
And it returns to NewKeywordSummary here:
if (alertView.tag == AttachAlert) {
if (index == 0)
return;
NSString *testString = kwReq.newKeyword;
NSLog([NSString stringWithFormat:#"Test variable is %#", kwReq.newKeyword]);
[kwReq addTagSubmissionsObject:ts];
[self.navigationController popViewControllerAnimated:YES];
}
}
My NSLogs before and after it is a zombie:
2014-08-04 21:00:52.306 Power Passage[3838:60b] Test variable is vcxvzcx
2014-08-04 21:00:53.378 Power Passage[3838:60b] Test variable is vcxvzcx
2014-08-04 21:00:55.908 Power Passage[3838:60b] Test variable is <__NSMallocBlock__: 0x8ea4b00>
(lldb)
Make sure your NSString references are assigned to a #property on your classes (plenty of examples of assigning and using properties on iOS) using an appropriate storage type (strong or retain, for example). And, yes you are correct any "standard" string in an iOS app is usually an instance of NSString, and thus required the "#" symbol in front.
Try setting a break point on Exceptions ... this will help you identify the offending statement.
I would suggest picking up a book on an intro to Objective-C / iOS / iPad development ... I think you'll find the information invaluable and a HUGE time saver.
I believe I have found the answer to my question. I ran into this post:
why can't I declare a variable like "newVariable" in Obj-C?
Quote from the link:"Objective-C has a naming convention for memory management that is enforced by the compiler. Methods that start with new (also "alloc", "copy", "mutableCopy") are required to return an object that will be "owned" by the caller. See the documentation." (thanks progrmr!)
and realized that I had a property that started with "new", and while it wasn't being synthesized it WAS generating setters/getters with #dynamic... so every time the member kwReq.newKeyword was referenced on my managed object, I am guessing that the compiler was trying to change ownership or otherwise alter the retain count - so after a couple times I was out of retains. So I changed the member name to reqKw, and I have no more problem. I was NOT getting any kind of warning on this.(thanks Apple:-)
I am currently going through the 3rd Edition of the Mac OSX Cocoa book from the Big Nerd Ranch guys and I am editing my program to include table views. Here the code from the AppController.m file where I have to implement the required protocol methods from the TableView:
-(id)init{
self = [super init];
voiceArray = [NSSpeechSynthesizer availableVoices];
speechSynth = [[NSSpeechSynthesizer alloc] initWithVoice:nil];
[speechSynth setDelegate:self];
return self;
}
-(int)numberOfRowsInTableView:(NSTableView *)tv{
NSLog(#"Getting number of rows in table view: %lu", [voiceArray count]);
return [voiceArray count];
}
-(id)tableView:(NSTableView *)tv objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row{
NSString *v = [voiceArray objectAtIndex:row];
NSDictionary *dict = [NSSpeechSynthesizer attributesForVoice:v];
NSLog(#"Voice Name %#", [dict objectForKey:NSVoiceName]);
return [dict objectForKey:NSVoiceName];
}
-(void)tableViewSelectionDidChange:(NSNotification *)notification{
int row = [tableView selectedRow];
NSLog(#"row selected %d", row);
if(row == 1){
return;
}
NSString *selectedVoice = [voiceArray objectAtIndex:row];
[speechSynth setVoice:selectedVoice];
NSLog(#"new voice %# ", selectedVoice);
}
When the app first loads I get the following output:
2012-05-27 15:02:29.040 Speakline[42836:f03] Getting number of rows in
table view: 24 2012-05-27 15:02:29.042 Speakline[42836:f03] row
selected 2 2012-05-27 15:02:29.043 Speakline[42836:f03] new voice
com.apple.speech.synthesis.voice.Alex 2012-05-27 15:02:29.162
Speakline[42836:f03] Voice Name Agnes 2012-05-27 15:02:29.163
Speakline[42836:f03] Voice Name Albert
I want to make sure I understand fully what is going on here. In order to do this I have a couple of questions.
It looks like the numberOfRowsInTableView: method was automatically called after the init method. Is this correct?
How often does objectValueForTableColumn: get called? What events prompt that method to get called? Also, in the code there, the return statement confused me. Where exactly does this return value go?
As a side note they wanted me to connect the Outlets and the AppController via control+clicking (via the connections panel) and linking them in that way. What alternatives are there avaialble for connecting delegates and datasources to different kinds of views without doing this? I am assuming that adding NSTableViewDelegate in the controller header file might be one way. If you have the option of control+click connecting all your views to outlets and so on vs programmatically setting it all up is it just a matter of preference at this point? It just seems to me that in order to understand what is going on it might be better to just write the code yourself.
1) Both numberOfRowsInTableView and objectValueForTableColumn:Row: get called soon after the nib is unarchived (I'm not sure of the exact order of things), so the table view can be populated with data.
2) The loading of the table and reloading or adding new data as well as scrolling will cause objectValueForTableColumn:row: to be called (by the table view). It gets called for each row that is populated. The value goes to the table view, that's how it gets its data.
3) The other way to connect delegates is to do it in code with setDelegate:, but putting NSTableViewDelegate in the .h file does not do that -- that's just a promise to the compiler that you will implement some or all of the delegate messages.
I have the following code that tries to set up a GridView and I have a GridviewController subclass that manages the datasource. This is the code that is used to set it up.
AQGridView* gridView = [[AQGridView alloc] initWithFrame:frame];
NUBMyCpGridviewController* controller = [[NUBMyCpGridviewController alloc] init];
gridView.dataSource = controller;
gridView.delegate = controller;
[gridView reloadData];
However, the app crashes when it tries to access the datasource. This is the line (in the Gridview class) that gives the tries to call the method and crashes it:
AQGridViewCell * cell = [_dataSource gridView: self cellForItemAtIndex: index];
The error is exc_bad_access. What could be the problem? Is it because the object is being released too early? How can I rectify it?
You're right; the problem is most likely that your NUBMyCpGridviewController is being deallocated. Based on your code snippet it looks like no one is retaining it.
My suggestion would be to make it a strong #property of whichever class your snippet code is being executed in.
UPDATE | I've uploaded a sample project using the panel and crashing here: http://w3style.co.uk/~d11wtq/BlocksCrash.tar.gz (I know the "Choose..." button does nothing, I've not implemented it yet).
UPDATE 2 | Just discovered I don't even have to invoke anything on newFilePanel in order to cause a crash, I merely need to use it in a statement.
This also causes a crash:
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
newFilePanel; // Do nothing, just use the variable in an expression
}];
It appears the last thing dumped to the console is sometimes this: "Unable to disassemble dyld_stub_objc_msgSend_stret.", and sometimes this: "Cannot access memory at address 0xa".
I've created my own sheet (an NSPanel subclass), that tries to provide an API similar to NSOpenPanel/NSSavePanel, in that it presents itself as a sheet and invokes a block when done.
Here's the interface:
//
// EDNewFilePanel.h
// MojiBaker
//
// Created by Chris Corbyn on 29/12/10.
// Copyright 2010 Chris Corbyn. All rights reserved.
//
#import <Cocoa/Cocoa.h>
#class EDNewFilePanel;
#interface EDNewFilePanel : NSPanel <NSTextFieldDelegate> {
BOOL allowsRelativePaths;
NSTextField *filenameInput;
NSButton *relativePathSwitch;
NSTextField *localPathLabel;
NSTextField *localPathInput;
NSButton *chooseButton;
NSButton *createButton;
NSButton *cancelButton;
}
#property (nonatomic) BOOL allowsRelativePaths;
+(EDNewFilePanel *)newFilePanel;
-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler;
-(void)setFileName:(NSString *)fileName;
-(NSString *)fileName;
-(void)setLocalPath:(NSString *)localPath;
-(NSString *)localPath;
-(BOOL)isRelative;
#end
And the key methods inside the implementation:
-(void)beginSheetModalForWindow:(NSWindow *)aWindow completionHandler:(void (^)(NSInteger result))handler {
[NSApp beginSheet:self
modalForWindow:aWindow
modalDelegate:self
didEndSelector:#selector(sheetDidEnd:returnCode:contextInfo:)
contextInfo:(void *)[handler retain]];
}
-(void)dismissSheet:(id)sender {
[NSApp endSheet:self returnCode:([sender tag] == 1) ? NSOKButton : NSCancelButton];
}
-(void)sheetDidEnd:(NSWindow *)aSheet returnCode:(NSInteger)result contextInfo:(void *)contextInfo {
((void (^)(NSUInteger result))contextInfo)(result);
[self orderOut:self];
[(void (^)(NSUInteger result))contextInfo release];
}
This all works provided my block is just a no-op with an empty body. My block in invoked when the sheet is dismissed.
EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:#"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
NSLog(#"I got invoked!");
}];
But as soon as I try to access the panel from inside the block, I crash with EXC_BAD_ACCESS. For example, this crashes:
EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
[newFilePanel setAllowsRelativePaths:[self hasSelectedItems]];
[newFilePanel setLocalPath:#"~/"];
[newFilePanel beginSheetModalForWindow:[windowController window] completionHandler:^(NSInteger result) {
NSLog(#"I got invoked and the panel is %#!", newFilePanel);
}];
It's not clear from the debugger with the cause is. The first item (zero 0) on the stack just says "??" and there's nothing listed.
The next items (1 and 2) in the stack are the calls to -endSheet:returnCode: and -dismissSheet: respectively. Looking through the variables in the debugger, nothing seems amiss/out of scope.
I had thought that maybe the panel had been released (since it's autoreleased), yet even calling -retain on it right after creating it doesn't help.
Am I implementing this wrong?
It's a little odd for you to retain a parameter in one method and release it in another, when that object is not an instance variable.
I would recommend making the completionHandler bit of your beginSheet stuff an instance variable. It's not like you'd be able to display the sheet more than once at a time anyway, and it would be cleaner this way.
Also, your EXC_BAD_ACCESS is most likely coming from the [handler retain] call in your beginSheet: method. You're probably invoking this method with something like (for brevity):
[myObject doThingWithCompletionHandler:^{ NSLog(#"done!"); }];
If that's the case, you must -copy the block instead of retaining it. The block, as typed above, lives on the stack. However, if that stack frame is popped off the execution stack, then that block is gone. poof Any attempt to access the block later will result in a crash, because you're trying to execute code that no longer exists and has been replaced by garbage. As such, you must invoke copy on the block to move it to the heap, where it can live beyond the lifetime of the stack frame in which it was created.
Try defining your EDNewFilePanel with the __block modifier:
__block EDNewFilePanel *newFilePanel = [EDNewFilePanel newFilePanel];
This should retain the object when the block is called, which may be after the Panel object is released. As an unrelated side-effect, this will make also make it mutable within the block scope.