1

我有一個奇怪的問題,它似乎陷入了一個似乎是隨機間隔的等待狀態,希望我能得到一些有關這方面的幫助。KVO綁定導致程序凍結?

當用戶啓動應用程序時,會顯示一個窗口,用於收集用戶的各種數據和選擇列表文件條目。然後用戶按下'run'按鈕,與'run'按鈕相關的app委託方法實例化一個類(TestBindingClass),該類中有一個方法,最終將在後臺運行。然後啓動第二個xib文件,顯示第二個包含空滾動視圖的窗口。作爲在windowDIdLoad方法中啓動第二個窗口的一部分,第二個窗口代碼註冊爲TestBindingClass屬性的觀察者。最後,主窗口'run'方法執行TestBindingClass中的第二個線程方法,該方法將條目添加到NSMutableArray中,並且每次添加新條目時也會觸發受監視的屬性。 NSMutableArray中的每個條目都是一行文本(字符串),它將顯示在第二個xib滾動視圖中。

TestBindingClass是一個類的(相對)空的shell,它將在其中進行大量的計算,當達到某些檢查點時它將發出狀態消息。這些檢查點消息將顯示在滾動視圖中。

大多數情況下,這工作正常。事實上,有時這可以完美地工作。其他時候,這個過程似乎凍結了,並且在掛起等待循環之前僅顯示一些狀態行。通常這是滾動視圖將要擴展超過窗口大小並且滾動條將被激活的點。奇怪的是,如果我將調試斷點添加到第二個xib代碼中的添加觀察器方法中,它總是正常工作。

這麼多的介紹...讓我們展示一些代碼....

下面是在主窗口中

-(IBAction)runButtonPressed:(id)sender 
{ 
// do a bunch of stuff 

TestBindingClass* tempTestBindingClass = [[TestBindingClass alloc] init]; 

RunResultWindowController = [[RunResultWindow alloc] initWithWindowNibName:@"RunResultWindow"]; 
RunResultWindowController.localTestBindingClass=tempTestBindingClass; 
[RunResultWindowController showWindow:self]; 

[self.StatusDisplayOutput setStringValue:@"Run Complete"]; 

[tempTestBindingClass submitStatusStringSequence]; 

} 

這裏的「運行」按鈕,方法的代碼是代碼對於上面提到的TestBindingClass。 StatusStrings是Mutable數組,它將包含應該最終顯示在滾動視圖中的狀態字符串列表,該視圖通過arraystatuscounter屬性觀察此類。

// TestBindingClass.h 

#import <Foundation/Foundation.h> 

@interface TestBindingClass : NSObject { 

NSMutableArray *StatusStrings; 
    int arraystatuscounter; 


} 

@property (nonatomic, retain) NSMutableArray *StatusStrings; 
@property int arraystatuscounter; 

- (void) runStatusStringSequence:(id)param; 
- (void) submitStatusStringSequence; 

@end 

這表明TestBindingClass的代碼,這是相當簡單的。只要將愚蠢的小字符串一次放入NSMutableArray中,並在每次向數組中添加一個字符串時調用arraystatuscounter屬性。

// TestBindingClass.m 

- (void) submitStatusStringSequence 
{ 

    [NSThread detachNewThreadSelector:@selector(runStatusStringSequence:) toTarget:self withObject:nil]; 

} 


- (void) runStatusStringSequence:(id)param 
{ 


NSMutableArray *StatusStringsAlloc = [[NSMutableArray alloc] initWithCapacity:1]; 
StatusStrings = StatusStringsAlloc; 

[StatusStrings addObject:[NSString stringWithString:@"first string"]]; 
[self setArraystatuscounter:1]; 

[StatusStrings addObject:[NSString stringWithString:@"second string"]]; 
[self setArraystatuscounter:2]; 

[StatusStrings addObject:[NSString stringWithString:@"third string"]]; 
[self setArraystatuscounter:3]; 

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]]; 
[self setArraystatuscounter:4]; 

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]]; 
[self setArraystatuscounter:5]; 

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]]; 
[self setArraystatuscounter:6]; 

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]]; 
[self setArraystatuscounter:7]; 

[StatusStrings addObject:[NSString stringWithString:@"last string"]]; 
[self setArraystatuscounter:8]; 

[StatusStrings addObject:[NSString stringWithString:@"first string"]]; 
[self setArraystatuscounter:1]; 

[StatusStrings addObject:[NSString stringWithString:@"second string"]]; 
[self setArraystatuscounter:2]; 

[StatusStrings addObject:[NSString stringWithString:@"third string"]]; 
[self setArraystatuscounter:3]; 

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]]; 
[self setArraystatuscounter:4]; 

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]]; 
[self setArraystatuscounter:5]; 

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]]; 
[self setArraystatuscounter:6]; 

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]]; 
[self setArraystatuscounter:7]; 

[StatusStrings addObject:[NSString stringWithString:@"last string"]]; 
[self setArraystatuscounter:8]; 

} 

這是RunResultWindows窗口控制器代碼。 TestBindingClass的地址被添加到ivars中,以便它可以正確設置必要的KVO觀察者設置。

// RunResultWindow.h 

@interface RunResultWindow : NSWindowController { 

    NSTextView *RunResultWindowTextView; 
    TestBindingClass *localTestBindingClass; 
} 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; 

@property (strong) IBOutlet NSTextView *RunResultWindowTextView; 
@property (nonatomic, retain) TestBindingClass *localTestBindingClass; 

@end 

這裏是感興趣的RunResultWindow方法。請原諒arraycount if語句,這是爲了一些臨時的調試目的而添加的,並且永遠不會被刪除。

// RunResultWindow.m methods of interest 

- (void)windowDidLoad 
{ 
    [super windowDidLoad]; 

    NSWindow *wcWindow; 
    wcWindow = [self window]; 
    [wcWindow makeKeyAndOrderFront:self]; 

    NSString *teststring; 
    teststring = [NSString stringWithString: @"show first time window did load "]; 
    [RunResultWindowTextView setString:teststring]; 
    [RunResultWindowTextView display]; 

    [localTestBindingClass addObserver:self 
     forKeyPath:@"arraystatuscounter" 
      options:NSKeyValueObservingOptionNew 
      context:NULL]; 

} 

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change 
    context:(void *)context 
{ 
    NSInteger arrayCount; 
    NSString* localDisplayString; 
    NSString* localNewlinePlusDisplayString; 
    NSTextStorage *tempTextStorage; 

    tempTextStorage = [RunResultWindowTextView textStorage]; 

    arrayCount = [ localTestBindingClass.StatusStrings count ]; 
    if (arrayCount >= 1) { 
    arrayCount--; 
    localDisplayString = [localTestBindingClass.StatusStrings objectAtIndex:arrayCount]; 
    localNewlinePlusDisplayString = [@"\n" stringByAppendingString:localDisplayString]; 
    [tempTextStorage beginEditing]; 
    [tempTextStorage replaceCharactersInRange:NSMakeRange([tempTextStorage length] - 1, 0) 
          withString:localNewlinePlusDisplayString]; 
    [tempTextStorage endEditing]; 
    [RunResultWindowTextView display]; 
    } 

} 
+0

有一點我使用inserttext方法,而不是開始編輯/ endEditing排序,並且通常在添加到滾動視圖中的第一行或第二行之後凍結。再次,當我把它放到調試中,並將其放入obsereValueForKeyPath方法的斷點時,它可以正常工作。當我用上面顯示的方法替換insertText時,它工作得好多了,但仍不完全正確。 – 2011-12-24 06:50:00

+0

一個有趣的問題。我不確定答案是什麼,但我認爲它可能是一個分離一個新線程,用相同方法更新相同觀測值6次,然後用觀察方法直接從觀測值中獲取新值的組合對象,而不是來自'change'字典。這是猜測,但那些是你的代碼中沒有聞到的東西。請注意,按照慣例,KVO確實起作用,您應該使用小寫字母開始變量名稱。你觀察到的變量是好的,但有一些沒有。 – jrturton 2011-12-24 07:25:19

回答

2

我不認爲KVO與它有很大關係。你描述爲「非常簡單」的那一點實際上是非常複雜的。這是罪魁禍首: -

[NSThread detachNewThreadSelector:@selector(runStatusStringSequence:) toTarget:self withObject:nil] 

只要你做到這一點,你需要比Cocoa框架的一個很好的瞭解更多 - 你需要多線程編程的一個很好的理解和Cocoa框架中是如何工作的一個很好的理解一個多線程的世界。這很複雜,很難。

您將至少需要研究這些

http://developer.apple.com/library/ios/#documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html

http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/Multithreading.pdf

如果刪除了第二個線程總而言之,我還是不希望這樣工作時間(雖然它可能工作一些時間)。你不應該直接在文本視圖上調用-display。繪製視圖,設置繪圖上下文,刷新率等由框架爲您處理。

只需在隨機時間調用-display而不知道當前繪圖上下文的狀態足以導致應用程序崩潰(您可能很容易將其繪製到任何隨機內存中)。將線程添加到混音中,並且遇到更多麻煩。

你的方法

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context

被稱爲在後臺線程但是GUI不是線程安全的,必須在主線程上運行,包括像文本視圖的GUI對象的任何交互。

+0

奇怪的是,這個過程通常是我第一次執行程序(在沒有斷點的調試器中)。關閉第二個窗口(並在此過程中移除KVO觀察者)並通過按下主窗口上的運行按鈕再次啓動它,那是當我開始出現問題時。另一個奇怪的是,如果我把一個斷點放到observeValueForKeyPath方法中,這個ALWAYS就可以工作。 – 2011-12-24 15:29:19

+0

我想我不需要顯示方法,因爲滾動視圖將在方法完成時刷新。這段代碼是一個宿醉,它試圖在完全不同的示例代碼集中的同一個方法中多次刷新滾動視圖。 – 2011-12-24 15:34:12

+0

最後,感謝您的鏈接,我會審查他們,看看我能理解他們。我想在某些時候讓它運行,因爲我想用實際工作的代碼替換這些更簡單的方法。 – 2011-12-24 15:35:48

0

這是實際工作的代碼。請原諒解決方案的前一次迭代遺留的錯誤信息和偶爾出現的奇怪代碼。

下面是在主窗口中的「運行」按鈕的方法的代碼...

- (IBAction)runButtonPressed:(id)sender 
{ 

    // do a bunch of stuff 

    TestBindingClass* tempTestBindingClass = [[TestBindingClass alloc] init]; 

    RunResultWindowController = [[RunResultWindow alloc] initWithWindowNibName:@"RunResultWindow"]; 
    RunResultWindowController.localTestBindingClass=tempTestBindingClass; 
    [RunResultWindowController showWindow:self]; 

    [self.outputfilestring1 becomeFirstResponder]; 

    [tempTestBindingClass submitStatusStringSequence]; 

} 

下面是TestBindingClass代碼如上文所提及。 StatusStrings是可變數組,它將包含狀態字符串列表,該列表應該最終顯示在通過arraystatuscounter屬性觀察此類的滾動視圖中。

// TestBindingClass.h 

#import <Foundation/Foundation.h> 

@interface TestBindingClass : NSObject { 

NSMutableArray *StatusStrings; 
    int arraystatuscounter; 


} 

@property (nonatomic, retain) NSMutableArray *StatusStrings; 
@property int arraystatuscounter; 

- (void) runStatusStringSequence:(id)param; 
- (void) submitStatusStringSequence; 

@end 

這顯示了TestBindingClass的實現代碼。它只是將愚蠢的小字符串一次性放入NSMutableArray中,並在每次向數組中添加一個字符串以啓動KVO觀察方法時調整arraystatuscounter屬性。第一個主要區別是,我使用的GCD方法如上建議...

// TestBindingClass.m 

- (void) submitStatusStringSequence 
{ 
    NSString *parameterString; 
    [self runStatusStringSequence: parameterString]; 
} 


- (void) runStatusStringSequence:(id)param 
{ 

dispatch_queue_t backgroundQueue = dispatch_queue_create("Background Queue",NULL); 

dispatch_async(backgroundQueue, ^{ 

NSMutableArray *StatusStringsAlloc = [[NSMutableArray alloc] initWithCapacity:1]; 
StatusStrings = StatusStringsAlloc; 

[StatusStrings addObject:[NSString stringWithString:@"first string"]]; 
[self setArraystatuscounter:1]; 

[StatusStrings addObject:[NSString stringWithString:@"second string"]]; 
[self setArraystatuscounter:2]; 

[StatusStrings addObject:[NSString stringWithString:@"third string"]]; 
[self setArraystatuscounter:3]; 

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]]; 
[self setArraystatuscounter:4]; 

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]]; 
[self setArraystatuscounter:5]; 

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]]; 
[self setArraystatuscounter:6]; 

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]]; 
[self setArraystatuscounter:7]; 

[StatusStrings addObject:[NSString stringWithString:@"last string"]]; 
[self setArraystatuscounter:8]; 

[StatusStrings addObject:[NSString stringWithString:@"first string"]]; 
[self setArraystatuscounter:1]; 

[StatusStrings addObject:[NSString stringWithString:@"second string"]]; 
[self setArraystatuscounter:2]; 

[StatusStrings addObject:[NSString stringWithString:@"third string"]]; 
[self setArraystatuscounter:3]; 

[StatusStrings addObject:[NSString stringWithString:@"fourth string"]]; 
[self setArraystatuscounter:4]; 

[StatusStrings addObject:[NSString stringWithString:@"fifth string"]]; 
[self setArraystatuscounter:5]; 

[StatusStrings addObject:[NSString stringWithString:@"sixth string"]]; 
[self setArraystatuscounter:6]; 

[StatusStrings addObject:[NSString stringWithString:@"seventh string"]]; 
[self setArraystatuscounter:7]; 

[StatusStrings addObject:[NSString stringWithString:@"last string"]]; 
[self setArraystatuscounter:8]; 

dispatch_release(backgroundQueue); 

}); 


} 

這裏是RunResultWindows窗口控制器代碼。 TestBindingClass的地址被添加到ivars中,以便它可以正確設置必要的KVO觀察者設置。另外,我強制該方法在主線程上運行,因爲我猜想它在後臺線程上運行,因爲它是由已在後臺線程上運行的進程調用的。

@interface RunResultWindow : NSWindowController { 

    NSTextView *RunResultWindowTextView; 
    TestBindingClass *localTestBindingClass; 

} 

- (IBAction)FinishButtonPush:(id)sender; 
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; 

@property (strong) IBOutlet NSTextView *RunResultWindowTextView; 
@property (nonatomic, retain) TestBindingClass *localTestBindingClass; 

@end 

這裏是感興趣的RunResultWindow方法。請原諒那些爲了一些臨時調試目的而添加的arraycount if語句並且永遠不會刪除。請注意,它被強制轉到主線程。

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change 
     context:(void *)context 
{  

    dispatch_queue_t mainQueue = dispatch_get_main_queue(); 
    dispatch_async(mainQueue, ^{ 

    NSInteger arrayCount; 
    NSString* localDisplayString; 
    NSString* localNewlinePlusDisplayString; 
    NSTextStorage *tempTextStorage; 

    tempTextStorage = [RunResultWindowTextView textStorage]; 

    arrayCount = [ localTestBindingClass.StatusStrings count ]; 
    if (arrayCount >= 1) { 
     arrayCount--; 
     localDisplayString = [localTestBindingClass.StatusStrings objectAtIndex:arrayCount]; 
     localNewlinePlusDisplayString = [@"\n" stringByAppendingString:localDisplayString]; 
     [tempTextStorage beginEditing]; 
     [tempTextStorage replaceCharactersInRange:NSMakeRange([tempTextStorage length] - 1, 0) 
           withString:localNewlinePlusDisplayString]; 
     [tempTextStorage endEditing]; 
     [RunResultWindowTextView setNeedsDisplay:YES];} 

    }); 
} 

非常感謝hooleyhoop的正確方向的好指針。