我有一个奇怪的问题,关于一个似乎陷入等待状态的过程似乎是随机的间隔,我希望我能得到一些帮助。
当用户启动应用程序时,会显示一个窗口,该窗口从用户收集各种数据和选择列表文件条目。然后用户按下“运行”按钮,与“运行”按钮相关的应用委托方法实例化一个类(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];
}
}
答案 0 :(得分:2)
我不认为KVO与它有很大关系。你描述为“非常简单”的那个实际上非常非常复杂。这是罪魁祸首: -
[NSThread detachNewThreadSelector:@selector(runStatusStringSequence:) toTarget:self withObject:nil]
一旦你这样做,你需要的不仅仅是对Cocoa框架的理解 - 你需要很好地理解多线程编程并且很好地理解Cocoa框架在多线程世界中的工作方式。它很复杂而且很难。
你至少需要学习这些
如果您完全删除了第二个帖子,我仍然不会期望它一直有效(尽管它可能会在某些时候起作用)。您不应该直接在文本视图上调用-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指向正确的方向。