Подтвердить что ты не робот

Рекомендации по передаче NSManagedObjectContext в контроллеры дочерних представлений UITabBarController?

Мне кажется, что здесь ситуация Catch-22. Обратите внимание на следующие широко (и умные) позиции в архитектуре приложений:

  • Apple (и большинство разработчиков alpha) рекомендуют не использовать синглтон или получать доступ к делегату Singleton для получения NSManagedObjectContext. Жесткость, плохой дизайн и т.д. ОК - я согласен!
  • Итерация над контроллерами дочерних представлений UITabbarController и предоставление каждому дочернему VC ссылки на контекст (инъекция зависимостей) также глупы, потому что вы загружаете каждую вкладку своего приложения во время запуска приложения, просто чтобы передать ссылку. Это также противоречит тому, что Apple рекомендует для архитектуры приложений.

Как вы это решаете? Вы используете NotificationCenter для публикации уведомлений, когда контроллер просмотра пробуждается от nib, и передайте делегат приложения в контексте ссылки? Это единственный способ, которым я могу думать о том, что дживы с обоими # 1 и # 2, но это также похоже на какой-то изгиб для меня.

Есть ли более элегантный способ?

Изменить: делать уведомление, когда инициализация контроллера представления может быть условием гонки, так как если вы делаете что-то со Storyboard, ваши контрольные контроллеры дочерних элементов вашего табулятора, как правило, инициализируются (хотя sans-view загрузка) при запуске. Поэтому вам нужно будет сделать такое уведомление в viewDidLoad, что является плохой идеей в отношении соглашения MVC. Это также связывает ваши руки с чем-либо с моделями данных (например, предварительное кэширование для производительности), прежде чем пользователь сделает что-то связанное с просмотром.

4b9b3361

Ответ 1

Когда вы передаете экземпляры NSManagedObject в контроллер представления, этот контроллер просмотра может сохранять эти объекты. Затем он может перейти в NSManagedObjectContext через эти управляемые объекты, вызвав

-[NSManagedObject managedObjectContext]

Я не уверен, что это будет работать в вашем конкретном случае, но часто это произойдет. Делегат приложения или контроллер корневого представления создают контекст, а затем передают управляемые объекты.

Update

Если вам нужно использовать контекст в нескольких местах, есть другой шаблон, который я нашел полезным:

Подкласс NSManagedObjectContext:

@interface MyManagedObjectContext : NSManagedObjectContext

+ (MyManagedObjectContext *)mainThreadContext;

@end

то есть. одноэлементный контекст для пользовательского интерфейса/основного потока. Это проще, чем использование делегата приложения, поскольку другим классам не нужно будет обращаться к делегату приложения, но может использовать этот класс напрямую. Также инициализация магазина и модели может быть инкапсулирована в этот класс:

@implementation MyManagedObjectContext

+ (MyManagedObjectContext *)mainThreadContext;
{
   static MyManagedObjectContext *moc;
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       moc = [[self alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
       // Setup persistent store coordinator here
   });
   return moc;
}

@end

Ответ 2

Я вижу, что прошло некоторое время с тех пор, как вы опубликовали свой вопрос, но у меня есть другой подход, который я хотел бы поделиться с вами и всеми остальными.

Я предполагаю, что вы хотите внедрить контекст управляемого объекта во все/некоторые из контроллеров представления, которые отображаются как вкладки UITabViewController и что вы используете раскадровку с UITabBarController как rootViewController.

  • В своем заголовке AppDelegate создайте приложение AppDelegate для реализации протокола UITabBarControllerDelegate.

    @interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
    ...
  • В вашей реализации AppDelegate добавьте следующий метод UITabBarControllerDelegate. Он позаботится о настройке контекста управляемого объекта в любом контроллере представления, который обладает свойством.

    - (BOOL) tabBarController:(UITabBarController *)tabBarController
    shouldSelectViewController:(UIViewController *)viewController {
    if ([viewController respondsToSelector:@selector(setManagedObjectContext:)]) {
        if ([viewController performSelector:@selector(managedObjectContext)] == nil) {
            [viewController performSelector:@selector(setManagedObjectContext:) withObject:self.managedObjectContext];
        }
    }
    return YES;
    }
  • В вашем приложении: didFinishLaunchingWithOptions: установите self как делегат UITabBarController.

    UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController;
    tabBarController.delegate = self;
  • К сожалению, первый контроллер просмотра, который должен быть загружен, не готов в это время (в tabBarController.selectedViewController) и не вызывает также делегата. Таким образом, самый простой способ установить первый - наблюдать за свойством selectedViewController элемента TabBarController

    [tabBarController addObserver:self forKeyPath:@"selectedViewController"
                          options:NSKeyValueChangeSetting context:nil];

    и установите его там.

    - (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    [object removeObserver:self forKeyPath:@"selectedViewController"];
    UIViewController *viewController = [change valueForKey:NSKeyValueChangeNewKey];
    if ([viewController respondsToSelector:@selector(setManagedObjectContext:)]) {
        if ([viewController performSelector:@selector(managedObjectContext)] == nil) {
            [viewController performSelector:@selector(setManagedObjectContext:) withObject:self.managedObjectContext];
        }
    }
    }

Ответ 3

Я бы создал базовый класс поставщика данных, который является одноэлементным. Этот класс может либо просто предоставить постоянное хранилище, чтобы каждый контроллер представления мог создать его собственный контекст, если это необходимо. Или вы могли бы использовать класс провайдера больше как менеджер и возвращать новый (если необходимо) контекст. Разумеется, также необходимо настроить уведомление о слиянии для каждого контекста, чтобы вы могли прослушивать изменения между потоками.

С помощью этой настройки каждый контроллер представления может запросить у поставщика/менеджера контекст, и провайдер/менеджер будет обрабатывать все внутренне и возвращать контекст для контроллера вида.

Как вы думаете?

Ответ 4

IMHO Я считаю более элегантным не делать тяжелую работу Core Data в ViewController. Я предлагаю вам инкапсулировать манипуляции с данными в объект и позволить объекту ссылаться/инкапсулировать ваш стек Core Data.

Когда ваше приложение и датамодель растут, вы хотите быть очень строгим с тем, как вы передаете MOC. Это становится еще более важным, если вы используете concurrency и должны иметь дело с несколькими MOC. Кроме того, вашим ViewControllers нужен только MOC для FetchedResultsControllers. В большинстве других случаев вы отлично передаете ManagedObjects.