NSPersistentDocument在保存先前锁定的文档时失败



当使用NSPersistentDocument子类打开锁定文件时,我在控制台上得到以下消息:

尝试添加路径[URL]的只读文件。添加它是只读的。这在将来会是一个严重的错误;你必须指定NSReadOnlyPersistentStoreOption.

文档窗口标题为"(文档名称)-锁定"。在用户解锁它,进行更改并尝试保存之后,保存失败并报错

保存时出现错误。

似乎NSPersistentDocument无法识别用户已经解锁了文档,并且没有以读/写模式重新打开它。这是一个bug在NSPersistentDocument或我错过了什么在这里?

我没有重写NSPersistentDocument中的任何文件I/O方法。

啊,文件自动锁定。

当自动保存文档在一段时间内没有被访问时,会发生这种情况。

典型的方法是在创建核心数据堆栈之前注意到锁,并设置一个对话框要求用户解锁文件。

如果他们同意解锁文件,您只需解锁它并正常运行。

如果他们不同意解锁,你复制它或只读打开它。当然,您可以简单地绕过用户的首选项并自动解锁文件,但这可能不是很好。

这里有一个分类,可以帮助您确定文件是否被锁定,以及锁定/解锁文件。

注意,这与将文件模式更改为只读是完全分开的,但是您可以以类似的方式处理它。

类别接口

@interface NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path;
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error;
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error;
@end

类别实现

@implementation NSFileManager (MyFileLocking)
- (BOOL)isFileLockedAtPath:(NSString *)path {
    return [[[self attributesOfItemAtPath:path error:NULL]
             objectForKey:NSFileImmutable] boolValue];
}
- (BOOL)unlockFileAtPath:(NSString*)path error:(NSError**)error {
    return [self setAttributes:@{NSFileImmutable:@NO}
                  ofItemAtPath:path
                         error:error];
}
- (BOOL)lockFileAtPath:(NSString*)path error:(NSError**)error {
    return [self setAttributes:@{NSFileImmutable:@YES}
                  ofItemAtPath:path
                         error:error];
}
@end

然后,您可以调用[[NSFileManager defaultManager] isFileLockedAtPath:path]来确定它是否被锁定,如果是,则弹出一个对话框,询问用户如何处理它。然后,您可以解锁它并像往常一样打开堆栈,或者将其锁定并以只读方式打开堆栈,这将防止保存更改文件存储。

请注意,您还可以监视文件,并知道它何时从锁定/未锁定更改并相应地响应。


有关Apple的指南,请参阅https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/StandardBehaviors/StandardBehaviors.html

编辑

Ok。我希望NSPersistentDocument能够复制在NSDocument中的行为——只有当一个尝试编辑。你的意思是没有这样的功能在NSPersistentDocument吗?——Aderstedt

OK。我以为你想让用户解锁它,这样它就可以打开读/写了。

如果你想"随波逐流",并在必要时将其打开为只读,那么你应该为你的NSPersistentDocument子类添加一点自定义。

首先,您希望添加一个小状态来跟踪原始选项是否指定了只读文件。

@implementation MyDocument {
    BOOL explicitReadOnly;
}

然后,您将需要几个实用程序方法…

- (NSDictionary*)addReadOnlyOption:(NSDictionary*)options {
    NSMutableDictionary *mutable = options ? [options mutableCopy]
                                           : [NSMutableDictionary dictionary];
    mutable[NSReadOnlyPersistentStoreOption] = @YES;
    return [mutable copy];
}
- (NSDictionary*)removeReadOnlyOption:(NSDictionary*)options {
    NSMutableDictionary *mutable = options ? [options mutableCopy]
                                           : [NSMutableDictionary dictionary];
    [mutable removeObjectForKey:NSReadOnlyPersistentStoreOption];
    return [mutable copy];
}

接下来,您希望提供您自己的持久存储协调器配置代码。这允许您在创建存储时为其提供只读选项。此方法在构建文档时自动调用,您所需要做的就是提供一个覆盖实现。

- (BOOL)configurePersistentStoreCoordinatorForURL:(NSURL *)url
                                           ofType:(NSString *)fileType
                               modelConfiguration:(NSString *)configuration
                                     storeOptions:(NSDictionary<NSString *,id> *)storeOptions
                                            error:(NSError * _Nullable __autoreleasing *)error {
    explicitReadOnly = [storeOptions[NSReadOnlyPersistentStoreOption] boolValue];
    if (![[NSFileManager defaultManager] isWritableFileAtPath:url.path]) {
        storeOptions = [self addReadOnlyOption:storeOptions];
    }
    return [super configurePersistentStoreCoordinatorForURL:url
                                                     ofType:fileType
                                         modelConfiguration:configuration
                                               storeOptions:storeOptions
                                                      error:error];
}

还要注意,NSPersistentDocument实现了NSFilePresenter协议。因此,您可以覆盖一个方法,并在文件内容或属性发生更改时收到通知。这将通知您对文件的任何更改,包括从应用程序、Finder或任何其他机制中锁定/解锁。

- (void)presentedItemDidChange {
    [self ensureReadOnlyConsistency];
    [super presentedItemDidChange];
}

然后,我们要确保持久存储与文件的只读属性保持一致。

这是一个实现,它只是改变了存储的readOnly属性。

- (void)ensureReadOnlyConsistency {
    NSURL *url = [self presentedItemURL];
    BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];
    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    [psc performBlock:^{
        NSPersistentStore *store = [psc persistentStoreForURL:url];
        if (store) {
            if (fileIsReadOnly) {
                if (!store.isReadOnly) {
                    store.readOnly = YES;
                }
            } else if (!explicitReadOnly) {
                if (store.isReadOnly) {
                    store.readOnly = NO;
                }
            }
        }
    }];
}

这个可以工作,但是有一个小问题。如果存储最初是用只读选项打开的,那么第一次将readOnly属性设置为NO时,第一个保存抛出(实际上是obtainPermanentIDsForObjects:error:调用)。核心数据似乎捕捉到异常,但它被记录到控制台。

继续保存,似乎没有任何问题。保存所有对象,并正确获取和记录对象id。

所以,没有什么不工作,我可以告诉。

然而,还有另一个更严厉的选择,但它避免了前面提到的"问题"。你可以替换商店。

- (void)ensureReadOnlyConsistency {
    NSURL *url = [self presentedItemURL];
    BOOL fileIsReadOnly = ![[NSFileManager defaultManager] isWritableFileAtPath:url.path];
    NSPersistentStoreCoordinator *psc = self.managedObjectContext.persistentStoreCoordinator;
    [psc performBlock:^{
        NSPersistentStore *store = [psc persistentStoreForURL:url];
        if (store) {
            if (fileIsReadOnly != store.isReadOnly) {
                NSString *type = store.type;
                NSString *configuration = store.configurationName;
                NSDictionary *options = store.options;
                if (fileIsReadOnly) {
                    options = [self addReadOnlyOption:options];
                } else if (!explicitReadOnly) {
                    options = [self removeReadOnlyOption:options];
                }
                NSError *error;
                if (![psc removePersistentStore:store error:&error] ||
                    ![psc addPersistentStoreWithType:type
                                       configuration:configuration
                                                 URL:url
                                             options:options
                                               error:&error]) {
                    // Handle the error
                }
            }
        }
    }];
}

最后,请注意,当操作系统注意到文件已更改时,会发生通知。当文件在应用程序中被锁定/解锁时,您可以获得更快的通知。

您可以覆盖这两个方法,以获得更快的响应更改…

- (void)lockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
    [super lockWithCompletionHandler:^(NSError * _Nullable error) {
        if (completionHandler) completionHandler(error);
        if (!error) [self ensureReadOnlyConsistency];
    }];
}
- (void)unlockWithCompletionHandler:(void (^)(NSError * _Nullable))completionHandler {
    [super unlockWithCompletionHandler:^(NSError * _Nullable error) {
        if (completionHandler) completionHandler(error);
        if (!error) [self ensureReadOnlyConsistency];
    }];
}

相关内容

  • 没有找到相关文章

最新更新