Передача ManagedObjectContext для просмотра контроллеров с использованием раскадровки с корневым UITabBarController

Используя раскадровки, у вас нет простого доступа к первому контроллеру представления в appDelegate (хотя, как только вы сделаете prepareForSegue, вы сможете легко передать ManagedObjectContext вниз по стеку навигации.

Я решил предоставить каждому контроллеру представления (или суперклассу каждого контроллера представления), требующему доступа к Core Data, член moc:

@synthesize moc = _moc;
@property (nonatomic) __weak NSManagedObjectContext *moc;

Меня это беспокоит, потому что это не кажется очень элегантным способом - слишком много кода. Но прямое назначение требует указания абсолютных индексов в массивах viewControllers и изменения appDelegate каждый раз, когда изменяется требование для ManagedObjectContexts.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    UITabBarController *tabBarController = (UITabBarController *)self.window.rootViewController;

    // rootView gets a tab bar controller
    for(UINavigationController *navController in tabBarController.viewControllers) {

        for(UIViewController *viewController in navController.viewControllers) {

            if([viewController respondsToSelector:@selector(setMoc:)]) {
                [viewController performSelector:@selector(setMoc:) withObject:self.managedObjectContext];
                NSLog(@"Passed moc to %@", [viewController description]); 
            }
        }
    }

    return YES;
}

Каковы подводные камни этого подхода и есть ли лучший способ? Лучше попробовать и быть более общим:

- (void)assignManagedObjectContextIfResponds:(UIViewController *)viewController {

    if([viewController respondsToSelector:@selector(setMoc:)]) {
        [viewController performSelector:@selector(setMoc:) withObject:self.managedObjectContext];
        NSLog(@"Passed moc to %@", [viewController description]); 
    }

}
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSMutableArray *viewControllers = [NSMutableArray array];

    UIViewController *firstLevelViewController = self.window.rootViewController;

    if([firstLevelViewController respondsToSelector:@selector(viewControllers)]) {

        NSArray *firstLevelViewControllers = [firstLevelViewController performSelector:@selector(viewControllers)];

        for(UIViewController *secondLevelViewController in firstLevelViewControllers) {

            if([secondLevelViewController respondsToSelector:@selector(viewControllers)]) {

                NSArray *secondLevelViewControllers = [secondLevelViewController performSelector:@selector(viewControllers)];

                for(UIViewController *thirdLevelViewController in secondLevelViewControllers) {

                    [viewControllers addObject:thirdLevelViewController];
                }

            } else {
                [viewControllers addObject:secondLevelViewController];
            }
        }
    } else {
        // this is the simple case, just one view controller as root
        [viewControllers addObject:firstLevelViewController];
    }

    // iterate over all the collected top-level view controllers and assign moc to them if they respond
    for(UIViewController *viewController in viewControllers) {
        [self assignManagedObjectContextIfResponds:viewController];
    }

    return YES;
}

person Adam Eberbach    schedule 28.02.2012    source источник


Ответы (2)


Адам,

Пока я изучал раскадровки, я делал это почти так же, как и вы, за исключением того, что я сделал каждый из своих контроллеров представления, у которых было свойство MOC, соответствующим протоколу.

Там нет ничего существенного, поэтому я пойду дальше.

Я думаю, дело в том, что раскадровки, IMO, полусырые. Исходя из фона .Net, чего явно не хватает, так это структуры построителя объектов в сочетании с контейнером IoC. Когда Apple добавит эти раскадровки, это будет круто. Когда фреймворк раскадровки может смотреть на targetViewController, определять его зависимости и разрешать их из жизни контейнера, это будет здорово. На данный момент все, что он действительно может сделать, это посмотреть на targetViewController и инициализировать вам общий, который имеет ограниченное применение.

К сожалению, поскольку это полусырое решение, я пока придерживаюсь традиционного подхода, поэтому все мои контроллеры представления выделяются и инициализируются вручную, и, что более важно, я добавил метод для каждого контроллера представления в initWithMOC :( МОС *)мок;

Архитектор во мне говорит мне, что этот код более надежен, я думаю, это вопрос мнения о том, стоит ли идти на компромисс.

Кто-нибудь еще придумал лучший способ?

CA.

person Chris A    schedule 05.03.2012

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

@property (readonly, strong, nonatomic) NSManagedObjectContext *managedObjectContext;

то вы можете вспомнить его в любое время из любого места.

NSManagedObjectContext *moc = [(YourApplicationDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext];

Для удобства я объявил для него определение:

#define MOC [(YourApplicationDelegate*)[[UIApplication sharedApplication] delegate] managedObjectContext]

Поэтому это становится:

[MOC save:&error];

Вы можете взять это куда угодно. Просто попробуйте взглянуть на автоматически сгенерированный код для приложения CoreData в Xcode, вы увидите, что там много методов доступа с CoreData, а сам CoreData лениво инициализируется при первом запросе.

person Leonardo    schedule 29.02.2012
comment
Я знаю, что ты можешь это сделать, но я стараюсь этого избегать. Не могу четко сформулировать, почему это неправильно, но так кажется, #importing AppDelegate везде. - person Adam Eberbach; 01.03.2012
comment
Это не неправильно, это способ сделать, нет смысла передавать контекст, когда вы можете легко получить к нему доступ. Импорт необходим только для того, чтобы избежать предупреждения компилятора (а не ошибки), потому что в любое время вы можете вызвать [UIApplication sharedApplication] из любого места вашего кода, и он будет работать в отношении импорта, это также то, что генерирует Xcode для приложения CoreData, это поражает мне почему вы утверждаете, что это неправильно, и предпочитаете передавать контекст таким образом. Централизуя средства доступа CoreData, вы также получаете больше контроля над ошибками сохранения, отложенной загрузкой и всем, что вам нравится. - person Leonardo; 01.03.2012
comment
cocoawithlove.com/2008/11/ неправильно было возможно не то слово. ищу оптимальное. - person Adam Eberbach; 02.03.2012
comment
Вот ответ: Nested contexts make it more important than ever that you adopt the “pass the baton” approach of accessing a context (by passing a context from one view controller to the next) rather than retrieving it directly from the application delegate. Отсюда Контексты вложенных управляемых объектов - person DanSkeel; 28.08.2012
comment
Хорошо, это связано конкретно с вложенным контекстом, только с одним контекстом, достаточно безопасно получить его из AppDelegate. - person Leonardo; 28.08.2012
comment
Леонардо: извините, но доступ к вашему MOC везде через синглтон делегата приложения неправильно. Все, что он делает, — это создает тесно связанный код, который сложнее тестировать, и превращает ваш делегат приложения в локатор сервисов для бедных. Apple прямо говорит, что вы должны передавать свой контекст объектам, которые в нем нуждаются. Это просто хороший объектно-ориентированный дизайн. - person Luke Redpath; 15.12.2012
comment
Не нужно извиняться, это просто техническая дискуссия, и приятно делиться мнениями. Для меня тесная связь возникает только тогда, когда контроллер представления полагается на своего предшественника для выполнения своей работы, то есть передачи контекста. Кроме того, переданный контекст совпадает с контекстом, полученным делегатом приложения или служебным классом. Так какой смысл передавать объект? Кстати, я думаю, это зависит от сложности приложения. Для большинства из них достаточно иметь ctx только в одном месте. - person Leonardo; 15.12.2012
comment
Лео (и все, кто последует за ним) причина того, что поиск, а не передача, является плохим шаблоном проектирования, связана с проблемами, которые он создает с инкапсуляцией. С вашим шаблоном каждый viewController должен знать что-то о контексте (например, есть ли более одного. Каково имя переменной и т. Д.). Если что-то из этого изменится, 1) все контроллеры необходимо будет обновить. 2) решение о том, какой ctx вам нужно будет получить, может быть сложным решением, основанным на состоянии, которое имеет больше общего с приложением в целом, нарушая инкапсуляцию. Решение: в корневых контроллерах обратитесь, во всех остальных передайте вниз. - person Nathan; 15.06.2015
comment
Натан, мне нравится это резюме. В корневых контроллерах протянуть руку, во всех остальных передать вниз, кажется ясным. Можно ли сделать еще один шаг и сказать «Передавать везде», даже на корневой контроллер? Я видел примеры, когда moc загружается в корневой контроллер с помощью AppDelegate в didFinishLaunchingWithOptions. На корневой контроллер ссылается mainStoryboard.instantiateViewControllerWithIdentifier, для чего требуется идентификатор раскадровки, назначенный корневому контроллеру. - person protasm; 03.01.2016