Как я могу сказать, был ли удален NSManagedObject? - PullRequest
67 голосов
/ 03 декабря 2010

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

. Как правильно определить, было ли NSManagedObject удалено после того, как содержащий его контекст былсохранен?

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

Ответы [ 6 ]

90 голосов
/ 03 декабря 2010

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

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

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

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

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

Обе эти вещи кажутся хорошими.

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

39 голосов
/ 26 октября 2011

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

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

Как Джеймс Хаддлстон упомянул в своем ответе, проверка, чтобы проверить, возвращает ли -managedObjectContext NSManagedObject nil "довольно хороший" способ проверить, был ли удаленный из кэша / устаревший NSManagedObject удален из Persistent Храните, но это не всегда точно, как утверждает 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'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.

Как видите, -managedObjectContext не всегда возвращает nil, если NSManagedObject был удален из постоянного хранилища.

27 голосов
/ 31 июля 2013

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

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)
11 голосов
/ 12 января 2012

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

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

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

Вот и все ... для получения дополнительной информации, дайте шанс главе Apple по программированию основных данных, глава Параллелизм с основными данными.Есть раздел «Отслеживание изменений в других потоках с помощью уведомлений», но не забудьте проверить предыдущий «Использовать ограничение потоков для поддержки параллелизма».

1 голос
/ 03 февраля 2016

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

if (manageObject.deleted) {
    // assume that the managed object has been deleted.
}
0 голосов
/ 30 августа 2016

Проверено в 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
...