Re: Temporarily disabling autosave
Re: Temporarily disabling autosave
- Subject: Re: Temporarily disabling autosave
- From: Jerry Krinock <email@hidden>
- Date: Fri, 19 Apr 2013 13:42:05 -0700
On 2013 Apr 19, at 12:37, Mike Abdullah <email@hidden> wrote:
> Why, what's wrong with cancelling a [auto]save?
That's a damned good question, Mike. You're probably thinking that, hey, we lived without any autosaves from 1984 to 2011. What's the big deal? It turns out that you need to be really careful when playing around with an autosave that Cocoa has designated "not implicitly cancellable". Search the internet for:
deadlock in -[NSDocument performActivityWithSynchronousWaiting:usingBlock:]
to see some of the fun that people have had.
I have an app with a requirement similar to Steve's. The app can do long-winded sequences of operations that take tens of seconds, and change the document. So if I honored an autosave request during one these sequences, I'd have to interrupt the operations (which is tricky), save, and then save again at the end after the changes were done.
The solution: Upon receiving a non-cancellable autosave message while other operations are in progress, I stash the completion handler that Cocoa sends in the message, create an operation to "really autosave" later, and add it to my operation queue.
I had to do other stuff to deal with corner cases such as Revert, being in the Versions Browser, etc. Below, I've snipped out a few of the relevant methods from my NSDocument (actually it's NSPersistentDocument, which adds even more to the mess) implementation.
Jerry
- (void)autosaveWithImplicitCancellability:(BOOL)autosavingIsImplicitlyCancellable
completionHandler:(void (^)(NSError *errorOrNil))completionHandler {
if (autosavingIsImplicitlyCancellable) {
// We can cancel this autosave if we want to.
if (
// If operations are currently in progress, cancel it. This is
// because we will save when our operations are complete.
([[[self operationQueue] operations] count] != 0)
||
// Prevent unnecessary saves, in case the document is in a watched folder
// that triggers a syncing mechanism.
(![self isDocumentEdited])
) {
// Cancel it.
completionHandler([NSError errorWithDomain:NSCocoaErrorDomain
code:NSUserCancelledError
userInfo:nil]) ;
return ;
}
}
NSMutableDictionary* info = [NSMutableDictionary dictionary] ;
[info setValue:Block_copy(completionHandler) forKey:constKeyCompletionHandler] ;
[info setObject:self forKey:constKeyDocument] ;
// The following adds a task to my home-made main operation
// queue which I wrote back in the Leopard days …
NSArray* selectorNames = [NSArray arrayWithObject:@"reallyAutosave"] ;
[[self operationQueue] queueGroup:@"Non-cancellable Autosave"
addon:NO
selectorNames:selectorNames
info:info
block:NO
owner:self
doneThread:nil
doneTarget:nil
doneSelector:NULL
keepWithNext:NO] ;
[self setSavingState:1] ;
}
// This is the method that runs (in the main thread) to fulfill "reqllyAutosave"
- (void)reallyAutosave_unsafe {
NSDictionary* info = [self info] ;
Bkmslf* bkmslf = [info objectForKey:constKeyDocument] ;
void (^completionHandler)(NSError*) = [info objectForKey:constKeyCompletionHandler] ;
[bkmslf reallyAutosaveWithCompletionHandler:completionHandler] ;
// Note: completionHandler will be released by the receiver of the above message.
}
/*!
@brief Override of new asynchronous saving method invoked by Cocoa
when running in Mac OS X 10.7 or later.
*/
- (void)saveToURL:(NSURL *)url
ofType:(NSString *)typeName
forSaveOperation:(NSSaveOperationType)saveOperation
completionHandler:(void (^)(NSError *errorOrNil))completionHandler {
[self prepareForSaveOperation:saveOperation] ;
// Despite my best efforts to play nice with autosave, it still occasionally
// deadlocked on me, until I added this:
if ([self savingState] > 1) {
NSLog(@"Warning 089-0831 Cancelling save, now in state %ld, from %@ to avoid deadlock", (long)[self savingState], SSYDebugCaller()) ;
completionHandler([NSError errorWithDomain:NSCocoaErrorDomain
code:NSUserCancelledError
userInfo:nil]) ;
return ;
}
[self setSavingState:2] ;
[super saveToURL:url
ofType:typeName
forSaveOperation:saveOperation
completionHandler:completionHandler] ;
}
- (void)doHousekeepingAfterSaveNotification:(NSNotification*)note {
[self setSavingState:0] ;
...
}
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden