【问题标题】:Best practices for passing NSManagedObjectContext around to UITabBarController child view controllers?将 NSManagedObjectContext 传递给 UITabBarController 子视图控制器的最佳实践?
【发布时间】:2012-01-27 12:05:07
【问题描述】:

所以在我看来,这里有一个 Catch-22 的情况。请注意以下关于应用程序架构的广泛(且巧妙)的立场:

  1. Apple(和大多数 alpha 开发人员)建议不要使用单例或访问应用程序委托单例来检索 NSManagedObjectContext。僵硬、糟糕的设计等等。好的——我同意!
  2. 遍历 UITabbarController 的子视图控制器并为每个子 VC 提供对上下文的引用(依赖注入)也是愚蠢的,因为您在应用程序启动时加载应用程序中的每个选项卡,只是为了传递一个引用。这也违背了 Apple 推荐的应用程序架构。

你如何解决这个问题?当视图控制器从笔尖唤醒时,您是否使用 NotificationCenter 发布通知,然后让应用程序委托传递上下文引用?这大概是我能想到的唯一一种方式,它同时适用于 #1 和 #2,但我也觉得这有点扭曲。

有没有更优雅的方式?

编辑:在初始化视图控制器时发出通知可能是一种竞争条件,因为如果您使用 Storyboard 进行操作,您的标签栏的子视图控制器往往会被初始化(尽管 sans-view加载)在发射。所以你必须在 viewDidLoad 中做这样的通知,这对于 MVC 约定来说是个坏主意。在用户执行与视图相关的任何操作之前,它还会束缚您对数据模型的任何操作(例如为性能而预先缓存)。

【问题讨论】:

  • 遍历 UITabBarController 的视图控制器似乎还不错,因为 iOS 5 运行时似乎无论如何都会唤醒每个组件视图控制器。它只是不加载视图。
  • 这已经有一段时间没有收到活动了。 @EricGoldburg 您在找到最佳实践方面取得了成功吗?
  • @MrJD:事实证明全世界都喜欢单例,即使很多开发者不喜欢。我最终在这个项目中使用了 RestKit,它抽象了传递上下文的需要,但它还在内部使用单例作为其对象加载器和托管对象处理类的默认情况。 C'est la vie。
  • 我仍然不相信。在视图控制器之间传递并不难。在没有子类化的情况下通过选项卡控制器和导航控制器变得很困难,但我相信通过委托可以减少由于单例使用而导致的耦合。我想知道我是否会屈服。
  • 我更加确信。我很感兴趣,因为我对从委托到选项卡到导航到视图控制器的传递时间感到恼火。您会强烈推荐下面的子类单例方法作为有效的替代方法吗?我注意到苹果强烈建议不要继承 NSManagedObjectContext

标签: objective-c cocoa-touch core-data


【解决方案1】:

我发现您发布问题已经有一段时间了,但我有一个不同的方法想与您和其他人分享。

我假设您希望将托管对象上下文注入所有/部分视图控制器中,这些视图控制器显示为 UITabViewController 的选项卡,并且您正在使用带有 UITabBarController 作为 rootViewController 的 Storyboard。

  1. 在您的 AppDelegate 标头中让您的 AppDelegate 实现 UITabBarControllerDelegate 协议。

    @interface AppDelegate : UIResponder <UIApplicationDelegate, UITabBarControllerDelegate>
    ...
  2. 在您的 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;
    }
  3. 在您的应用程序中:didFinishLaunchingWithOptions:将 self 设置为 UITabBarController 委托。

    UITabBarController *tabBarController = (UITabBarController *) self.window.rootViewController;
    tabBarController.delegate = self;
  4. 遗憾的是,要加载的第一个视图控制器当时还没有准备好(tabBarController.selectedViewController),也没有调用委托。所以设置第一个最简单的方法就是观察TabBarController的selectedViewController属性

    [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];
        }
    }
    }

【讨论】:

    【解决方案2】:

    当您将 NSManagedObject 实例传递给视图控制器时,该视图控制器可以保留这些对象。然后它可以通过调用这些托管对象到达 NSManagedObjectContext

    -[NSManagedObject managedObjectContext]
    

    我不确定这是否适用于您的特定情况,但通常会。应用委托或根视图控制器创建一个上下文,然后传递托管对象。

    更新

    如果您需要在多个地方使用上下文,我发现另一种模式很有用:

    子类NSManagedObjectContext:

    @interface MyManagedObjectContext : NSManagedObjectContext
    
    + (MyManagedObjectContext *)mainThreadContext;
    
    @end
    

    即UI / 主线程的 singleton 上下文。这比使用 App 委托更干净,因为其他类不必访问 App 委托,但可以直接使用此类。也可以将store和model的初始化封装到这个类中:

    @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
    

    【讨论】:

    • 确实如此。但是,使用 Apple 在 Application Delegate 中构建 Core Data 堆栈的方式,如果您的窗口的根视图控制器是选项卡栏控制器,您仍然在进行半奇怪(恕我直言)扭曲以将上下文(或托管对象)传递给标签栏拥有的视图控制器。它有效,但感觉就像是撒下一个广泛的、过于笼统的网络来解决问题。特别是如果 UITabBarController 有 UINavigationControllers 作为孩子。不过,感谢您的回答,这将增加 SO 用户在查看此问题时的知识。 :)
    【解决方案3】:

    恕我直言,我发现不在 ViewController 中执行 Core Data 繁重的工作更优雅。 我建议您将数据操作封装到一个对象中,并让该对象引用/封装您的核心数据堆栈。

    当您的应用程序和数据模型增长时,您希望对通过 MOC 的方式非常严格。如果您使用并发并且必须处理多个 MOC,这将变得更加重要。此外,您的 ViewControllers 只需要一个用于 FetchedResultsControllers 的 MOC。在大多数其他情况下,您可以通过 ManagedObjects。

    【讨论】:

    • 另一个好点。不过,仍然必须小心不要引入不必要的抽象。不过,我同意你的看法。我的非核心数据项目总是在自己封装的地方完成持久对象处理。我认为从 Apple 的入门文档开始会引导人们走上基于视图控制器的上下文访问路径。可能 Apple 这样做是为了简单。
    • 我有几个应用程序已经发展了两年。我从 Apple 开始使用 Core Data,但很快发现它的扩展性/老化性不是很好。
    【解决方案4】:

    我会创建一个核心数据提供者类,它是一个单例。此类可以仅提供持久存储,以便每个视图控制器都可以在需要时创建自己的上下文。或者,您可以更像管理器一样使用提供程序类并返回新的(如果需要)上下文。当然,它还应该为每个上下文设置一个合并通知,以便您可以监听线程之间的变化。

    通过这种设置,每个视图控制器都可以向提供者/管理器询问上下文,提供者/管理器将在内部处理所有事情并为视图控制器返回一个上下文。

    你怎么看?

    【讨论】:

    • 我不喜欢的是每个视图控制器都有自己的托管对象上下文的数据透视图。即使您不在每个 VC 中使用新的上下文,您仍然必须使用单例来共享上下文。这基本上意味着您正在硬编码 VC 和单例之间更紧密的耦合。这是依赖注入更好的原因之一:它更灵活,每个 VC 都可以天真地了解它是如何获取上下文的;它只是使用给定的上下文。
    • 这不是依赖注入吗?
    • 不,Larcus94,那是依赖非注入。 :) 如果一个实例寻找某个“已知”位置的东西,它只是通过与“已知”位置的紧密耦合来解决依赖关系。如果换个地方呢?或者想为一个视图控制器使用不同的地方?或者针对新的数据存储运行单元测试?或者测试一个新的持久存储协调器?我认为视图控制器最好天真地使用给定的任何上下文,以实现灵活性和可维护性。
    猜你喜欢
    • 2012-07-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多