2011-07-04 27 views
14

在我的UIScrollView子類中,我觀察幀之間的變化:這個observeValueForKeyPath有什麼問題:ofObject:change:context:implementation?

[self addObserver:self forKeyPath:@"frame" options:0 context:NULL]; 

observeValueForKeyPath:ofObject:change:context:實現如下:

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 
    if (object == self && [keyPath isEqualToString:@"frame"]) { 
     [self adjustSizeAndScale]; 
    } 
    if ([UIScrollView instancesRespondToSelector:@selector(observeValueForKeyPath:ofObject:change:context:)]) { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; // Exception 
    } 
} 

,但我得到的異常與此代碼:

*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '<WLImageScrollView: 0x733a440; baseClass = UIScrollView; frame = (0 0; 320 416); clipsToBounds = YES; layer = <CALayer: 0x7346500>; contentOffset: {0, 0}>: An -observeValueForKeyPath:ofObject:change:context: message was received but not handled. 
Key path: frame 
Observed object: <WLImageScrollView: 0x733a440; baseClass = UIScrollView; frame = (0 0; 320 416); clipsToBounds = YES; layer = <CALayer: 0x7346500>; contentOffset: {0, 0}> 
Change: { 
    kind = 1; 
} 
Context: 0x0' 

這是否意味着UIScrollView執行observeValueForKeyPath:ofObject:change:context:但引發上述異常?

如果是這樣,我該如何正確實施observeValueForKeyPath:ofObject:change:context:,這樣我才能處理我感興趣的變化,並給超類有機會處理它感興趣的變化?

回答

12

編輯:BJ荷馬的答案可能是一個更好的方法來採取;我忘了所有關於上下文參數!

即使調用超級實現由這本書,它seems like呼籲observeValueForKeyPath:ofObject:change:context:UIKit類,實際上不遵守有關的領域拋出一個異常NSInternalInconsistency(不是NSInvalidArgumentException你會得到具有無法識別的選擇)。在我看來這個例外中的關鍵字字符串是「已收到但未處理」。

據我所知,沒有充分記錄的方式來查明對象是否觀察到給定關鍵路徑上的另一個對象。可能會有部分記錄的方式,例如-observationInfo屬性,據說這些屬性可以將信息傳遞給對象的觀察者,但您自己在那裏 - 這是一個void *

所以,在我看來,你有兩個選擇:要麼不叫super實施或使用@try/@catch/@finally塊忽略特定類型的NSInternalInconsistencyException。第二種選擇可能更具前瞻性,但我有一種預感,即某些偵探工作可以通過第一種選擇讓您獲得更滿意的結果。

+1

你掛在談論另一件事的文章:超沒有實現observeValueForKeyPath:ofObject變化:背景:。但是,從代碼中可以看出,UIScrollView的確實現了它,否則超級方法將不會被調用。 – an0

+0

即使它確實實現了該方法,它可能會有代碼檢測未被發現的鍵併爲它們拋出異常,從而導致「NSInternalInconsistency」異常「收到但未處理」,這與發送給實例的「NSInvalidArgumentException」不可識別的選擇器不同」。 –

+1

這就是我要問的。因此,在NSObject中的observableValueForKeyPath:ofObject:change:context:的默認實現會拋出異常?如果是這樣,我們無法檢測超類是否要通知關鍵的價值變化,不是嗎? – an0

16

添加觀察者時,應該添加一個context值。在您的-observeValueForKeyPath方法中,檢查上下文參數。如果它不是你在添加觀察者時傳遞的上下文,那麼你知道這個消息不適合你的子類,並且你可以安全地將它傳遞給超類。如果它相同的值,那麼你知道它是爲你準備的,你不應該把它傳遞給超級。

像這樣:

static void *myContextPointer; 

- (void)addSomeObserver { 
    [self addObserver:self forKeyPath:@"frame" options:0 context:&myContextPointer]; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { 
    if (context != &myContextPointer) { 
     [super observeValueForKeyPath:keyPath ofObject:object change:change context:context]; 
    } 
    else { 
     // This message is for me, do whatever I want with it. 
    } 
}         
+0

最好是像'static int myContext;'然後''self addObserver:self forKeyPath:@「frame」options:0 context:&myContext];''做一些事情。這樣,指向的地址保證是唯一的。 –

+0

哦,你說得對,應該絕對是'&myContextPointer',而不僅僅是'myContextPointer'。但我不明白爲什麼'int'會比'void *'好。 –

+0

沒有更好的使用int,我不認爲。鍵入'static int myContext;'稍短於輸入'static void * myContext;'。 ( - :我想你也可以這樣說,讓編譯器提醒你應該使用'&myContext'而不僅僅是'myContext' - 如果'myContext'的類型是'int',編譯器會警告 –