保存先前锁定的文档时 NSPersistentDocument 失败
NSPersistentDocument fails when saving a previously locked document
使用我的 NSPersistentDocument
子类打开锁定的文件时,我在控制台中收到以下消息:
Attempt to add read-only file at path [URL] read/write. Adding
it read-only instead. This will be a hard error in the future; you
must specify the NSReadOnlyPersistentStoreOption.
文档 window 标题为“(文档名称)- 已锁定”。用户解锁后,进行更改然后尝试保存,保存失败并显示错误
An error occurred while saving.
NSPersistentDocument 似乎无法识别用户已解锁文档并且不会在 read/write 模式下重新打开它。这是 NSPersistentDocument
中的错误还是我在这里遗漏了什么?
我没有覆盖 NSPersistentDocument
中的任何文件 I/O 方法。
啊,好的自动文件锁定。
一段时间未访问的自动保存文档会出现这种情况。
典型的方法是在创建核心数据堆栈之前注意到锁定,并提出一个对话框要求用户解锁文件。
如果他们同意解锁文件,您只需解锁它,然后 运行 照常进行。
如果他们不同意解锁,您复制它或以只读方式打开它。当然,您可以简单地绕过用户的偏好并自动解锁文件,但这可能不是很好。
这是一个可以帮助您确定文件是否被锁定的类别,以及 lock/unlock 文件。
请注意,这与更改为只读的文件模式完全不同,但您可以用类似的方式处理它。
分类界面
@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]
来确定它是否被锁定,如果是,则弹出一个对话框询问用户如何处理。然后您可以解锁它并照常打开堆栈,或者保持锁定状态并以只读方式打开堆栈,这将防止保存更改文件存储。
请注意,您还可以监视该文件,了解它何时从 locked/unlocked 发生变化并做出相应的响应。
有关 Apple 的相关指南,请参阅 https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/StandardBehaviors/StandardBehaviors.html
编辑
Ok. I would have liked for NSPersistentDocument to replicate the
behavior in NSDocument - where the prompt to unlock comes only when an
edit is attempted. What you're saying is that there is no such feature
in NSPersistentDocument? – Aderstedt
好的。我以为你想要求用户解锁它以便打开它 read/write。
如果你想 "go with the flow" 并在必要时以只读方式打开它,那么你应该向你的 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 或任何其他机制的 lock/unlock。
- (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也被正确获取和记录。
所以,我可以说没有什么是行不通的。
不过,还有一个更严酷的选择,但是它避免了前面提到的"issue."你可以更换商店。
- (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
}
}
}
}];
}
最后,请注意当操作系统注意到文件已更改时会发生通知。当文件来自您的应用程序时 locked/unlocked,您可以获得更快的通知。
您可以覆盖这两个方法以获得对更改的更快响应...
- (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];
}];
}
希望这就是您要找的。
使用我的 NSPersistentDocument
子类打开锁定的文件时,我在控制台中收到以下消息:
Attempt to add read-only file at path [URL] read/write. Adding it read-only instead. This will be a hard error in the future; you must specify the NSReadOnlyPersistentStoreOption.
文档 window 标题为“(文档名称)- 已锁定”。用户解锁后,进行更改然后尝试保存,保存失败并显示错误
An error occurred while saving.
NSPersistentDocument 似乎无法识别用户已解锁文档并且不会在 read/write 模式下重新打开它。这是 NSPersistentDocument
中的错误还是我在这里遗漏了什么?
我没有覆盖 NSPersistentDocument
中的任何文件 I/O 方法。
啊,好的自动文件锁定。
一段时间未访问的自动保存文档会出现这种情况。
典型的方法是在创建核心数据堆栈之前注意到锁定,并提出一个对话框要求用户解锁文件。
如果他们同意解锁文件,您只需解锁它,然后 运行 照常进行。
如果他们不同意解锁,您复制它或以只读方式打开它。当然,您可以简单地绕过用户的偏好并自动解锁文件,但这可能不是很好。
这是一个可以帮助您确定文件是否被锁定的类别,以及 lock/unlock 文件。
请注意,这与更改为只读的文件模式完全不同,但您可以用类似的方式处理它。
分类界面
@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]
来确定它是否被锁定,如果是,则弹出一个对话框询问用户如何处理。然后您可以解锁它并照常打开堆栈,或者保持锁定状态并以只读方式打开堆栈,这将防止保存更改文件存储。
请注意,您还可以监视该文件,了解它何时从 locked/unlocked 发生变化并做出相应的响应。
有关 Apple 的相关指南,请参阅 https://developer.apple.com/library/mac/documentation/DataManagement/Conceptual/DocBasedAppProgrammingGuideForOSX/StandardBehaviors/StandardBehaviors.html
编辑
Ok. I would have liked for NSPersistentDocument to replicate the behavior in NSDocument - where the prompt to unlock comes only when an edit is attempted. What you're saying is that there is no such feature in NSPersistentDocument? – Aderstedt
好的。我以为你想要求用户解锁它以便打开它 read/write。
如果你想 "go with the flow" 并在必要时以只读方式打开它,那么你应该向你的 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 或任何其他机制的 lock/unlock。
- (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也被正确获取和记录。
所以,我可以说没有什么是行不通的。
不过,还有一个更严酷的选择,但是它避免了前面提到的"issue."你可以更换商店。
- (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
}
}
}
}];
}
最后,请注意当操作系统注意到文件已更改时会发生通知。当文件来自您的应用程序时 locked/unlocked,您可以获得更快的通知。
您可以覆盖这两个方法以获得对更改的更快响应...
- (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];
}];
}
希望这就是您要找的。