【问题标题】:ways to bind model classes in Objective-C在 Objective-C 中绑定模型类的方法
【发布时间】:2011-09-29 09:41:20
【问题描述】:

(如果我弄错了,我希望有人能纠正我的术语——我仍在整理术语)

我的模型中有一系列类。我需要从 url (SatDataGetter) 获取一些数据并将其与特定于位置和日期的计算 (DayCalculator) 结合起来,进行一些进一步的计算 (DataMixer),进行一些解释以使其易于用户理解 (Advisor),然后呈现结果是一个视图。

在设置依赖关系并确保例如 SatDataGetter 具有有效数据之前它被 DataMixer 调用,之前它被调用..你明白了。当然,如果位置发生变化,我需要从下往上更新整个事情。至少我必须向 ViewController 和 Advisor 发送消息以重新加载他们的数据。

我所做的研究表明 NSNotification 是一种方法,但我也可以尝试 Key-Value Observing。我在 KVO 上找到了一些旧帖子(2009 年),提出了一些可能的问题和调试困难。 http://www.mikeash.com/pyblog/key-value-observing-done-right.html

首选方法是什么?我在决定时应该考虑哪些问题—— 例如: SatDataGetter 本质上返回一个数字。 KVO 似乎是 DataMixer 跟踪该值是什么的合理方式,但我认为我不希望所有父类都对因变量执行 KVO。

什么时候选择 NSNotification 什么时候选择 KVO?

【问题讨论】:

    标签: iphone objective-c model-view-controller key-value-observing nsnotifications


    【解决方案1】:

    NSNotifications 和 Key-Value Observation 之间的区别主要是耦合之一,但也有性能影响。

    任何人都可以订阅您的 NSNotifications。他们只需要知道您通知的字符串/键。他们不需要知道任何关于你的类/对象图等的事情。所以当你想通知一个不知道你的类细节的世界时,NSNotification 是要走的路。例如,如果您要向其他开发人员出售一个框架,那么通过 NSNotification 通知可能比将框架的内部暴露到允许消费者键值对观察您的对象所必需的程度要好。

    为了 KVO 观察一个对象,您首先必须能够获得对它的引用,这对于 NSNotifications 来说并不严格(但根据我的经验,这通常是正确的。)其次,您需要足够了解实施知道要观察什么。使用 NSNotification,通知者只需要发布一个通知字符串/密钥。使用 KVO,您需要知道对象的属性名称。当然,有人可以发布静态字符串并告诉你“你可以为这些属性 KVO 我”,但这实际上变成了一个 API 合约,可能更难以维护。 (比如说,例如,你想在未来的版本中删除该属性——然后你必须安装其他东西来继续发送这些通知并在人们调用 valueForKey 时提供值:——简而言之,一旦你这样做了,你永远不能改变那个属性。)

    要记住这些不同程度的耦合的另一件事是,使用 KVO,可以期望观察者知道您的类/对象的详细信息。毕竟,他们对您的对象表现出非常特殊的兴趣;他们声称知道这意味着什么——它是如何工作的。因此,您可能希望它们对性能影响敏感。使用 NSNotifications,消费者可以观察到您的通知,几乎对您一无所知,并且可能不知道他们如何响应通知的性能影响。

    这两种方法的共同缺点是,如果没有额外的工作,它们都是同步交付的。通知对象受观察者在收到通知时(同步)选择做什么的支配。这两种机制的不同之处在于,对于 NSNotification,通知对象很容易使用 performSelector:afterDelay: 在下一次运行时“异步”发送通知(相对于产生通知的调用)循环(另见 NSNotificationQueue)。这对于 KVO 来说并不容易。在某些情况下,仅此差异可能是至关重要的。

    一般来说,我发现 NSNotification 的松散耦合适合于大粒度(即可能代表一大组更改)或相对不频繁的事件。 KVO 通知本质上是细粒度的。您正在明确观察单个对象上单个属性(每个注册)的变化。这有可能会增加注册数量和通知数量。每个观察都有性能成本。

    这两者的另一个共同缺点是可调试性。我在上面的评论中提到,KVO 可能是一个调试挑战,它也与 NSNotificationCenter 共享这个挑战。

    这两者与委托模式的主要区别在于委托关系通常是 1:1(一个委托)。(当然,您可以拥有一组委托,但这种情况非常少见,并且可能是一种反模式。 ) NSNotification 和 KVO 本质上都是 1:N(许多观察者)。

    最后,我总是喜欢说,“使用可以完成工作的最高抽象级别。”如果您只需要 1:1 的关系,请使用委托。对于 1:N 通知,如果 NSNotification 可以工作 - 换句话说,如果所需的耦合很低和/或通知是大粒度的或不频繁的,请使用 NSNotifications。如果耦合很紧密,或者需要细粒度,请使用 KVO。还要记住,您可以通过让委托发送 NSNotifiations 来将委托模式从 1:1 调整为 1:N。使用 NSNotifications 和 KVO,您无法真正控制谁观察您或您有多少观察者。

    【讨论】:

    • 哇。好的第一个问题:关于“抽象级别”和委托与 NSNotification。我想出了构建一系列委托的可能性,这些委托从计算中的较低层到链的较高层工作,每个类依次进行处理。另一种选择是注册最高级别的控制器类以从底部观察通知并让最高级别协调中间步骤。委托阶梯看起来很不稳定,但也看起来更抽象——控制器不需要知道任何关于计算链的信息。
    • 我最初并没有最清楚地了解您要做什么,所以我回答了更一般的问题。现在重新阅读它,我认为 GradyPlayer 和 Jano 的答案通常指向正确的方向。如果是直链,那么就使用委托;一个回调启动下一个操作,依此类推。如果您发现需要并行化这些步骤,那么您将需要知道它在等待什么的东西。 @Jano 的示例可能是一个很好的起点。
    【解决方案2】:

    我通常使用 KVO 来监视对象中属性的更改 - 因此,我不会覆盖对象的设置器,而是使用 KVO 在更改时触发观察方法,然后在那时调用其他方法.

    我倾向于使用 NSNotifications 来通知其他对象发生了什么事。在您的示例中,我可能有一个单例来处理位置信息,然后在 userLocation 更改时发送通知。

    当然,您也可以使用委托模式来通知另一个对象发生了变化。

    不过,这些并不是硬性规定……我不会随心所欲地改变!

    【讨论】:

    • 只是为了确保我做对了——你倾向于在类之间使用 KVO,而不是在类之间使用 KVO,而是观察类自身属性的变化(如在视图控制器的新设置中)传递到模型中),然后 KVO 触发类中的任何其他方法。我在视图控制器之间使用了委托。我试图了解选择其中一种方式(委托、KVO 或通知)的设计注意事项。
    • 是的,我就是这样使用它们的。这背后没有真正的科学,只是经验引导我朝那个方向发展。委托不仅必须由视图和控制器实现 - 模型对象也可以有一个委托,例如 MKReverseGeocoder(或者那是一个控制器?!?)
    • 在 KVO 上使用委托的一个论点是它更容易调试。你可以进入委托调用,KVO 不是那么多。
    【解决方案3】:

    您可能不需要任何这些策略,您可能可以使用某种委托层次结构。如果您正在使用核心位置或任何其他无法同步获取可靠数据的服务,那么您可以将其用作起点……如果您有 2 个此类服务(例如获取 http 数据和核心位置) ),然后您可能必须执行一些缓存和委派...即最后的最佳位置 + 当前的 http 数据被推送到视图。

    【讨论】:

    • 我已经在使用 iOS 方法,这让这变得非常方便。问题是我不只是推动视图。我想根据回报或默认值进行相当复杂的计算,无论我最终得到什么。计算也取决于一系列用户设置。我有一些结合 KVO、委托和通知的“工作”,但它有问题。
    【解决方案4】:

    NSOperations 怎么样?

    // create two ops, and set a dependency
    DownloadOp *op = [[Download alloc] initWithURL:@"http://xxx"];
    CalculationOp *op = [CalculationOp new];
    [calculationOp addDependency:downloadOp];
    // ... more steps
    
    // add to the queue
    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [queue addOperation:downloadOp];
    [queue addOperation:calculationOp];
    
    // runloop for unit testing
    while(!parserOp.isFinished) {
        [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
    }
    

    -[CalcuationOp start]里面:

        // get the result from the previous step
        id obj = [self.dependencies objectAtIndex:0];
        if ([obj isKindOfClass:[DownloadOp class]]){
            DownloadOp *op = (DownloadOp*) obj;
            self.input = op.output;
        }
    

    在 AdvisorOp 内部:

      // update the GUI from the main thread
      dispatch_queue_t mainQueue = dispatch_get_main_queue();
      dispatch_async(mainQueue,^{
        // update some UI element
     });
    

    然后在 CLLocationManager 委托上收听位置更新以开始整个过程​​。我将其与操作分离的逻辑一起使用,因此我可以单独对每个部分进行单元测试。
    好处:它是异步运行的,你可以随时取消对队列的操作。缺点:你必须学习 NSOperation。

    【讨论】:

    • 听起来不错,我一直在想我可能需要线程...但我担心的是这个缺点。 bbum 在这里的很多问题上都将 cmets 称为“线程很难”。所以问题是有多难?学习 NSOperation 是几天、几周、几个月的事情,还是你必须做数百个应用程序才能很好地了解它会出现什么问题?
    • NSOperation 是执行线程的最高抽象级别,高于 GCD。所以它应该是你线程的第一选择。这很容易,这根本不是线程编程的难点。您只需覆盖 start 和/或 main,并更改两个布尔值的值:executingfinished 以指示进度。某些 UIKit API 并非设计为在主线程之外运行,您可以修复它添加: if (![NSThread isMainThread]){ [self performSelectorOnMainThread: ...;返回 }。我会在谷歌上搜索 NSOperation 教程来感受一下。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-01-18
    • 2021-09-17
    • 1970-01-01
    • 1970-01-01
    • 2011-10-14
    • 2018-02-14
    相关资源
    最近更新 更多