NSUserDefaultsがobserveValueForKeyPathで2回呼ばれてる問題
iOS9.3がリリースされ、アプリのテストをした際に発見したバグ?について投稿しておこうと思います。
KVOでNSUserDefaultsのキー値の変化を監視するシステムを作ったのですが、NSUserDefaultが2回、observeValueForKeyPathに呼び出されているようです。
試しにNSUserDefaultsではなく、NSMutableDictionaryで値の監視を行ったところ2回呼び出されず、1回だけログに出力がされました。
なお、この現象はこちらで確認した限りではiOS9.3でのみ起こっており、9.2以下では発生しませんでした。
NSUserDefaultsのコードと出力結果
#import "ViewController.h" @interface ViewController (){ } @end @implementation ViewController - (void)dealloc{ [super dealloc]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; [ud removeObserver:self forKeyPath:TESTIOS93_FIRST]; [ud removeObserver:self forKeyPath:TESTIOS93_SECOND]; } - (void)viewDidLoad { [super viewDidLoad]; NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; dict = [NSMutableDictionary dictionaryWithDictionary:@{TESTIOS93_FIRST:@"This is key1.", TESTIOS93_SECOND:@"This is key2."}]; [ud addObserver:self forKeyPath:TESTIOS93_FIRST options:0 context:nil]; [ud addObserver:self forKeyPath:TESTIOS93_SECOND options:0 context:nil]; [self changeKeyValue]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; NSLog(@"-------------------------"); NSLog(@"call observeValueForKeyPath"); if([keyPath isEqualToString:TESTIOS93_FIRST] == YES){ NSLog(@"TESTIOS93_FIRST CALLING = %@",[ud objectForKey:TESTIOS93_FIRST]); }else if([keyPath isEqualToString:TESTIOS93_SECOND] == YES){ NSLog(@"TESTIOS93_SECOND CALLING = %@",[ud objectForKey:TESTIOS93_SECOND]); } } - (void)changeKeyValue{ NSUserDefaults *ud = [NSUserDefaults standardUserDefaults]; [ud setValue:@"change key1 KVO Test" forKey:TESTIOS93_FIRST]; [ud setValue:@"change key2 KVO Test" forKey:TESTIOS93_SECOND]; [ud synchronize]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
出力結果
2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] ------------------------- 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] call observeValueForKeyPath 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] TESTIOS93_FIRST CALLING = change key1 KVO Test 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] ------------------------- 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] call observeValueForKeyPath 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] TESTIOS93_FIRST CALLING = change key1 KVO Test 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] ------------------------- 2016-03-25 12:07:15.442 iOS9.3_KVOTest[1711:469489] call observeValueForKeyPath 2016-03-25 12:07:15.443 iOS9.3_KVOTest[1711:469489] TESTIOS93_SECOND CALLING = change key2 KVO Test 2016-03-25 12:07:15.443 iOS9.3_KVOTest[1711:469489] ------------------------- 2016-03-25 12:07:15.443 iOS9.3_KVOTest[1711:469489] call observeValueForKeyPath 2016-03-25 12:07:15.443 iOS9.3_KVOTest[1711:469489] TESTIOS93_SECOND CALLING = change key2 KVO Test
******************************************************************************************
NSMutableDictionaryのコードと出力結果
#import "ViewController.h" @interface ViewController (){ NSMutableDictionary *dict; } @end @implementation ViewController - (void)dealloc{ [super dealloc]; [dict removeObserver:self forKeyPath:TESTIOS93_FIRST]; [dict removeObserver:self forKeyPath:TESTIOS93_SECOND]; } - (void)viewDidLoad { [super viewDidLoad]; dict = [NSMutableDictionary dictionaryWithDictionary:@{TESTIOS93_FIRST:@"This is key1.", TESTIOS93_SECOND:@"This is key2."}]; [dict addObserver:self forKeyPath:TESTIOS93_FIRST options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; [dict addObserver:self forKeyPath:TESTIOS93_SECOND options:NSKeyValueObservingOptionNew|NSKeyValueObservingOptionOld context:nil]; [self changeKeyValue]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { NSLog(@"-------------------------"); NSLog(@"call observeValueForKeyPath"); if([keyPath isEqualToString:TESTIOS93_FIRST] == YES){ NSLog(@"TESTIOS93_FIRST CALLING = %@",[dict objectForKey:TESTIOS93_FIRST]); }else if([keyPath isEqualToString:TESTIOS93_SECOND] == YES){ NSLog(@"TESTIOS93_SECOND CALLING = %@",[dict objectForKey:TESTIOS93_SECOND]); } } - (void)changeKeyValue{ [dict setObject:@"change key1 KVO Test" forKey:TESTIOS93_FIRST]; [dict setObject:@"change key2 KVO Test" forKey:TESTIOS93_SECOND]; } - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; // Dispose of any resources that can be recreated. } @end
出力結果
2016-03-25 12:01:26.001 iOS9.3_KVOTest[1676:466919] ------------------------- 2016-03-25 12:01:26.002 iOS9.3_KVOTest[1676:466919] call observeValueForKeyPath 2016-03-25 12:01:26.002 iOS9.3_KVOTest[1676:466919] TESTIOS93_FIRST CALLING = change key1 KVO Test 2016-03-25 12:01:26.002 iOS9.3_KVOTest[1676:466919] ------------------------- 2016-03-25 12:01:26.002 iOS9.3_KVOTest[1676:466919] call observeValueForKeyPath 2016-03-25 12:01:26.002 iOS9.3_KVOTest[1676:466919] TESTIOS93_SECOND CALLING = change key2 KVO Test
ちらほらと報告が上がってるみたいですが、このようなことは初めて経験したので正直焦りました...