KVO绑定导致程序冻结?

时间:2011-12-24 06:45:46

标签: objective-c multithreading scrollview key-value-observing

我有一个奇怪的问题,关于一个似乎陷入等待状态的过程似乎是随机的间隔,我希望我能得到一些帮助。

当用户启动应用程序时,会显示一个窗口,该窗口从用户收集各种数据和选择列表文件条目。然后用户按下“运行”按钮,与“运行”按钮相关的应用委托方法实例化一个类(TestBindingClass),该类中有一个最终将在后台运行的方法。然后它会启动第二个xib文件,该文件显示包含空滚动视图的第二个窗口。作为在windowDIdLoad方法中启动第二个窗口的一部分,第二个窗口代码注册为TestBindingClass属性的观察者。最后,主窗口'run'方法执行TestBindingClass中的第二个线程方法,该方法将条目添加到NSMutableArray中,并且每次添加新条目时也触发被监视的属性。进入NSMutableArray的每个条目都是一行文本(字符串),它将显示在第二个xib滚动视图中。

TestBindingClass是一个类的(相对)空壳,它将在其中进行大量计算,当达到某些检查点时将发出状态消息。这些检查点消息将显示在滚动视图中。

大多数情况下,这很好。事实上,有时这完美无缺。其他时候,该过程似乎冻结,并且在挂起等待循环之前仅显示一些状态行。通常,这是滚动视图即将扩展超过窗口大小并且滚动条将被激活的点。奇怪的是,如果我在第二个xib代码中将调试断点添加到add observer方法中,它总是可以正常工作。

描述太多了......让我们展示一些代码......

以下是主窗口中“运行”按钮方法的代码

-(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属性进行dink。

 //  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];
  }

 }

2 个答案:

答案 0 :(得分: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对象的任何交互。

答案 1 :(得分: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属性进行dink。第一个主要区别是我使用了上面建议的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指向正确的方向。