Cocoa / OSX - NSSavePanel中没有显示子代的奇怪行为 [英] Cocoa/OSX - Strange behavior in NSSavePanel that not shows sub-itens

查看:243
本文介绍了Cocoa / OSX - NSSavePanel中没有显示子代的奇怪行为的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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





在这个例子中,我点击了workspace目录。并且面板没有显示子代。甚至奇怪的是,我再次点击它(重新绘制目录),然后再次单击(重新打开目录),它正确显示所有文件。



我的代码是如下:

  //这里,我正在创建一个Web服务客户端,然后调用一个方法来下载报告,并通过与委托相同的类$ b $(IBAction)generateReport:(id)sender {

// SOME STUFF HERE ...

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

- (void)GenerateReport:(NSString *)fromParams:(NSDictionary *)参数{

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

//这是回调
- (void)successHandlerCallback:(NSData *)数据来自:(NSString *)from {
NSString savePath = [savePath stringByReplacingOccurrencesOfString:@ file://withString:@];
NSString * filePath = [NSString stringWithFormat:@%@,savePath];
[data writeToFile:filePath atomically:YES];
}

//这是构建一个面板,让用户选择目录来保存文件
- (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相关的东西!



提前感谢

解决方案

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

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

首先,任何时候执行GUI操作,都应该在主线程上执行。 (有一些罕见的例外,但你现在应该忽略它们。)所以,如果要打开文件对话框,你可以将工作提交到主队列。



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



就我而言,这是一个错误在可可您应该向苹果提交错误报告。



正确的解决方案是将 NSSavePanel 提交任何与之相关的任务主线程使用重入机制,如运行循环源, -performSelectorOnMainThread:... CFRunLoopPerformBlock()。它需要避免使用GCD的主队列或 NSOperationQueue



既然你不能等待苹果解决这个问题,解决方法是为您做同样的事情。使用上述机制之一提交可能运行保存面板到主队列的任务。


I've a NSSavePanel instance with a strange behavior: whenever I open it and click on a directory's arrow (the little expand button) it shows an indeterminate loading icon on the left-bottom corner that never ends, and not shows the directory/file tree. An image can see as follow:

In that example, I've clicked in "workspace" directory. And the panel not shows the sub-itens. Even strange is that after I click it again (redrawing the directory) and then click again (re-open the directory), it properly shows all files.

My code is as follows:

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

Can someone give a hint on where I'm missing?

UPDATE: To me, it seens to be something related with dispatch_async!

Thanks in advance!

解决方案

Actually, this is a bad interaction between NSSavePanel and the Grand Central Dispatch main queue and/or +[NSOperationQueue mainQueue]. You can reproduce it with just this code:

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

First, any time you perform GUI operations, you should do so on the main thread. (There are rare exceptions, but you should ignore them for now.) So, you were right to submit the work to the main queue if it was going to do something like open a file dialog.

Unfortunately, the main queue is a serial queue, meaning that it can only run one task at a time, and NSSavePanel submits some of its own work to the main queue. So, if you submit a task to the main queue and that task runs the save panel in a modal fashion, then it monopolizes the main queue until the save panel completes. But the save panel is relying on the ability to submit its own tasks to the main queue and have them run.

As far as I'm concerned, this is a bug in Cocoa. You should submit a bug report to Apple.

The correct solution is for NSSavePanel to submit any tasks it has to the main thread using a re-entrant mechanism like a run-loop source, -performSelectorOnMainThread:..., or CFRunLoopPerformBlock(). It needs to avoid using the main queue of GCD or NSOperationQueue.

Since you can't wait for Apple to fix this, the workaround is for you to do the same. Use one of the above mechanisms to submit your task that may run the save panel to the main queue.

这篇关于Cocoa / OSX - NSSavePanel中没有显示子代的奇怪行为的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆