如何判断「 NSManagedObject 」是否已被删除?

我有一个已被删除的 NSManagedObject,并且包含该托管对象的上下文已被保存。我理解,如果核心数据将要求持久存储在下一次保存操作中删除对象,那么 isDeleted将返回 YES。然而,由于保存已经发生,isDeleted返回 NO

什么是一个很好的方法来告诉一个 NSManagedObject是否已被删除 之后其包含的上下文已被保存?

(如果你想知道为什么引用被删除的托管对象的对象没有意识到删除,那是因为删除和上下文保存是由一个后台线程启动的,后台线程使用 performSelectorOnMainThread:withObject:waitUntilDone:执行删除和保存。)

32453 次浏览

检查托管对象的上下文似乎是有效的:

if (managedObject.managedObjectContext == nil) {
// Assume that the managed object has been deleted.
}

从苹果公司的 managedObjectContext文件..。

方法时,此方法可能返回 null 接收器已从其 背景。

如果接收方有错误,则调用 此方法不会导致激发。

这两件事似乎都是好事。

更新: 如果您试图测试是否删除了专门使用 objectWithID:检索的托管对象,请检查 Dave Gallagher 的回答。他指出,如果使用已删除对象的 ID 调用 objectWithID:,返回的对象将是一个错误,因为 没有managedObjectContext设置为 nil。因此,您不能简单地检查它的 managedObjectContext来测试它是否已被删除。如果可以,请使用 existingObjectWithID:error:。如果没有,例如,你的目标是 Mac OS 10.5或者 iOS 2.0,你需要做一些其他的事情来测试删除。详情请参阅 他的回答

更新: 一个改进的答案,基于 James Huddleston在下面讨论的想法。

- (BOOL)hasManagedObjectBeenDeleted:(NSManagedObject *)managedObject {
/*
Returns YES if |managedObject| has been deleted from the Persistent Store,
or NO if it has not.


NO will be returned for NSManagedObject's who have been marked for deletion
(e.g. their -isDeleted method returns YES), but have not yet been commited
to the Persistent Store. YES will be returned only after a deleted
NSManagedObject has been committed to the Persistent Store.


Rarely, an exception will be thrown if Mac OS X 10.5 is used AND
|managedObject| has zero properties defined. If all your NSManagedObject's
in the data model have at least one property, this will not be an issue.


Property == Attributes and Relationships


Mac OS X 10.4 and earlier are not supported, and will throw an exception.
*/


NSParameterAssert(managedObject);
NSManagedObjectContext *moc = [self managedObjectContext];


// Check for Mac OS X 10.6+
if ([moc respondsToSelector:@selector(existingObjectWithID:error:)])
{
NSManagedObjectID   *objectID           = [managedObject objectID];
NSManagedObject     *managedObjectClone = [moc existingObjectWithID:objectID error:NULL];


if (!managedObjectClone)
return YES;                 // Deleted.
else
return NO;                  // Not deleted.
}


// Check for Mac OS X 10.5
else if ([moc respondsToSelector:@selector(countForFetchRequest:error:)])
{
// 1) Per Apple, "may" be nil if |managedObject| deleted but not always.
if (![managedObject managedObjectContext])
return YES;                 // Deleted.




// 2) Clone |managedObject|. All Properties will be un-faulted if
//    deleted. -objectWithID: always returns an object. Assumed to exist
//    in the Persistent Store. If it does not exist in the Persistent
//    Store, firing a fault on any of its Properties will throw an
//    exception (#3).
NSManagedObjectID *objectID             = [managedObject objectID];
NSManagedObject   *managedObjectClone   = [moc objectWithID:objectID];




// 3) Fire fault for a single Property.
NSEntityDescription *entityDescription  = [managedObjectClone entity];
NSDictionary        *propertiesByName   = [entityDescription propertiesByName];
NSArray             *propertyNames      = [propertiesByName allKeys];


NSAssert1([propertyNames count] != 0, @"Method cannot detect if |managedObject| has been deleted because it has zero Properties defined: %@", managedObject);


@try
{
// If the property throws an exception, |managedObject| was deleted.
(void)[managedObjectClone valueForKey:[propertyNames objectAtIndex:0]];
return NO;                  // Not deleted.
}
@catch (NSException *exception)
{
if ([[exception name] isEqualToString:NSObjectInaccessibleException])
return YES;             // Deleted.
else
[exception raise];      // Unknown exception thrown.
}
}


// Mac OS X 10.4 or earlier is not supported.
else
{
NSAssert(0, @"Unsupported version of Mac OS X detected.");
}
}

旧的/怀旧的答案:

我写了一个稍微好一点的方法。 self是你的核心数据类/控制器。

- (BOOL)hasManagedObjectBeenDeleted:(NSManagedObject *)managedObject
{
// 1) Per Apple, "may" be nil if |managedObject| was deleted but not always.
if (![managedObject managedObjectContext])
return YES;                 // Deleted.


// 2) Clone |managedObject|. All Properties will be un-faulted if deleted.
NSManagedObjectID *objectID             = [managedObject objectID];
NSManagedObject   *managedObjectClone   = [[self managedObjectContext] objectWithID:objectID];      // Always returns an object. Assumed to exist in the Persistent Store. If it does not exist in the Persistent Store, firing a fault on any of its Properties will throw an exception.


// 3) Fire faults for Properties. If any throw an exception, it was deleted.
NSEntityDescription *entityDescription  = [managedObjectClone entity];
NSDictionary        *propertiesByName   = [entityDescription propertiesByName];
NSArray             *propertyNames      = [propertiesByName allKeys];


@try
{
for (id propertyName in propertyNames)
(void)[managedObjectClone valueForKey:propertyName];
return NO;                  // Not deleted.
}
@catch (NSException *exception)
{
if ([[exception name] isEqualToString:NSObjectInaccessibleException])
return YES;             // Deleted.
else
[exception raise];      // Unknown exception thrown. Handle elsewhere.
}
}

正如 James Huddleston在他的回答中提到的,检查 NSManagedObject 的 -managedObjectContext是否返回 nil是一个“非常好”的方法,可以检查缓存/过期的 NSManagedObject 是否已经从持久存储中删除,但是它并不总是像苹果在他们的文档中说的那样准确:

如果接收方已从其 背景。

如果你使用被删除的 NSManagedObject 的 -objectID获取一个不同的 NSManagedObject,如下所示:

// 1) Create a new NSManagedObject, save it to the Persistant Store.
CoreData        *coreData = ...;
NSManagedObject *apple    = [coreData addManagedObject:@"Apple"];


[apple setValue:@"Mcintosh" forKey:@"name"];
[coreData saveMOCToPersistentStore];




// 2) The `apple` will not be deleted.
NSManagedObjectContext *moc = [apple managedObjectContext];


if (!moc)
NSLog(@"2 - Deleted.");
else
NSLog(@"2 - Not deleted.");   // This prints. The `apple` has just been created.






// 3) Mark the `apple` for deletion in the MOC.
[[coreData managedObjectContext] deleteObject:apple];


moc = [apple managedObjectContext];


if (!moc)
NSLog(@"3 - Deleted.");
else
NSLog(@"3 - Not deleted.");   // This prints. The `apple` has not been saved to the Persistent Store yet, so it will still have a -managedObjectContext.




// 4) Now tell the MOC to delete the `apple` from the Persistent Store.
[coreData saveMOCToPersistentStore];


moc = [apple managedObjectContext];


if (!moc)
NSLog(@"4 - Deleted.");       // This prints. -managedObjectContext returns nil.
else
NSLog(@"4 - Not deleted.");




// 5) What if we do this? Will the new apple have a nil managedObjectContext or not?
NSManagedObjectID *deletedAppleObjectID = [apple objectID];
NSManagedObject   *appleClone           = [[coreData managedObjectContext] objectWithID:deletedAppleObjectID];


moc = [appleClone managedObjectContext];


if (!moc)
NSLog(@"5 - Deleted.");
else
NSLog(@"5 - Not deleted.");   // This prints. -managedObjectContext does not return nil!




// 6) Finally, let's use the method I wrote, -hasManagedObjectBeenDeleted:
BOOL deleted = [coreData hasManagedObjectBeenDeleted:appleClone];


if (deleted)
NSLog(@"6 - Deleted.");       // This prints.
else
NSLog(@"6 - Not deleted.");

这是打印出来的资料:

2 - Not deleted.
3 - Not deleted.
4 - Deleted.
5 - Not deleted.
6 - Deleted.

如您所见,如果 NSManagedObject 已从持久存储中删除,则 -managedObjectContext并不总是返回 nil。

由于我最近在依赖于核心数据的 iOS 应用程序中实现 iCloud 的经验,我意识到最好的方法是观察框架的通知。至少比依赖一些模糊的方法好,这些方法可能会告诉您某个托管对象是否被删除,也可能不会告诉您。

对于“纯”的核心数据应用程序,你应该在主线程上观察 NSManagedObjectContextObjectsDidChangeNotification。通知的用户信息字典包含一组托管对象的 objectID,这些对象被插入、删除和更新。

如果您在这些集合中找到托管对象的 objectID,那么您可以以某种好的方式更新您的应用程序和 UI。

这就是它... 更多的信息,给一个机会,苹果的核心数据编程指南,并发与核心数据章节。其中有一节“使用通知跟踪其他线程中的更改”,但不要忘记检查前一节“使用线程限制来支持并发性”。

我担心其他答案中的讨论实际上掩盖了正确答案的简单性。在大多数情况下,正确答案是:

if ([moc existingObjectWithID:object.objectID error:NULL])
{
// object is valid, go ahead and use it
}

这个答案唯一不适用的情况是:

  1. 如果您的目标是 Mac OS 10.5或更早版本
  2. 如果你的目标是 iOS2.0或更早的版本
  3. 如果对象/上下文尚未保存(在这种情况下,您可以不关心,因为它不会抛出 NSObjectInaccessibleException,或者您可以使用 object.isDeleted)

在 Swift 3 Xcode 7.3中验证

您还可以简单地 PRINT每个上下文的内存引用并检查

(a) if the context exists,
(b) if the contexts of 2 objects are different

(书和会员是两个不同的对象)

 print(book.managedObjectContext)
print(member.managedObjectContext)

如果上下文存在但不同,它会打印类似的内容

0x7fe758c307d0
0x7fe758c15d70