如何判断“NSManagedObject”是否已被删除?

发布于 2024-10-05 13:04:26 字数 388 浏览 5 评论 0原文

我有一个已被删除的 NSManagedObject,并且包含该托管对象的上下文已被保存。据我所知,如果 Core Data 要求持久存储在下一次保存操作期间删除对象,则 isDeleted 返回 YES。但是,由于保存已经发生,isDeleted 返回NO

有什么好方法可以判断 NSManagedObject 在保存其包含的上下文之后是否已被删除?

(如果您想知道为什么引用已删除托管对象的对象尚未意识到删除,这是因为删除和上下文保存是由后台线程启动的,该后台线程使用performSelectorOnMainThread执行删除和保存: withObject:waitUntilDone:。)

I have an NSManagedObject that has been deleted, and the context containing that managed object has been saved. I understand that isDeleted returns YES if Core Data will ask the persistent store to delete the object during the next save operation. However, since the save has already happened, isDeleted returns NO.

What is a good way to tell whether an NSManagedObject has been deleted after its containing context has been saved?

(In case you're wondering why the object referring to the deleted managed object isn't already aware of the deletion, it's because the deletion and context save was initiated by a background thread which performed the deletion and save using performSelectorOnMainThread:withObject:waitUntilDone:.)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

旧城烟雨 2024-10-12 13:04:26

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

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

来自 Apple 关于 managedObjectContext 的文档 ...

如果以下情况,此方法可能返回 nil
接收者已从其中删除
上下文。

如果接收器出现故障,请致电
此方法不会导致其触发。

这两件事似乎都是好事。

更新:如果您尝试测试专门使用objectWithID:检索的托管对象是否已被删除,请查看戴夫·加拉格尔的回答。他指出,如果您使用已删除对象的 ID 调用 objectWithID:,则返回的对象将出现错误,没有managedObjectContext 设置为零。因此,您不能简单地检查其 managementObjectContext 来测试它是否已被删除。如果可以的话,请使用 existingObjectWithID:error:。如果不是,例如,您的目标是 Mac OS 10.5 或 iOS 2.0,则需要执行其他操作来测试删除。请参阅他的回答细节。

Checking the context of the managed object seems to work:

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

From Apple's documentation on managedObjectContext ...

This method may return nil if the
receiver has been deleted from its
context.

If the receiver is a fault, calling
this method does not cause it to fire.

Both of those seem to be good things.

UPDATE: If you're trying to test whether a managed object retrieved specifically using objectWithID: has been deleted, check out Dave Gallagher's answer. He points out that if you call objectWithID: using the ID of a deleted object, the object returned will be a fault that does not have its managedObjectContext set to nil. Consequently, you can't simply check its managedObjectContext to test whether it has been deleted. Use existingObjectWithID:error: if you can. If not, e.g., you're targeting Mac OS 10.5 or iOS 2.0, you'll need to do something else to test for deletion. See his answer for details.

默嘫て 2024-10-12 13:04:26

更新:基于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 的 -managementObjectContext 是否返回 nil 是查看缓存是否存在的“相当好的”方法。 /stale NSManagedObject 已从持久存储中删除,但它并不总是准确,正如 Apple 在其文档中所述:

如果接收者已从其接收者中删除,此方法可能返回nil
上下文。

什么时候不会返回nil?如果您使用已删除的 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 有已从持久存储中删除。

UPDATE: An improved answer, based on James Huddleston's ideas in the discussion below.

- (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.");
    }
}

OLD/DEPRECIATED ANSWER:

I wrote a slightly better method. self is your Core Data class/controller.

- (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.
    }
}

As James Huddleston mentioned in his answer, checking to see if NSManagedObject's -managedObjectContext returns nil is a "pretty good" way of seeing if a cached/stale NSManagedObject has been deleted from the Persistent Store, but it's not always accurate as Apple states in their docs:

This method may return nil if the receiver has been deleted from its
context.

When won't it return nil? If you acquire a different NSManagedObject using the deleted NSManagedObject's -objectID like so:

// 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.");

Here's the printout:

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

As you can see, -managedObjectContext won't always return nil if an NSManagedObject has been deleted from the Persistent Store.

吻风 2024-10-12 13:04:26

我担心其他答案中的讨论实际上隐藏了正确答案的简单性。在几乎所有情况下,正确答案是:

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

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

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

I fear the discussion in the other answers is actually hiding the simplicity of the correct answer. In pretty much all cases, the correct answer is:

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

The only cases this answer doesn't apply in is:

  1. If you are targetting Mac OS 10.5 or earlier
  2. If you are targetting iOS 2.0 or earlier
  3. If the object/context has not been saved yet (in which case you either don't care because it won't throw a NSObjectInaccessibleException, or you can use object.isDeleted)
唱一曲作罢 2024-10-12 13:04:26

由于我最近在依赖 Core Data 进行持久性的 iOS 应用程序中实现 iCloud 的经验,我意识到最好的方法是观察框架的通知。至少,比依赖一些晦涩的方法要好,这些方法可能会或可能不会告诉您某些托管对象是否已被删除。

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

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

就是这样...有关更多信息,请参阅 Apple 的核心数据编程指南,核心数据并发章节。有一节“使用通知跟踪其他线程中的更改”,但不要忘记检查上一节“使用线程限制支持并发”。

Due to my recent experience implementing iCloud in my iOS app that relies on Core Data for persistence, I realized that the best way is observing the framework's notifications. At least, better than relying on some obscure methods that may, or may not tell you if some managed object was deleted.

For 'pure' Core Data apps you should observe NSManagedObjectContextObjectsDidChangeNotification on the main thread. The notification's user info dictionary contains sets with the managed objects' objectIDs that were inserted, deleted and updated.

If you find your managed object's objectID in one of these sets, then you can update your application and UI in some nice way.

That's it... for more information, give a chance to Apple's Core Data Programming Guide, Concurrency with Core Data chapter. There's a section "Track Changes in Other Threads Using Notifications", but don't forget to check the previous one "Use Thread Confinement to Support Concurrency".

破晓 2024-10-12 13:04:26

在 Swift 3、Xcode 7.3 中验证

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

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

例如:( Book 和 Member 是 2 个不同的对象)

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

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

0x7fe758c307d0
0x7fe758c15d70

Verified in Swift 3, Xcode 7.3

You can also simply PRINT the memory references of each context and check

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

eg:( Book and Member being 2 different objects)

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

It would print something like this if the contexts exist but are different

0x7fe758c307d0
0x7fe758c15d70
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文