2016-03-24 20 views
6

Questo potrebbe essere un bug terribile in iOS 9.3 (rilascio).KVO interrotto in iOS 9.3

Quando si aggiunge un singolo osservatore a [NSUserDefaults standardUserDefaults], ho notato che il metodo di risposta -observeValueForKeyPath:ofObject:change:context: è chiamato più volte.

Nel semplice esempio seguente, ogni volta che viene premuto un UIButton, observValueForKeyPath viene attivato due volte. In esempi più complicati spara ancora più volte. È presente solo su iOS 9.3 (sia su sim che su dispositivi).

Questo può ovviamente devastare un'app. Qualcun altro vivendo lo stesso?

// 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); 
} 
+0

è NSUserDefaults valore chiave osservabile? Non vedo prove che sia. Stavi facendo qualcosa che non avevi alcun mandato da fare. Non puoi lamentarti se smette di funzionare. – matt

+0

@matt non l'ho considerato. Tuttavia, esaminandolo ho trovato quanto segue in NSUserDefaults.h: 'NSUserDefaults può essere osservato usando Key-Value Observing per qualsiasi chiave memorizzata in esso. – Matt

+0

@Matt, sto cercando nello stesso' NSUserDefaults.h'? Non riesco a trovare il commento che hai postato in questa intestazione. –

risposta

4

Sì sto vivendo questo come bene e sembra essere un bug, sotto un breve soluzione che sto utilizzando per il momento fino a quando questo è fisso. Spero possa essere d'aiuto!

Anche per chiarire, dal momento che iOS 7 KVO ha funzionato perfettamente con NSUserDefaults e sicuramente sembra essere un valore chiave osservabile come dichiarato da Matt, è scritto esplicitamente in NSUserDefaults.h nell'SDK di iOS 9.3: "È possibile osservare NSUserDefaults usando chiave-valore Osservando per qualsiasi chiave memorizzata in essa ".

#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

Ottima risposta. Per me va bene! Nota come @ "MyKey" contiene una citazione strana dopo @ che Xcode si lamenta di – Omar

2

Quando si aggiunge un singolo osservatore a [NSUserDefaults standardUserDefaults] ho notato che il metodo rispondere -observeValueForKeyPath:ofObject:change:context: viene chiamato più volte

Questo è un problema noto ed è reported (di Apple) come stabilito in iOS 11 e macOS 10.13.

+0

hi, dove è doc? – frank

+0

@frank https://developer.apple.com/library/content/releasenotes/Foundation/RN-Foundation/ index.html "Questo dovrebbe anche correggere le notifiche duplicate di alcuni cambiamenti consegnati agli osservatori" - esattamente il comportamento lamentato dall'OP – matt

+0

Grazie per la risposta – frank

0

Aggiunta di questa risposta per MacOS (10.13) che ha sicuramente il bug che ottiene più notifiche per KVO di NSUserDefault Keys e che risolve anche le deprecazioni. È meglio usare un calcolo per i secondi nani trascorsi che lo ottiene per la macchina su cui si sta eseguendo. Farlo in questo modo:

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

Questo si basa sul Listing 2 del presente di Apple Doc: https://developer.apple.com/library/content/qa/qa1398/_index.html