Шаблон CoreData+многопоточность. Правильно ли я делаю?

Я пишу приложение для iOS, которое использует Core Data для хранения своего материала. Данные хранятся в управляемых объектах и ​​обновляются в отдельных потоках (я использую GCD). В своем Core Data Programming Guide шаблоне Apple предлагает два способа адаптации Core Data для использования в многопоточной среде:

  1. Создайте отдельный контекст управляемого объекта для каждого потока и совместно используйте один координатор постоянного хранилища. Это обычно рекомендуемый подход.
  2. Создайте отдельный контекст управляемого объекта и постоянный координатор хранилища для каждого потока. Этот подход обеспечивает больший параллелизм за счет большей сложности (особенно если вам нужно сообщать об изменениях между разными контекстами) и увеличения использования памяти.

Поэтому я выбрал первое. У меня есть класс Database, который управляет всеми вещами, связанными с Core Data.

// Database.h

#import <CoreData/CoreData.h>

@interface Database : NSObject

@property (nonatomic, retain, readonly) NSManagedObjectModel *model;
@property (nonatomic, retain, readonly) NSManagedObjectContext *context;
@property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *coordinator;

+ (Database *)sharedInstance;

@end


// Database.m

#import "Database.h"

static NSManagedObjectModel *sharedModel;
static NSPersistentStoreCoordinator *sharedCoordinator;

static NSMutableDictionary *contexts;

@implementation Database

+ (NSMutableDictionary *)contextsDictionary
{
    if (!contexts) {
        contexts = [[NSMutableDictionary alloc] init];
    }
    return contexts;
}

+ (NSManagedObjectContext *)contextForThread:(NSThread *)thread
{
    id threadKey = @(thread.hash);
    NSManagedObjectContext *context = [Database contextsDictionary][threadKey];
    if (!context) {
        context = [[NSManagedObjectContext alloc] init];
        context.persistentStoreCoordinator = sharedCoordinator;
        contexts[threadKey] = context;
        [context release];
        [[NSNotificationCenter defaultCenter] addObserver:[Database class]
                                                 selector:@selector(threadWillExit:)
                                                     name:NSThreadWillExitNotification
                                                   object:thread];
    }
    return context;
}

+ (void)threadWillExit:(NSThread *)thread
{
    id threadKey = @(thread.hash);
    [contexts removeObjectForKey:threadKey];
    [[NSNotificationCenter defaultCenter] removeObserver:[Database class]
                                                    name:NSThreadWillExitNotification
                                                  object:thread];
}

+ (Database *)sharedInstance
{
    static Database *shared;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        shared = [[Database alloc] init];
    });
    return shared;
}

- (id)init
{
    self = [super init];
    if (self) {
        sharedModel = [[NSManagedObjectModel mergedModelFromBundles:nil] retain];
        sharedCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:sharedModel];

        NSString *docsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject];
        NSURL *storeUrl = [NSURL fileURLWithPath:[docsDir stringByAppendingPathComponent: @"MyModelFile"]];
        NSError *error = nil;
        [sharedCoordinator addPersistentStoreWithType:NSBinaryStoreType
                                        configuration:nil
                                                  URL:storeUrl
                                              options:nil
                                                error:&error];
        NSAssert(!error, @"Initialization error %@", error);
    }
    return self;
}

- (NSManagedObjectModel *)model
{
    return sharedModel;
}

- (NSPersistentStoreCoordinator *)coordinator
{
    return sharedCoordinator;
}

- (NSManagedObjectContext *)context
{
    return [Database contextForThread:[NSThread currentThread]];
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [sharedModel release];
    [sharedCoordinator release];
    [contexts release];

    [super dealloc];
}
@end

Вот мне и интересно, правильно ли я делаю? Есть ли проблемы в моем коде? Есть ли какие-нибудь шаблоны, которые я могу использовать здесь?

Спасибо.


person Max Komarychev    schedule 13.07.2013    source источник
comment
Ознакомьтесь с примечаниями к выпуску основных данных для OS X версии 10.7 и iOS 5.0. Контексты управляемых объектов, использующие новые типы параллелизма NSMainQueueConcurrencyType и NSPrivateQueueConcurrencyType, автоматически управляют своими очередями (и потоками) и намного проще в использовании. К сожалению, это еще не упомянуто в Руководстве по программированию основных данных.   -  person Martin R    schedule 13.07.2013
comment
Это отличная статья о Core Data и параллелизме: objc.io/issue. -2/common-background-practices.html   -  person Felix    schedule 13.07.2013
comment
Что ж, статьи, которые вы оба предложили, были действительно полезными, и теперь все это можно сделать намного проще! Спасибо!   -  person Max Komarychev    schedule 14.07.2013


Ответы (1)


Описанный паттерн многопоточности сейчас не актуален, поскольку базовые данные имеют встроенную поддержку параллелизма. Как упоминалось в статьях Core Data Release Notes для OS X v10. 7 и iOS 5.0 и Общие рекомендации moc может быть настроен с правильным типом параллелизма либо NSPrivateQueueConcurrencyType, либо NSMainQueueConcurrencyType. Затем каждая операция с контекстом управляемого объекта может выполняться в блоке, передаваемом методам performBlock или performBlockAndWait, и позволять внутренним компонентам данных обрабатывать все материалы параллелизма.

person Max Komarychev    schedule 22.03.2014