Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Core Data objects become null

I am creating a tasks application which should support offline mode. I have used RestKit to download tasks and mapped that in local Core data.

This is working good in online mode. But in offline there is strange problem. I use NSPredicate to fetch data from local storage. For this I am using Magical Records.

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

And I call it like this:

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

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

And this is how I am displaying it in 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];
}

This is working only for first time and all tasks are populated in UITableView.

Problem is after the UITableView is populated, all records in self.tasksArray become Null. If I scroll UITableView, the table rows start being empty.

But if I print self.tasksArray in displayTasksWithResults method, it prints perfectly.

(
    "Title: Task 01",
    "Title: You've gone incognito. Pages you view in incognito tabs won't stick around in your browser's 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's 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"
)

If I print self.taskArray later, may be in didSelectRow delegate of UITableView, it prints like below:

(
    "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)"
)

I think this may be some thing related to NSManagedObjectContext, but don't know how to fix it.

Please help!

like image 471
Khawar Avatar asked Apr 29 '14 06:04

Khawar


People also ask

When should I use Core Data?

Use Core Data to save your application's permanent data for offline use, to cache temporary data, and to add undo functionality to your app on a single device. To sync data across multiple devices in a single iCloud account, Core Data automatically mirrors your schema to a CloudKit container.

What is Nsmanagedobjectcontext in Core Data?

An object space to manipulate and track changes to managed objects.

How delete all Core Data?

Delete Everything (Delete All Objects, Reset Core Data) One approach to delete everything and reset Core Data is to destroy the persistent store. Deleting and re-creating the persistent store will delete all objects in Core Data.

What is Core Data database?

Core Data is a framework that you use to manage the model layer objects in your application. It provides generalized and automated solutions to common tasks associated with object life cycle and object graph management, including persistence.


2 Answers

The problem is that (as I wrote in a comment) that the objects are fetched on a background thread, but used on the main (UI) thread. Managed objects can only "live" in the context that they were created in. If the context is deallocated, the objects still exist, but the property accessor methods return just nil.

Possible solutions:

  • Fetch the objects on the main thread.
  • Use

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

    to "copy" the objects from the background context to the main context. (Perhaps MagicalRecord has a convenience method.)

  • Instead of fetching managed objects, set

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

    to fetch an array of dictionaries with the attributes you are interested in.

like image 166
Martin R Avatar answered Oct 20 '22 18:10

Martin R


Finally found the reason for Null objects. I was calling fetching in background. So Magical Records creates a new Managed Object Context specific for that thread, in 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;
    }
}

So I needed to copy objects from background thread context to main thread context. I finally found the solution here and created my method like this.

+ (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);
        });
    }];
}

Now I just need to call it like this:

[self backgroundFetchWithPredicate:predicate completion:^(NSArray *results, NSError *error) {
            completionHandler(results, error);
        }];
like image 29
Khawar Avatar answered Oct 20 '22 18:10

Khawar