0

我有一个具有奇怪行为的 NSSavePanel 实例:每当我打开它并单击目录的箭头(小展开按钮)时,它会在左下角显示一个永不结束的不确定加载图标,并且不显示目录/文件树。一张图片可以看到如下:

在此处输入图像描述

在该示例中,我单击了“工作区”目录。并且面板不显示子项。甚至奇怪的是,当我再次单击它(重新绘制目录)然后再次单击(重新打开目录)后,它正确显示了所有文件。

我的代码如下:

// here, I'm creating a web service client, and then calling a method to download a report, and passing the same class as delegate
- (IBAction) generateReport:(id)sender {

    // SOME STUFF HERE...

    WSClient *client = [[[WSClient alloc] init] initWithDelegate:self];
    [client GenerateReport:@"REPORT" withParams:params];
}

- (void) GenerateReport:(NSString *)from withParams:(NSDictionary *)parameters {

    // SOME STUFF HERE...

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];
    [NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
    if (!error) {
        dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
            dispatch_async(dispatch_get_main_queue(), ^(void) {
                NSLog(@"GenerateReport: [success]");
                [self.delegate successHandlerCallback:data from: from];
            });
        });
    }
}];

// this is the callback
- (void) successHandlerCallback:(NSData *) data from: (NSString *) from {
    NSString savePath = [savePath stringByReplacingOccurrencesOfString:@"file://" withString:@""];
    NSString *filePath = [NSString stringWithFormat:@"%@", savePath];
    [data writeToFile:filePath atomically:YES];
}

// and this is to build a panel to let user chose the directory to save the file
- (NSURL *) getDirectoryPath {
    NSSavePanel *panel = [NSSavePanel savePanel];
    [panel setNameFieldStringValue:[self getDefaultFileName]];
    [panel setDirectoryURL:[NSURL fileURLWithPath:[[NSString alloc] initWithFormat:@"%@%@%@", @"/Users/", NSUserName(), @"/Downloads"]]];
    if ([panel runModal] != NSFileHandlingPanelOKButton) return nil;
    return [panel URL];
}

有人可以提示我在哪里失踪吗?

更新:对我来说,它似乎与 dispatch_async 相关!

提前致谢!

4

2 回答 2

1

实际上,这是NSSavePanel与 Grand Central Dispatch 主队列和/或+[NSOperationQueue mainQueue]. 您可以使用以下代码重现它:

dispatch_async(dispatch_get_main_queue(), ^{
    [[NSSavePanel savePanel] runModal];
});

首先,任何时候执行 GUI 操作,都应该在主线程上执行。(有极少数例外,但您现在应该忽略它们。)因此,如果要执行诸如打开文件对话框之类的操作,您将工作提交到主队列是正确的。

不幸的是,主队列是串行队列,这意味着它一次只能运行一个任务,并将NSSavePanel自己的一些工作提交给主队列。因此,如果您将任务提交到主队列并且该任务以模态方式运行保存面板,那么它将独占主队列,直到保存面板完成。但是保存面板依赖于将自己的任务提交到主队列并让它们运行的​​能力。

就我而言,这是 Cocoa 中的一个错误。您应该向 Apple 提交错误报告。

正确的解决方案是NSSavePanel使用可重入机制(如 run-loop 源-performSelectorOnMainThread:...、 或CFRunLoopPerformBlock(). 它需要避免使用 GCD 或NSOperationQueue.

由于您不能等待 Apple 解决此问题,因此解决方法是让您也这样做。使用上述机制之一将可能运行保存面板的任务提交到主队列。

于 2014-10-02T22:25:49.403 回答
0

我刚刚找到了方法:我在主线程队列的 dispatch_async 中进行了所有调用。由于在回调时刻下载仍在运行,它与打开面板的线程发生冲突。我通过放置正确的行来解决所有问题,从:

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
    dispatch_async(dispatch_get_main_queue(), ^(void) {
        NSLog(@"GenerateReport: [success]");
        [self.delegate successHandlerCallback:data from: from];
    });
});

至:

dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(void) {
    NSLog(@"GenerateReport: [success]");
    [self.delegate successHandlerCallback:data from: from];
});

在回调中,只需更新字段。最后,我发现所有这些都是我误解的主/后台线程。

于 2014-10-02T21:31:46.063 回答