【问题标题】:KVO infinite recurrence / loopKVO 无限循环/循环
【发布时间】:2013-01-09 21:09:48
【问题描述】:

我希望能够更改具有 startDate 和 endDate 的(日历)事件。还有一个持续时间。

  1. 当用户更改 endDate 时,持续时间也会更新。
  2. 当用户更改 startDate 时,endDate 会根据持续时间而更改。
  3. 当用户改变持续时间时,endDate 也会改变。

这最后一个动作将触发第一个动作,这将触发第三个动作,第三个动作将触发第一个动作,无限期(或当堆栈填满时)。

像下面这样的行,为了改变值,导致这个循环:

        [self setValue:[NSNumber numberWithLong:(interval%60)] forKeyPath:@"durationMinutes"];
        [self setValue:ed forKeyPath:@"endDate"];

简单地停止观察并在更改后重新启动是没有吸引力的,因为 GUI 中的值不会得到更新。 那么问题来了:我怎样才能安全(优雅地)更新两个相互依赖的属性之一?

【问题讨论】:

  • 我现在有一个名为 timeIntervalChanged 的​​实例变量(布尔值)。我在要更改 durationendDate 时将其设置为 TRUE,并在完成后将其设置回 FALSE。当布尔值为 TRUE 时,更改 durationendDate 的代码会立即返回。它可以防止无限递归,但感觉就像一个丑陋的解决方案。
  • 这是在自定义设置方法中吗? setStartDate: 之类的东西?
  • 没有自定义设置器; “有问题的”代码位于通过属性值更改调用的方法中。然后这些方法计算其他属性的变化。

标签: cocoa core-data key-value-observing


【解决方案1】:

您可以在必要时使用setPrimitiveValue:forKey: 绕过KVO 通知。这会设置值但不会触发任何通知。它还绕过了您可能拥有的属性的任何自定义设置器。这应该会打破通话周期。

使用此方法时,您通常希望调用“将更改”和“已更改”方法以确保维持核心数据状态。也就是说,类似于:

[self willChangeValueForKey:@"endDate"];
[self setPrimitiveValue:ed forKey:@"endDate"];
[self didChangeValueForKey:@"endDate"];

这避免了对标志的需要——你只是说,嘿,设置值,不要乱来,好吗?

【讨论】:

    【解决方案2】:

    最好的方法是编写自定义设置器,以便它们仅在值发生更改时更新它们:

    例如:

    - (void)setDurationMinutes:(NSNumber *)minutes
    {
        if (![minutes isEqual:self.durationMinutes])
        {
            _durationMinutes = minutes;
            self.endDate = [self.startDate dateByAddingTimeInterval:minutes * 60]; 
        }
    }
    
    - (void)setEndDate:(NSDate *)date
    {
        if (![_endDate isEqualToDate:date])
        {
            _endDate = date;
            self.durationMinutes = [date timeIntervalSinceDate:self.startDate] / 60;
        }
    }
    

    所以现在,当您将分钟设置为不同的值时,它会更新并设置结束日期。结束日期会有所不同,因此它将更新并设置持续时间。但是,这一次的持续时间将是相同的(因为它是之前设置的)并且它不会尝试再次设置自己或结束日期。

    它还可以让您的代码保持整洁,并清楚地知道什么时候改变了什么。

    【讨论】:

      【解决方案3】:

      将这三种方法合二为一。

      - (void)updateEndDate:(NSDate *)end
                  startDate:(NSDate *)start
                   duration:(NSNumber *)duration
      {
        ... insert your update logic here
        [self willChangeValueForKey:@"startDate"];
        [self willChangeValueForKey:@"endDate"];
        [self willChangeValueForKey:@"duration"];
        _endDate = end;
        _startDate = start;
        _duration = duration;
        [self didChangeValueForKey:@"duration"];
        [self didChangeValueForKey:@"endDate"];
        [self didChangeValueForKey:@"startDate"];
      }
      

      在你的设置器中调用该方法

      -(void)setStartDate:(NSDate *)start
      {
         [self updateEndDate:self.endDate startDate:start duration:self.duration];
      }
      

      KVO 合规性要求您指定手动发送属性通知

      + (BOOL)automaticallyNotifiesObserversForKey:(NSString *)theKey
      {
      
          BOOL automatic = NO;
          NSArray * manualKeys = @[@"endDate", @"startDate", @"duration"];
          if ([manualKeys containsObject:theKey])
          {
              automatic = NO;
          }
          else
          {
              automatic = [super automaticallyNotifiesObserversForKey:theKey];
          }
          return automatic;
      }
      

      【讨论】:

      • 我也会试试这个。我喜欢一种方法的想法:它可以存在于observeValueForKeyPath:...
      【解决方案4】:

      最后,结果证明我是在与系统作斗争。我现在动态计算持续时间值,并使用 IBActions 对更改采取行动。

      - (IBAction)changedEndDate:(id)sender
          if ( [_occurrence.endDate timeIntervalSinceDate:_occurrence.startDate] < 0 )
                  _occurrence.startDate = _occurrence.endDate;
      
          long duration = ( [_occurrence.endDate timeIntervalSinceDate:_occurrence.startDate] ) / 60;
          [self.durationHoursTextField setStringValue:[NSString stringWithFormat:@"%ld",duration/60]];
          [self.durationMinutesTextField setStringValue:[NSString stringWithFormat:@"%ld",duration%60]];
      }
      - (IBAction)changedDurationOrStartDate:(id)sender
      {
          long durH = [self.durationHoursTextField integerValue];
          long durM = [self.durationMinutesTextField integerValue];
          if ( durH < 0 )
          {
              durH = 0;
              [self.durationHoursTextField setStringValue:@"0"];
          }
          if ( durM < 0 )
          {
              durM = 0;
              [self.durationMinutesTextField setStringValue:@"0"];
          }
          long duration = durH * 3600 + durM * 60;
          _occurrence.endDate = [_occurrence.startDate dateByAddingTimeInterval:duration];
       }
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2015-06-25
        • 1970-01-01
        • 2018-10-11
        • 1970-01-01
        • 2018-10-01
        • 2012-12-24
        • 2021-02-20
        相关资源
        最近更新 更多