NSManagedObjectControllers只读取另一个线程上的访问权限

时间:2013-08-24 02:15:41

标签: ios objective-c multithreading thread-safety nsmanagedobjectcontext

我正在选择一些核心数据条目并对其进行排序。这很直接,但由于按距离排序的计算,我想把它放在另一个线程中。我通过在下面的#ifdef THREADS部分中添加代码为另一个线程创建一个新的NSManagedObjectController,这是通常的做法。

现在我可以进行提取,然后进行排序和组织,而不会减慢用户界面的速度,表格将在准备好后进行更新。

但是,由于我在本地托管对象上下文中进行提取,因此我的提取对象在我的主线程上无效 - 实际上它们从我的表视图单元格中消失。

所以,我的问题是:1)使用主要的托管对象上下文是否可以,因为我只是在阅读,而根本没有创建本地上下文? 2)在排序和组织后,是否应该使用搜索管理对象上下文对象中的ObjectID值从主线程上的主要托管对象上下文中重新获取所有对象? 3)我应该使用PerformBlockAndWait对主要托管对象上下文进行提取,但是保留排序并使用searchQ线程上的返回对象进行组织? 4)我没有想过的其他东西?

- (void) fetchShopLocations {
#ifdef THREADS
    dispatch_queue_t searchQ = dispatch_queue_create( "searchQueue", NULL );
    dispatch_async( searchQ, ^{
#endif
        NSError *error;

#ifdef THREADS
        // Make a managed object context that can be not on the main thread
        NSManagedObjectContext *localContext = [[NSManagedObjectContext alloc] init];
        [localContext setPersistentStoreCoordinator: [self.managedObjectContext persistentStoreCoordinator]];
#else
        NSManagedObjectContext *localContext = self.managedObjectContext;
#endif

        // Get the array of locations sorted by distance
        NSArray *locations = NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName: NSStringFromClass([Shop class])];
        // No sort descriptors since I cannot sort on things not in the database

        NSArray *locations = [context executeFetchRequest: request error: &error];
        if( !locations ) {
            NSLog( @"Shop fetch error: %@", error );
            return;
        }
        locations = [locations sortedArrayUsingComparator: ^NSComparisonResult( Shop *loc1, Shop *loc2 ) {
                    double dist1 = [loc1 distanceFromLatitude: self.referenceLatitude andLongitude: self.referenceLongitude];
                    double dist2 = [loc2 distanceFromLatitude: self.referenceLatitude andLongitude: self.referenceLongitude];
                    if( dist1 < dist2 )
                            return NSOrderedAscending;
                    else if( dist1 > dist2 )
                            return NSOrderedDescending;
                    return [[loc1 name] localizedCaseInsensitiveCompare: [loc2 name]];
        }];

        NSMutableArray *sections = [NSMutableArray arrayWithCapacity: 5];
        NSMutableDictionary *section;
        NSMutableArray *rows;

        section = [NSMutableDictionary dictionaryWithCapacity: 2];
        rows = [NSMutableArray arrayWithCapacity: locations.count];
        __block double sectionLimitUserUnits = 5.0;
        __block double sectionLimitKilometers;
        if( self.userUnits == LocationDistanceUnitsMiles )
              sectionLimitKilometers = sectionLimitUserUnits * KILOMETERS_PER_MILE;
        else  sectionLimitKilometers = sectionLimitUserUnits;
        [section setValue: [NSString stringWithFormat: @"Within %g", sectionLimitUserUnits] forKey: kSectionHeaderTitleKey];
        [locations enumerateObjectsUsingBlock: ^( Shop *loc, NSUInteger idx, BOOL *stop ) {
            double distance = [loc distanceFromLatitude: self.referenceLatitude andLongitude: self.referenceLongitude];
            if( distance > self.maxDistance ) {
                *stop = YES;
            } else {
                while( distance > sectionLimitKilometers ) {
                    [section setValue: [rows copy] forKey: kRowKey];
                    [sections addObject: [section copy]];
                    [section removeAllObjects];
                    [rows removeAllObjects];

                    sectionLimitUserUnits += 5.0;
                    if( self.userUnits == LocationDistanceUnitsMiles )
                          sectionLimitKilometers = sectionLimitUserUnits * KILOMETERS_PER_MILE;
                    else  sectionLimitKilometers = sectionLimitUserUnits;
                    [section setValue: [NSString stringWithFormat: @"Within %g", sectionLimitUserUnits] forKey: kSectionHeaderTitleKey];
                }
                [rows addObject: loc];
            }
        }];
        [section setValue: [rows copy] forKey: kRowKey];
        [sections addObject: [section copy]];

#ifdef THREADS
        dispatch_async( dispatch_get_main_queue(), ^{
#endif
            self.sections = sections;
            [self.tableView reloadData];
#ifdef THREADS
        });
    });
    dispatch_release( searchQ );
#endif
}

由于线程冲突的非确定性,我知道我不能仅仅依靠测试模拟器中看起来有用的东西。

提前致谢!

2 个答案:

答案 0 :(得分:0)

我强烈建议您将NSManagedObjectContext上的所有操作保留在单个线程或串行队列中。使用iOS5,您可以使用专用队列并发类型创建NSManagedObjectContext,并在该上下文中处理performBlock方法中的所有操作。因此,要回答问题1,只能在与其关联的线程或队列中访问NSManagedObjectContext及其关联。如果是为另一个队列创建的,请不要在主队列中访问它。它会给你带来头痛的道路!

问题2是最好的方法。在后台进行艰苦的工作,然后只在主线程上获取所需的内容,这是一种很好的,安全的方式来处理你想要做的事情。

问题3:如果您的主要托管对象是使用NSMainQueueConcurrencyType并发类型创建的,那么您不需要在主线程上执行performBlock:或performBlockAndWait :.如果你需要从后台线程做一些事情,你只需要使用它们。

另外,我强烈推荐MagicalRecord。它让我的生活更轻松地处理核心数据,并且显着减少了我为编写简单的事情而必须编写的样板代码量。

〜詹姆斯

答案 1 :(得分:0)

  

1)使用主要的托管对象上下文是否可以,因为我只是在阅读,而根本不创建本地上下文?

没有。总有一天,有些东西会同时使用fetch访问主要的托管对象上下文,这将是一个令人头痛的问题。 (我遇到了这样的死锁。)

  

2)在排序和组织后,是否应该使用搜索管理对象上下文对象中的ObjectID值从主线程上的主要托管对象上下文中重新获取所有对象?

不是所有对象,因为您已经在使用表视图,所以您只需要重新获取将在表中显示的对象。使用ObjectID从主线程重新获取对象是正确的做法。

  

3)我应该使用PerformBlockAndWait对主要托管对象上下文进行提取,但是保留排序并使用searchQ线程上的返回对象进行组织

这是另一种选择,但你需要理解非常好它是如何工作的。你正在做的方法实际上更简单。

  

4)我没有想过的其他东西?

您可以通过创建返回ORDER BY距离的查询来避免在代码中对数组进行排序。请参阅this answer