Is NSDocument's Saving Message Flow Threaded?
-
Hi Guys,
I've overridden -
canCloseDocumentWithDelegate:shouldCloseSelector:contextInfo: to
display a custom NSAlert if any of the text files that my NSDocument
subclass manages are not saved.
The problem is, if the user tries to close the window and very quickly
presses return to select the default response of "Save All Files" the
app crashes when [self close] is called. If they wait a second or two
then no crash happens. The "saveAllFiles" method writes the text
files to the disk and then saves the NSDocument model, which holds all
the file references.
From my logs I can see that the [self close] causes the document to
be dealloc'd before -dataOfType:error: can be called. With NSZombie
enabled I get the following log output
2008-02-16 16:57:25.803 Scribbler[697:10b] *** -[ESDocument
_saveDocumentWithDelegate:didSaveSelector:contextInfo:]: message sent
to deallocated instance 0xea625c0
Where the instance 0xea625c0 is the ESDocument that is being saved,
then closed.
Could this be a threading issue with the way NSDocument saves itself,
even though I haven't put any threaded code in my app at all? I ask
this as the time interval between hitting command-W and return seems
to matter.
Here's the relevant code from my NSDocument:
- (IBAction)saveAllFiles:(id)sender;
{
NSLog(@"%p %s",self,__func__);
for (ESNode *node in self.projectFiles) {
if (node.isEdited) {
NSError *writeError;
if (![node writeBodyToFileError:&writeError]) {
NSLog(@"%p %s %@",self,__func__,writeError);
[self presentError:writeError];
}
}
}
[self saveDocument:nil];
}
- (void)unsavedFilesAlertDidEnd:(NSAlert *)alert returnCode:
(NSUInteger)returnCode contextInfo:(void *)contextInfo;
{
[alert release];
if (returnCode == NSAlertSecondButtonReturn)
return;
if (returnCode == NSAlertFirstButtonReturn) {
[self saveAllFiles:nil];
[self close]; // THIS IS THE CULPRIT
}
if (returnCode == NSAlertThirdButtonReturn)
[self close]; // close without saving
}
- (void)canCloseDocumentWithDelegate:(id)delegate shouldCloseSelector:
(SEL)shouldCloseSelector contextInfo:(void *)contextInfo;
{
NSLog(@"%p %s",self,__func__);
BOOL canClose = YES;
for (ESNode *node in self.projectFiles) {
if (node.isEdited) {
canClose = NO;
break;
}
}
if (!canClose) {
NSAlert *alert = [[NSAlert alloc] init]; // released in didEndSelector
[alert setMessageText:NSLocalizedString(@"ThereAreUnsavedFiles",@"")];
[alert
setInformativeText:NSLocalizedString
(@"ThereAreUnsavedFileExplanation",@"")];
[alert addButtonWithTitle:NSLocalizedString(@"SaveAll",@"")];
[alert addButtonWithTitle:NSLocalizedString(@"Cancel",@"")];
[alert addButtonWithTitle:NSLocalizedString(@"DontSave",@"")];
[alert beginSheetModalForWindow:[self windowForSheet]
modalDelegate:self
didEndSelector
:@selector(unsavedFilesAlertDidEnd:returnCode:contextInfo:)
contextInfo:nil];
}
objc_msgSend(delegate,shouldCloseSelector,self,canClose,contextInfo);
}
Thanks in advance, this has got be stumped.
Jon -
On Feb 16, 2008, at 2:54 PM, Jonathan Dann wrote:> Could this be a threading issue with the way NSDocument saves
> itself, even though I haven't put any threaded code in my app at
> all? I ask this as the time interval between hitting command-W and
> return seems to matter.
>
> Here's the relevant code from my NSDocument:
>
> - (IBAction)saveAllFiles:(id)sender;
> {
> ....
> [self saveDocument:nil];
> }
One wild guess is that the "guts" of -saveDocument: actually happens
in a delayed manner. Meaning it would get called after the end of the
current user event. Some of the action methods tend to work this way.
This seems to be reinforced by the error message you're getting.
If you can, you should try to just let NSDocument do the "unsaved
documents" thing itself, since it has built-in functionality for that.
If you need to do it yourself for some reason, you might have more
luck with calling -writeToURL:ofType:error: on the document instead of
-saveDocument:, though I'm not sure how the details of that would fit
together.
- Scott -
[forgot to post to the list]
On 17 Feb 2008, at 01:20, Scott Stevenson wrote:> One wild guess is that the "guts" of -saveDocument: actually happens
> in a delayed manner. Meaning it would get called after the end of
> the current user event. Some of the action methods tend to work this
> way. This seems to be reinforced by the error message you're getting.
You're right, a quick log above and below this show that it get called
and then my -dataOfType:error: gets called some time after. So does it
delay by spawning a new thread then?> If you can, you should try to just let NSDocument do the "unsaved
> documents" thing itself, since it has built-in functionality for
> that. If you need to do it yourself for some reason, you might have
> more luck with calling -writeToURL:ofType:error: on the document
> instead of -saveDocument:, though I'm not sure how the details of
> that would fit together.
The problem is I have to alter the alter dialog and get all the text
file to write their own contents to the disk. I have an app that
works a little like XCode, so the NSDocument itself just holds
references (and the structure) of the "project" the user has made.
The "body" of each text file is read and written using NSString's
writing methods.
The default alert that show up relates to the NSDocument itself and I
need it to say that there are "unsaved files in the project", its
really the way the user will be thinking about using the app. They
will be thinking that each of the references in the source list is an
actual file, and that they can be opening a bunch of projects, each
with different content, much like how we don't think about saving
the .xcodeproj file itself.
Just tried -writeToURL:ofType:error: and it doesn't delay its call!
Thanks for the fix.
So do many other actions work in a delayed manner, and is it
documented? Would I have missed this somewhere?
Thanks again for your time Scott,
Jon -
On 17 Feb 2008, at 05:02, Jonathan Dann wrote:>> One wild guess is that the "guts" of -saveDocument: actually
>> happens in a delayed manner. Meaning it would get called after the
>> end of the current user event. Some of the action methods tend to
>> work this way. This seems to be reinforced by the error message
>> you're getting.
>
> You're right, a quick log above and below this show that it get
> called and then my -dataOfType:error: gets called some time after.
> So does it delay by spawning a new thread then?
Doubtful. That could introduce all sorts of threading issues, and the
delayed action is probably intended to let things settle down (i.e.
have all processing completed) before it happens. It's probably just
performSelector:withObject:afterDelay:.> So do many other actions work in a delayed manner, and is it
> documented? Would I have missed this somewhere?
FWIW, this is likely an implementation detail that you can't rely on.
I know there were some things that changed between Tiger and Leopard,
with respect to being delayed or not (e.g. updateChangeCount:).
David Dunham A Sharp, LLC
Voice/Fax: 206 783 7404 http://a-sharp.com
Efficiency is intelligent laziness. -
On Feb 17, 2008, at 5:02 AM, Jonathan Dann wrote:> So do many other actions work in a delayed manner, and is it
> documented? Would I have missed this somewhere?
I'm not sure if it's on paper somewhere, but as David Dunham says,
it's probably not something you should rely on.
For the most part, action methods (IBActions) are meant to be used by
user interface controls. It might work out by using them in code too,
but often there's a better method to call.
- Scott -
On 18 Feb 2008, at 22:27, Scott Stevenson wrote:>
> On Feb 17, 2008, at 5:02 AM, Jonathan Dann wrote:
>
>> So do many other actions work in a delayed manner, and is it
>> documented? Would I have missed this somewhere?
>
> I'm not sure if it's on paper somewhere, but as David Dunham says,
> it's probably not something you should rely on.
>
> For the most part, action methods (IBActions) are meant to be used
> by user interface controls. It might work out by using them in code
> too, but often there's a better method to call.
Won't do it again then! Thanks for the info guys.
Jon


