Как я могу определить, был ли удален элемент NSManagedObject?

У меня есть NSManagedObject, который был удален, и контекст, содержащий этот управляемый объект, был сохранен. Я понимаю, что isDeleted возвращает YES, если Core Data попросит постоянное хранилище удалить объект во время следующей операции сохранения. Однако, поскольку сохранение уже произошло, isDeleted возвращает NO.

Что такое хороший способ узнать, был ли удален NSManagedObject после сохранения содержащего его контекста?

(Если вам интересно, почему объект, ссылающийся на удаленный управляемый объект, еще не знает об удалении, это потому, что удаление и сохранение контекста было инициировано фоновым потоком, который выполнил удаление и сохранил с помощью performSelectorOnMainThread:withObject:waitUntilDone:.)

Ответ 1

Проверка контекста управляемого объекта, похоже, работает:

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

Из документации Apple по managedObjectContext...

Этот метод может возвращать нуль, если приемник удален из своего контекст.

Если приемник неисправен, вызов этот метод не вызывает его.

Оба они кажутся хорошими.

ОБНОВЛЕНИЕ:. Если вы пытаетесь проверить, был ли удален удаленный управляемый объект с помощью objectWithID:, проверьте ответ Дэйва Галлахера. Он указывает, что если вы вызываете objectWithID: с помощью идентификатора удаляемого объекта, возвращаемый объект будет ошибкой, у которой не установлен managedObjectContext значение nil. Следовательно, вы не можете просто проверить его managedObjectContext, чтобы проверить, было ли оно удалено. Используйте existingObjectWithID:error:, если сможете. Если нет, например, вы ориентируетесь на Mac OS 10.5 или iOS 2.0, вам нужно сделать что-то еще, чтобы проверить удаление. Подробнее см. .

Ответ 2

ОБНОВЛЕНИЕ: Улучшенный ответ, основанный на идеях Джеймса Хаддлстона в обсуждении ниже.

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

Я написал немного лучший метод. self - ваш класс/контроллер Core Data.

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

В качестве Джеймса Хаддлстона, упомянутого в его ответе, проверка того, что NSManagedObject -managedObjectContext возвращает nil, является "довольно хорошим" способом увидеть, был ли кешированный/устаревший NSManagedObject удален из Persistent Store, но это не всегда точно, как Apple заявляет в своих документах:

Этот метод может возвращать нуль, если получатель был удален из своего контекст.

Когда он не вернет нуль? Если вы приобрели другой 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 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.

Как вы можете видеть, -managedObjectContext не всегда возвращает nil, если NSManagedObject был удален из Persistent Store.

Ответ 3

Я боюсь, что обсуждение в других ответах действительно скрывает простоту правильного ответа. В почти всех случаях правильный ответ:

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

Единственными случаями, в которых этот ответ не применяется, является:

  • Если вы используете Mac OS 10.5 или более раннюю версию
  • Если вы настроите iOS 2.0 или ранее
  • Если объект/контекст еще не сохранен (в этом случае вам все равно, потому что он не будет бросать NSObjectInaccessibleException, или вы можете использовать object.isDeleted)

Ответ 4

Из-за моего недавнего опыта внедрения iCloud в моем приложении iOS, основанного на Core Data для сохранения, я понял, что лучший способ - наблюдать за уведомлениями о структуре. По крайней мере, лучше, чем полагаться на некоторые неясные методы, которые могут или могут не указывать вам, удался ли какой-либо управляемый объект.

Для "чистых" приложений Core Data вы должны наблюдать NSManagedObjectContextObjectsDidChangeNotification в основном потоке. Словарь информации о пользователях уведомлений содержит наборы с объектными идентификаторами управляемых объектов, которые были вставлены, удалены и обновлены.

Если вы обнаружите свой идентификатор объекта управляемого объекта в одном из этих наборов, вы можете улучшить свое приложение и пользовательский интерфейс несколькими способами.

Чтобы он... для получения дополнительной информации дайте возможность руководству по программированию ядра Apple Core, Concurrency с главой Core Data. Там раздел "Отслеживание изменений в других потоках с использованием уведомлений", но не забудьте проверить предыдущий вариант "Использовать ограничение темы для поддержки Concurrency".

Ответ 5

попробуйте этот метод:

if (manageObject.deleted) {
    // assume that the managed object has been deleted.
}

Ответ 6

Проверено в Swift 3, Xcode 7.3

Вы также можете просто PRINT ссылки на память для каждого контекста и проверить

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

eg:( Book and Member - 2 разных объекта)

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

Он будет печатать что-то вроде этого, если контексты существуют, но разные

0x7fe758c307d0
0x7fe758c15d70