Re: NSDocument Serialization (-performSynchronousFileAccessUsingBlock: and friends)
Re: NSDocument Serialization (-performSynchronousFileAccessUsingBlock: and friends)
- Subject: Re: NSDocument Serialization (-performSynchronousFileAccessUsingBlock: and friends)
- From: Kevin Perry <email@hidden>
- Date: Thu, 29 Sep 2011 12:33:43 -0700
On Sep 29, 2011, at 12:00 PM, Kyle Sluder wrote:
> On Thu, Sep 29, 2011 at 9:20 AM, Kevin Perry <email@hidden> wrote:
>> If it were to call the fileAccessCompletionHandler any earlier then it might
>> be possible, for example, for -fileModificationDate to be invoked on the
>> main thread after -writeSafelyToURL: has written the file, but before the
>> value has been properly updated.
>
> Thank you, this is enlightening. Your sketch omitted the "take a
> snapshot of the document wrapper" part; I assume that comes prior to
> the call to -performAsynchronousFileAccess…, but still within the same
> enveloping call to -performActivityWithSynchronousWaiting:….
For Versions, you mean? That also happens within -performAsynchronousFileAccess:, because we don't want anything else touching the file while we're preserving the version.
> However, I am still concerned about blocking other apps. According to
> the documentation, -performAsynchronousFileAccessUsingBlock: does not
> block the main thread. But it surely has to block _something_ until
> the fileAccessCompletionHandler is called.
Yeah, it blocks further invocations of -perform(A)synchronousFileAccessUsingBlock:, where "block" means "enqueue" for async access, and "block the thread" for sync access.
> We've guaranteed that we've
> escaped the -performAsynchronousFileAccess stack frame by calling
> dispatch_async(). Is it just blocking other NSFileCoordinator requests
> until the fileAccessCompletionHandler is called?
NSDocument's NSFilePresenter methods use performAsynchronousFileAccessUsingBlock: internally, so if something else current has file access, the NSFileCoordinator requests are indeed blocked (or "enqueued").
> In that case, why is
> the fileAccessCompletionHandler important for synchronizing with our
> own main thread access, and how can it possibly protect our calls to
> -fileURL from within the block passed to dispatch_async?
With async file access, you have sole access to the file until you call fileAccessCompletionHandler, even if you move out of the original block's execution with dispatch_async, etc. For example, if you have these two methods:
doSync:
performSyncFileAccess:{
[self setFileURL:]
}
doAsync:
performAsyncFileAccess:^{
dispatch_async(^{
[self fileURL]
fileAccessCompletionHandler()
})
}
and you call them like this:
[self doAsync]
[self doSync]
The [self fileURL] call in doAsync is safe, because doSync's performSyncFileAccess: won't run doAsync's fileAccessCompletionHandler() is called.
Similarly, if you have this additional method:
doAsync2:
performAsyncFileAccess:^{
[self setFileURL:]
}
and call them like this:
[self doAsync]
[self doAsync2]
This is also safe because doAsync2's block won't be invoked until doAsync's fileAccessCompletionHandler() is called.
> Does
> "-performAsynchronousFileAccess does not block the main thread" really
> mean "if I can't execute the block now, I will execute it later, and
> in doing so the thread on which this method was called (which might be
> the main thread) will be blocked until the block has finished
> executing _and_ the fileAccessCompletionHandler has been called"?
No, because blocking the thread until fileAccessCompletionHandler is called would defeat the purpose of asynchronous saving. The thread is unblocked immediately after the block finishes executing. The main thread will only be blocked if performSynchronousFileAccess: is called later and the async file access hasn't yet completed.
> In my original example, I was using -performSynchronousFileAccess
> around my call to -moveItemAtURL:toURL:error: and the subsequent call
> to -presentError:, rather than -performAsynchronousFileAccess. If
> -presentError: throws up an app-modal alert, then I've blocked any
> other pending NSFileCoordinator clients who want to access my file,
> correct? This can include other file presenters in my own app, who
> might perhaps want to read the document in the background?
There should be no need to include error presentation with the file access. Sorry I missed that in the original example. The usual pattern is:
performAsyncFileAccess:^{
NSError *error;
doSomethingWithFile(&error);
fileAccessCompletionHandler();
handleError(error);
}
or:
__block NSError *error;
performSyncFileAccess:^{
doSomethingWithFile(&error);
}
handleError(error);
> Or do I need to switch to -performAsynchronousFileAccess to make sure
> I don't cause other apps to beachball? If so, would it look roughly
> like this?
>
> /**
> * Again, not meant to be a good example of using NSFileManager.
> **/
>
> - (BOOL)isInHomeDirectory {
> NSURL *fileURL = [self fileURL];
> return [[fileURL absoluteString] hasPrefix:NSHomeDirectory()];
> }
>
> - (IBAction)doThing:(id)sender {
> [self performActivityWithSynchronousWaiting:YES usingBlock:^{
> [self performAsynchronousFileAccessUsingBlock:^(void
> (^fileAccessCompletionHandler)(void){
> dispatch_async(backgroundQueue, ^{
> BOOL isInHome = [self isInHomeDirectory];
> if (isInHome) {
> NSURL *fileURL = [self fileURL];
> NSURL destinationURL = [NSURL
> fileURLWithPath:[NSTemporaryDirectory()
> stringByAppendingPathComponent:[fileURL lastPathComponent]]];
>
> NSError *error;
> BOOL success = [[NSFileManager defaultManager]
> moveItemAtURL:fileURL toURL:destinationURL error:&error];
> [self continueAsynchronousWorkOnMainThreadUsingBlock:^{
> fileAccessCompletionHandler();
>
> if (!success)
> [self presentError:error];
> }];
> }
> });
> }];
> }];
> }
This looks fine.
> All -performActivityWithSynchronousWaiting:usingBlock: is doing here
> is wrapping a call to -performAsynchronousFileAccessUsingBlock:, which
> in turn is just wrapping a call to dispatch_async. Do I need to
> reimplement my own analogue to -unblockUserInteraction, to ensure the
> user doesn't make any changes to the document while the move operation
> is taking place on my backgroundQueue? Or is that scenario specific to
> the conflict between "save the document asynchronously" vs "manipulate
> the document's data?"
Why do you care if the user is modifying the document in memory while you're playing around with the file in the background? The only reason we block the main thread with async saving is because we need to get the snapshot of the in-memory state to write it to disk on the background thread. Once we have that snapshot, we can touch the file all we want while the user merrily continues editing the document in memory.
> Sorry to bombard you with questions, but the headerdoc only goes so far.
As you discover things that you think aren't documented well, please give back and file doc bugs. The more specific you can get, the better.
-KP_______________________________________________
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