iOS 9.3中KVO出现问题

7
这可能是iOS 9.3版本中一个严重的bug。
当向[NSUserDefaults standardUserDefaults]添加单个观察者时,我注意到响应方法-observeValueForKeyPath:ofObject:change:context:会被多次调用。在下面的简单示例中,每次按下UIButton一次,observeValueForKeyPath就会触发两次。在更复杂的示例中,它甚至会触发更多次。这仅出现在iOS 9.3上(模拟器和设备都是如此)。这显然会对应用程序造成严重影响。还有其他人遇到了同样的问题吗?
// 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);
} 

NSUserDefaults的键值可观察吗?我没有发现它是可观察的证据。你在做一些你没有授权做的事情。如果它停止工作,你不能抱怨。 - matt
@matt 我没有考虑到这一点。然而,我查阅了NSUserDefaults.h文件,发现可以使用键值观察(Key-Value Observing)来观察存储在其中的任何键(key)。 - Matt
@Matt,我看的是同一个NSUserDefaults.h吗?我在这个头文件中找不到你发布的评论。 - Borys Verebskyi
@BorisVerebsky 你在检查哪个版本的Xcode?也许这是Xcode 7.3/iOS 9.3中的新功能。 - Matt
我一直遇到同样的问题,即使是在Xcode 7.3.1 beta也是如此:( - Omar
显示剩余4条评论
3个回答

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
很棒的答案。对我有用!请注意,你的@“MyKey”在@后面包含了一个奇怪的引号,这是Xcode抱怨的。 - Omar
在Swift 4和iOS 11.4中,这个功能无法正常工作。多个更改仍会被触发。 - Don Miguel

2
当向[NSUserDefaults standardUserDefaults]添加单个观察者时,我注意到响应方法-observeValueForKeyPath:ofObject:change:context:会被多次调用。
这是一个已知的问题,并且在iOS 11和macOS 10.13中已被苹果报告为已修复。

嗨,文档在哪里? - frank
@frank https://developer.apple.com/library/content/releasenotes/Foundation/RN-Foundation/index.html "这也应该纠正一些改变的重复通知被传递给观察者" - 这正是OP所抱怨的行为。 - matt
谢谢您的回复。 - frank
1
我认为这个问题在MacOS 10.13中没有被解决。我的观察者在一个按钮点击时被调用了3次,该按钮设置了NSUserDefaults中的一个值。我已经验证了按钮只被触发了一次,该值也只被设置了一次,并且我只注册了一次。我已经尝试了使用部署目标10.13构建来检查,但没有任何区别。 - Cliff Ribaudo
1
@CliffRibaudo 感谢您的反馈。请向苹果公司提交错误报告!我不能确定发布说明是否有意解决此问题,但看起来是这样的。如果他们没有修复这个问题,无论如何他们都需要知道。 - matt
显示剩余3条评论

0

针对MacOS(10.13)的此答案,它确实存在KVO NSUserDefault Keys的多个通知错误,并解决了过时问题。最好使用计算来获取您正在运行的机器的经过的纳秒数。请按以下方式执行:

#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.
}

这是基于此苹果文档的第2个清单: https://developer.apple.com/library/content/qa/qa1398/_index.html

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接