【问题标题】:Core Data objects become null核心数据对象变为空
【发布时间】:2014-04-29 06:33:00
【问题描述】:

我正在创建一个应该支持离线模式的任务应用程序。我已经使用 RestKit 下载任务并将其映射到本地核心数据中。

这在在线模式下运行良好。但是在离线时有一个奇怪的问题。我使用 NSPredicate 从本地存储中获取数据。为此,我正在使用 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);
}

我这样称呼它:

[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,表格行开始为空。

但如果我在displayTasksWithResults 方法中打印self.tasksArray,它会完美打印。

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

如果我稍后打印self.taskArray,可能在UITableViewdidSelectRow 代表中,它会打印如下:

(
    "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 有关,但不知道如何解决。

请帮忙!

【问题讨论】:

  • tasks 为空时,无论如何您都在清除tasksArray。有你的问题。
  • @Desdenova,感谢您的回复,清空任务数组永远不会被调用,它只会在新的数据请求时被调用。顺便说一句,我也尝试过评论清空任务数组的行,但仍然无法正常工作。
  • 好的,显然有些东西正在清空数组。尝试在属性上放置一个断点,看看是什么在访问它。你应该可以从那里回溯。
  • 对象是否在辅助托管对象上下文中获取?如果该上下文被释放,对象将继续存在,但其所有属性的计算结果为nil
  • @Desdenova,tasksArray 在任何地方都不是空的。 tasksArray.count 仍然显示 34。它是空的数据。这在在线模式下也很完美,对象由 RestKit 检索。

标签: ios iphone objective-c core-data restkit-0.20


【解决方案1】:

问题是(正如我在评论中所写)对象是在后台线程上获取的,但在主(UI)线程上使用。托管对象只能“生活”在 它们是在其中创建的。如果上下文被释放,对象仍然存在,但是 属性访问器方法只返回 nil

可能的解决方案:

  • 在主线程上获取对象。
  • 使用

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

    将对象从背景上下文“复制”到主上下文。 (也许是魔法唱片 有一个方便的方法。)

  • 不是获取托管对象,而是设置

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

    获取具有您感兴趣的属性的字典数组。

【讨论】:

  • 如果对象只能在创建的上下文中“存活”,您可以在 CoreData 默认上下文中创建 findAll ([NSManagedObjectContext MR_defaultContext]) 吗?它似乎确实解决了我的问题,因为这个上下文永远不会被释放。 NSArray *tasks = [self MR_findAllWithPredicate:[NSPredicate predicateWithFormat:@"due_date = nil AND user_id = %@", [DBUsers currentUser].id] inContext:[NSManagedObjectContext MR_defaultContext]];
  • 事实证明,即使上下文没有被释放,你也不应该在其他线程中使用 NSManagedObjects。在访问其他线程上的某些属性时,我不断出现随机崩溃。现在我正在传递 ID 并在需要它们的地方检索 NSManagedObjects。在另一种情况下,我将自己的对象与 CoreData 分离。
  • 我不知道我是怎么错过的。被困在这两个星期.. grrr
【解决方案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;
    }
}

所以我需要将对象从后台线程上下文复制到主线程上下文。我终于找到了解决方案here,并像这样创建了我的方法。

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

    【讨论】:

    • 感谢您的回复,我已经尝试了您的解决方案,但 mutableCopy 甚至无法正常工作。结果仍然为空
    【解决方案4】:

    我在您的代码中发现了几个弱点,请尝试解决它们:

    1. 您永远不会在 + (void)getIdeasTasksWithPageNo:(int)pageNo completionHandler:(void (^)(NSArray *, NSError *))completionHandler 中使用 pageNo 参数

    2. 在上述方法中,error 参数总是传递 nil

    3. 从数组[self.tasksArray removeAllObjects]; 中删除对象的目的是什么? (首先尝试评论这行可能是这个原因)

    【讨论】:

    • @Stats,感谢您的回复。实际上我在这里提到了完整的代码。 pageNo 用于分页。在原始代码中,如果有错误,我调用 completionHandler(nil, error); [self.tasksArray removeAllObjects] 用于如果用户再次调用数据,我们需要从数组中删除以前的数据并添加新的获取数据,如果我不删除,它会在 UITableView 中显示重复记录。
    • 我的问题是我在后台线程中调用方法。后台线程中的数据是使用临时托管对象上下文获取的。所以当我回到主线程时,那个临时的托管对象上下文变成了空,所以所有的对象也变成了空。现在我正在寻找某种方法将对象从临时托管对象上下文复制到我的默认托管对象(在主线程中使用)。
    【解决方案5】:

    首先,检查您是否在多线程环境中使用 CoreData,如果是,您可能需要添加一些调度队列代码以确保您的 CoreData 代码仅在一个线程上运行。 其次,调用结果数组的一些其他方法来查看 NSManagedObject 是否只是处于故障状态,调用它的一些方法会使其获取。

    【讨论】:

      【解决方案6】:

      此外,对字段中出现意外 Null 值的 NSManagedObject 类的实现执行一项预防性检查。 在 NSManagedObject 类的 .m 文件中,确保为字段执行了@dynamic,而不是例如由于某种原因错误地@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
      

      【讨论】:

        猜你喜欢
        • 2015-04-09
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-08
        • 2012-01-30
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多