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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/4340445/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 18:12:47  来源:igfitidea点击:

How can I tell whether an `NSManagedObject` has been deleted?

core-dataiosnsmanagedobject

提问by James Huddleston

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

我有一个NSManagedObject已被删除的,并且包含该托管对象的上下文已被保存。据我所知,isDeleted返回YES如果核心数据会问持久存储在接下来的保存操作,以删除对象。但是,由于保存已经发生,isDeleted返回NO

What is a good way to tell whether an NSManagedObjecthas been deleted afterits containing context has been saved?

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

(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:.)

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

回答by James Huddleston

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

从 Apple 的文档中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.

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

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

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 nothave its managedObjectContextset to nil. Consequently, you can't simply check its managedObjectContextto 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 answerfor details.

更新:如果您尝试测试专门使用检索的托管对象是否objectWithID:已被删除,请查看Dave Gallagher 的回答。他指出,如果objectWithID:使用已删除对象的 ID 进行调用,则返回的对象将是将其managedObjectContext设置为 nil 的错误。因此,您不能简单地检查它managedObjectContext以测试它是否已被删除。existingObjectWithID:error:如果可以,请使用。如果不是,例如,您的目标是 Mac OS 10.5 或 iOS 2.0,则需要执行其他操作来测试删除。详情请看他的回答

回答by Dave Gallagher

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

更新:改进的答案,基于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.");
    }
}


OLD/DEPRECIATED ANSWER:

旧/弃用的答案:

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

我写了一个稍微好一点的方法。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.
    }
}


As James Huddlestonmentioned in his answer, checking to see if NSManagedObject's -managedObjectContextreturns nilis 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:

正如James Huddleston在他的回答中提到的,检查 NSManagedObject 的-managedObjectContext返回nil是否是一种“非常好的”方式来查看缓存/陈旧的 NSManagedObject 是否已从 Persistent Store 中删除,但正如 Apple 在其文档中所述,它并不总是准确的:

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

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

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

什么时候不返回nil?如果您使用已删除的 NSManagedObject 获取不同的 NSManagedObject,-objectID如下所示:

// 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, -managedObjectContextwon't always return nil if an NSManagedObject has been deleted from the Persistent Store.

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

回答by JosephH

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

回答by rmartinsjr

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.

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

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

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

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.

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

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".

就是这样……有关更多信息,请参阅 Apple 的 Core Data Programming Guide, Concurrency with Core Data 章节。有一节“使用通知跟踪其他线程中的更改”,但不要忘记检查上一节“使用线程限制来支持并发”。

回答by dusty

Thanks @Paludis. You are right. I removed the content so as not to confuse others. The reason I didn't delete it is because @Paludis' comment was valuable.

谢谢@Paludis。你是对的。我删除了内容以免混淆其他人。我没有删除它的原因是因为@Paludis 的评论很有价值。

回答by Naishta

Verified in Swift 3, Xcode 7.3

在 Swift 3、Xcode 7.3 中验证

You can also simply PRINTthe memory references of each context and check

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

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

eg:( Book and Member being 2 different objects)

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

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

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

如果上下文存在但不同,它会打印这样的东西

0x7fe758c307d0
0x7fe758c15d70