KVO在iOS 9.3中被破坏了

时间:2016-03-24 04:54:02

标签: ios objective-c key-value-observing ios9.3

这可能是iOS 9.3(发布)中的一个糟糕的错误。

[NSUserDefaults standardUserDefaults]添加单个观察者时,我注意到多次调用响应方法-observeValueForKeyPath:ofObject:change:context:

在下面的简单示例中,每次按下一次UIButton时,observeValueForKeyPath会触发两次。在更复杂的例子中,它会发射更多次。它仅出现在iOS 9.3上(包括sim和设备)。

这显然会对应用程序造成严重破坏。其他人经历过同样的事情吗?

// ViewController.m (barebones, single view app)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"viewDidLoad");
    [[NSUserDefaults standardUserDefaults] addObserver:self forKeyPath:@"SomeKey" options:NSKeyValueObservingOptionNew context:NULL];
}

- (IBAction)buttonPressed:(id)sender {
    NSLog(@"buttonPressed");
    [[NSUserDefaults standardUserDefaults] setInteger:1 forKey:@"SomeKey"];
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSString *,id> *)change context:(void *)context {
    NSLog(@"observeValueForKeyPath: %@", keyPath);
} 

3 个答案:

答案 0 :(得分:5)

是的我也遇到了这个问题而且似乎是一个错误,下面是我正在使用的快速解决方法,直到修复完毕。我希望它有所帮助!

另外要澄清的是,由于iOS 7 KVO在NSUserDefaults上运行良好,并且它看起来像Matt所说的那样具有关键的值可观察性,因此它在iOS 9.3 SDK中的NSUserDefaults.h中明确写出:“可以使用NSUserDefaults进行观察键值观察存储在其中的任何键。“

#include <mach/mach.h>
#include <mach/mach_time.h>

@property uint64_t newTime;
@property uint64_t previousTime;
@property NSString *previousKeyPath;

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
    //Workaround for possible bug in iOS 9.3 SDK that is causing observeValueForKeyPath to be called multiple times.
    newTime = mach_absolute_time();
    NSLog(@"newTime:%llu", newTime);
    NSLog(@"previousTime:%llu", previousTime);

    //Try to avoid duplicate calls
    if (newTime > (previousTime + 5000000.0) || ![keyPath isEqualToString:previousKeyPath]) {
        if (newTime > (previousTime + 5000000.0)) {
            NSLog(@"newTime > previousTime");
            previousTime = newTime;
            NSLog(@"newTime:%llu", newTime);
            NSLog(@"previousTime:%llu", previousTime);
        }
        if (![keyPath isEqualToString:previousKeyPath]) {
            NSLog(@"new keyPath:%@", keyPath);
            previousKeyPath = keyPath;
            NSLog(@"previousKeyPath is now:%@", previousKeyPath);
        }
        //Proceed with handling changes
        if ([keyPath isEqualToString:@“MyKey"]) {
            //Do something
        }
    }
}

答案 1 :(得分:2)

  

[NSUserDefaults standardUserDefaults]添加单个观察者时,我注意到多次调用了响应方法-observeValueForKeyPath:ofObject:change:context:

这是一个已知问题,在iOS 11和macOS 10.13中已修复reported(Apple)。

答案 2 :(得分:0)

为MacOS(10.13)添加此答案,肯定有错误获取NSUserDefault Keys的KVO的多个通知,并且还解决了弃用问题。最好使用经过纳秒的计算,为您运行的机器获取它。这样做:

#include <mach/mach.h>
#include <mach/mach_time.h>
static mach_timebase_info_data_t _sTimebaseInfo;

uint64_t  _newTime, _previousTime, _elapsed, _elapsedNano, _threshold;
NSString  *_previousKeyPath;

-(BOOL)timeThresholdForKeyPathExceeded:(NSString *)key thresholdValue:(uint64_t)threshold
{
   _previousTime = _newTime;
   _newTime = mach_absolute_time();

    if(_previousTime > 0) {
        _elapsed = _newTime - _previousTime;
        _elapsedNano = _elapsed * _sTimebaseInfo.numer / _sTimebaseInfo.denom;
    }

    if(_elapsedNano > threshold || ![key isEqualToString:_previousKeyPath]) {
        if(![key isEqualToString:_previousKeyPath]) _previousKeyPath = key;
            return YES;
        }
        return NO;
    }
}

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    if(![self timeThresholdForKeyPathExceeded:keyPath thresholdValue:5000000]) return;  // Delete this line of MacOS bug ever fixed
    }
    // Else this is the KeyPath you are looking for Obi Wan, process it.
}

这是基于此 Apple Doc 的清单2: https://developer.apple.com/library/content/qa/qa1398/_index.html