Еще, многие знают, что при помощи рантайма, можно получить список переменных/методов/свойств/протоколов/… класса. И обычно для этих целей пишут вспомогательный код. Но оказывается, все уже давно написано сотрудниками Apple.
В UIKit.framework реализована категория с именем IvarDescription.
Для реализации KVO, объект должен реализовать неформальный протокол NSKeyValueObserving. NSObject уже предоставляет реализацию этого протокола, поэтому разработчику нужно лишь грамотно использовать готовый механизм.
Большинство разработчиков умеют правильно подписываться/отписываться, но с observeValueForKeyPath еще возникают проблемы. Вот примеры неправильной реалзиции этого метода из реальных проектов:
1234567891011121314151617181920212223242526272829
-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary*)changecontext:(void*)context{if(context!=(__bridgevoid*)kActionLoadingStateObservationContext)return;NSUIntegerindex=[self.actionsindexOfObject:object];NSAssert(index!=NSNotFound,@"Can not found action %@ in the current actions",object);// update loading state YMTAction*action=self.actions[index];YMTActionView*actionView=self.subviews[index];actionView.loading=action.loading;}-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary*)changecontext:(void*)context{if(object==self.webView.scrollView&&[keyPathisEqualToString:@"contentSize"]){self.webViewHeightConstraint.constant=[change[NSKeyValueChangeNewKey]CGSizeValue].height;}}-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary*)changecontext:(void*)context{if([keyPathisEqual:@keypath(YRCachedSettings.new,selectedSuburbanZone)]){[selfzoneDidChange];}}-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(NSDictionary*)changecontext:(void*)context{[selfreloadData];}
Главная проблема у всех показанных реализаций, это отсутствие вызова [super observeValueForKeyPath:keyPath:change:context:]. Многие забывают о том, что базовый класс тоже может использовать KVO. Порой даже не забывают, а сознательно опускают вызов метода, так как, когда наткнулись на то, что базовы класс кинул NSInternalInconsistencyException.
Собсвтенно, чтобы не было проблем с базовым классом, [super observeValueForKeyPath:keyPath:change:context:] надо вызвать не при каждом уведомление, а только при тех, на которые вы не подписывались. Чтобы отличить свои уведомления от чужих (принадлежащих базовому классу) надо использовать контекст.
Вот пример правильного observeValueForKeyPath:
1234567891011121314
-(void)observeValueForKeyPath:(NSString*)keyPathofObject:(id)objectchange:(__unusedNSDictionary*)changecontext:(void*)context{if(context==&ja_kvoContext){if([keyPathisEqualToString:@"view"]){if(self.centerPanel.isViewLoaded&&self.recognizesPanGesture){[self_addPanGestureToView:self.centerPanel.view];}}elseif([keyPathisEqualToString:@"viewControllers"]&&object==self.centerPanel){// view controllers have changed, need to replace the button [self_placeButtonForLeftPanel];}}else{[superobserveValueForKeyPath:keyPathofObject:objectchange:changecontext:context];}}