Question

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!

Was it helpful?

Solution

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.

OTHER TIPS

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

I guess it is because you are directly assigning the value to the array. Try assigning value by using mutablecopy

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

I've found several weak points in your code, try to work them out:

  1. You never use pageNo parameter in your + (void)getIdeasTasksWithPageNo:(int)pageNo completionHandler:(void (^)(NSArray *, NSError *))completionHandler

  2. You always pass nil for error parameter in the method above

  3. What is the purpose of removing objects from array [self.tasksArray removeAllObjects]; ? (firstly try to comment this line maybe this is the reason)

First, check if you are using CoreData in a multi-thread circumstance, if you do, you may need to add some dispatch queue code to make sure your CoreData code running on only one thread. Second, call some other methods of your result array to see if the NSManagedObject is just in a fault status, calling some methods of it will make it fetched.

Also, perform one prophylactic check on the implementation of your NSManagedObject class with unexpected Null value in fields. In the .m file of NSManagedObject class, make sure that @dynamic is executed for the fields, and not for example, for some reason, by mistake @synthesize.

// NullFieldsCoreDataObject.h
@interface NullFieldsCoreDataObject : NSManagedObject
@property (nonatomic, retain) NSString * nullField;
@property (nonatomic, retain) NSString * anotherOneNullField;
@property (nonatomic, retain) NSNumber * andAnotherOneNullField;
@end

// NullFieldsCoreDataObject.m
#import "NullFieldsCoreDataObject.h"

@implementation NullFieldsCoreDataObject
// wrong:
//@synthesize nullField, anotherOneNullField, andAnotherOneNullField;
// correctly:
@dynamic nullField, anotherOneNullField, andAnotherOneNullField;
@end
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top