Объекты Core Data становятся нулевыми

Я создаю приложение задач, которое должно поддерживать автономный режим. Я использовал RestKit для загрузки задач и сопоставил их в локальных данных Core.

Это работает в режиме онлайн. Но в оффлайне есть странная проблема. Я использую NSPredicate для извлечения данных из локального хранилища. Для этого я использую магические записи.

+ (void)getIdeasTasksWithPageNo:(int)pageNo completionHandler:(void (^)(NSArray *, NSError *))completionHandler {

    NSArray *tasks = [self MR_findAllWithPredicate:[NSPredicate predicateWithFormat:@"due_date = nil AND user_id = %@", [DBUsers currentUser].id]];
    completionHandler(tasks, nil);
}

И я называю это следующим образом:

[DBTasks getIdeasTasksWithPageNo:1 completionHandler:^(NSArray *tasks, NSError *error) {
            if (!error) {
                [self displayTasksWithResults:tasks forPageNo:1];                   

            } else {
                NSLog(@"Error is %@", error);
            }
        }];

И вот как я показываю его в UITableView

-(void)displayTasksWithResults:(NSArray *)tasks forPageNo:(int)pageNo {
    if (!self.tasksArray) {
        self.tasksArray = [[NSMutableArray alloc] init];

    } else {
        [self.tasksArray removeAllObjects];
    }
    [self.tasksArray addObjectsFromArray:tasks];
    [self.tableview reloadData];
}

Это работает только в первый раз, и все задачи заполняются в UITableView.

Проблема возникает после заполнения UITableView, все записи в self.tasksArray становятся Null. Если я прокручиваю UITableView, строки таблицы начинают пуст.

Но если я печатаю self.tasksArray в методе displayTasksWithResults, он отлично отпечатывается.

(
    "Title: Task 01",
    "Title: You've gone incognito. Pages you view in incognito tabs won't stick around in your browser history, cookie store, or search history after you've closed all of your incognito tabs. Any files you download or bookmarks you create will be kept. ",
    "Title: Task 06",
    "Title: Task 04",
    "Title: Hi",
    "Title: Task 3",
    "Title: Task 4",
    "Title: Hi 4",
    "Title: hh",
    "Title: Task 02",
    "Title: Task 05\n",
    "Title: Task 4",
    "Title: Task 5",
    "Title: Task 2 updated",
    "Title: Here is a task. ",
    "Title: Task 03",
    "Title: Hi 3",
    "Title: Task 2",
    "Title: Hi 2",
    "Title: Testing task email with Idea Task",
    "Title: Task f6",
    "Title: 1.117",
    "Title: Task f5",
    "Title: Task f12",
    "Title: Task f4",
    "Title: Task f3",
    "Title: 111.0.113",
    "Title: 111.0.115",
    "Title: Pages you view in incognito tabs won't stick around in your browser history, cookie store, or search history after you've closed all of your incognito tabs. Any files you download or bookmarks you create will be kept.",
    "Title: Task f7",
    "Title: 1.116",
    "Title: 1.118",
    "Title: Going incognito doesn't hide your browsing from your employer, your internet service provider, or the websites you visit. ",
    "Title: 111.0.111"
)

Если я напечатаю self.taskArray позже, может быть в делегате didSelectRow UITableView, он печатает, как показано ниже:

(
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)",
    "Title: (null)"
)

Я думаю, что это может быть связано с NSManagedObjectContext, но не знаю, как это исправить.

Пожалуйста, помогите!

Ответ 1

Проблема заключается в том, что (как я писал в комментарии) объекты извлекаются в фоновом потоке, но используются в основном потоке (UI). Управляемые объекты могут "жить" только в том контексте, что они были созданы в. Если контекст освобожден, объекты все еще существуют, но методы доступа к ресурсам возвращаются только nil.

Возможные решения:

  • Получить объекты в основном потоке.
  • Используйте

    NSManagedObject *copy = [[mainContext objectWithID:[object objectID]];
    

    чтобы "скопировать" объекты из фонового контекста в основной контекст. (Возможно, MagicalRecord имеет удобный метод.)

  • Вместо выборки управляемых объектов установите

    [fetchRequest setResultType:NSDictionaryResultType];
    [fetchRequest setPropertiesToFetch:@[@"title", ...]];
    

    чтобы получить массив словарей с интересующими вас атрибутами.

Ответ 2

Наконец, найдена причина для объектов Null. Я звонил в фоновом режиме. Итак, Magical Records создает новый Контекст управляемого объекта, специфичный для этого потока, в NSManagedObject+MagicalFinders.m

+ (NSManagedObjectContext *) MR_contextForCurrentThread;
{
    if ([NSThread isMainThread])
    {
        return [self MR_defaultContext];
    }
    else
    {
        NSMutableDictionary *threadDict = [[NSThread currentThread] threadDictionary];
        NSManagedObjectContext *threadContext = [threadDict objectForKey:kMagicalRecordManagedObjectContextKey];
        if (threadContext == nil)
        {
            threadContext = [self MR_contextWithParent:[NSManagedObjectContext MR_defaultContext]];
            [threadDict setObject:threadContext forKey:kMagicalRecordManagedObjectContextKey];
        }
        return threadContext;
    }
}

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

+ (void)backgroundFetchWithPredicate:(NSPredicate *)predicate completion:(void(^)(NSArray *, NSError *))completion {
    NSManagedObjectContext *privateContext = [NSManagedObjectContext MR_context];
    [privateContext performBlock:^{
        NSArray *privateObjects = [self MR_findAllWithPredicate:predicate inContext:privateContext];
        NSArray *privateObjectIDs = [privateObjects valueForKey:@"objectID"];
        // Return to our main thread
        dispatch_async(dispatch_get_main_queue(), ^{
            NSPredicate *mainPredicate = [NSPredicate predicateWithFormat:@"self IN %@", privateObjectIDs];
            NSArray *finalResults = [self MR_findAllWithPredicate:mainPredicate];
            completion(finalResults, nil);
        });
    }];
}

Теперь мне просто нужно вызвать его так:

[self backgroundFetchWithPredicate:predicate completion:^(NSArray *results, NSError *error) {
            completionHandler(results, error);
        }];

Ответ 3

Я предполагаю, что это потому, что вы прямо присваиваете значение массиву. Попробуйте присвоить значение с помощью mutablecopy

NSArray *tasks = [[self MR_findAllWithPredicate:[NSPredicate predicateWithFormat:@"due_date = nil AND user_id = %@", [DBUsers currentUser].id]] mutableCopy];

Ответ 4

Я нашел несколько слабых мест в вашем коде, попытаюсь их выработать:

  • Вы никогда не используете параметр pageNo в + (void)getIdeasTasksWithPageNo:(int)pageNo completionHandler:(void (^)(NSArray *, NSError *))completionHandler

  • Вы всегда передаете nil для параметра error в методе выше

  • Какова цель удаления объектов из массива [self.tasksArray removeAllObjects];? (во-первых, попробуйте прокомментировать эту строку, возможно, это и есть причина).

Ответ 5

Во-первых, проверьте, используете ли вы CoreData в многопоточном случае, если вам нужно, вам может потребоваться добавить код очереди отправки, чтобы убедиться, что ваш код CoreData работает только в одном потоке. Во-вторых, вызовите некоторые другие методы вашего массива результатов, чтобы убедиться, что NSManagedObject находится только в состоянии сбоя, вызывая некоторые его методы, он выберет его.