【问题标题】:Cocoa/OSX - Strange behavior in NSSavePanel that not shows sub-itensCocoa/OSX - NSSavePanel 中不显示子项的奇怪行为
【发布时间】:2014-10-02 19:13:49
【问题描述】:

我有一个具有奇怪行为的 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 相关!

提前致谢!

【问题讨论】:

    标签: macos directory nssavepanel


    【解决方案1】:

    实际上,这是NSSavePanel 与 Grand Central Dispatch 主队列和/或+[NSOperationQueue mainQueue] 之间的不良交互。您可以使用以下代码重现它:

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

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

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

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

    正确的解决方案是NSSavePanel 使用可重入机制(如运行循环源、-performSelectorOnMainThread:...CFRunLoopPerformBlock())将其拥有的任何任务提交给主线程。需要避免使用GCD的主队列或者NSOperationQueue

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

    【讨论】:

    • Ken,您刚刚提供了有关该问题的更详细说明。我接受您的回答作为对问题的最终回答。谢谢。
    【解决方案2】:

    我刚刚找到了方法:我在主线程队列的 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];
    });
    

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-05
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 2021-12-15
      相关资源
      最近更新 更多