替换NSOpenPanel的runModalForDirectory:为beginWithCompletionHandler:遇问题求助
问题原因
beginWithCompletionHandler:是异步API,调用后会立即返回,不会阻塞当前线程等待用户完成目录选择操作。这就是为什么新代码中MADE IT HERE!!的日志会先输出,用户选择目录后的日志后出现——后续代码在面板弹出后就直接执行了,不需要等用户操作。
原代码中的runModalForDirectory:是同步阻塞的API,会暂停当前线程直到用户关闭面板,所以后续代码只会在用户操作完成后才执行。
解决方案1:适配异步执行逻辑
所有依赖用户选择结果的代码,必须放到completionHandler的闭包内部。因为这个闭包是在用户完成选择(点击确定/取消)后才会被调用的。修改后的代码如下:
if ([destpath isEqualToString:@""]) { NSOpenPanel *oPanel = [NSOpenPanel openPanel]; [oPanel setAllowsMultipleSelection:NO]; [oPanel setCanChooseDirectories:YES]; [oPanel setCanChooseFiles:NO]; [oPanel beginWithCompletionHandler:^(NSInteger result) { if(result == NSModalResponseOK) { destpath = [[oPanel URL] retain].path; NSLog(@"destpath is : %@", destpath); // 把所有需要用到destpath的代码移到这里 NSLog(@"MADE IT HERE!! -- destpath is : %@", destpath); // 比如后续的文件操作、任务启动等逻辑都要放在这里 } else { // 用户取消选择后的处理逻辑 return; } }]; } else { // 如果destpath已有值,直接执行后续逻辑 NSLog(@"MADE IT HERE!! -- destpath is : %@", destpath); }
解决方案2:使用现代同步API(保持阻塞逻辑)
如果你希望继续使用类似原代码的同步阻塞方式,可以使用macOS 10.15+提供的runModal方法(搭配presenter参数),这是官方推荐替代旧runModalForDirectory:的同步方案:
if ([destpath isEqualToString:@""]) { NSOpenPanel *oPanel = [NSOpenPanel openPanel]; [oPanel setAllowsMultipleSelection:NO]; [oPanel setCanChooseDirectories:YES]; [oPanel setCanChooseFiles:NO]; // 设置初始目录 oPanel.directoryURL = [NSURL fileURLWithPath:NSHomeDirectory()]; NSInteger result = [oPanel runModal]; // macOS 10.15+ // 或者针对特定窗口作为presenter:[oPanel runModalForWindow:self.window]; if (result == NSModalResponseOK) { destpath = [[oPanel URL] retain].path; NSLog(@"destpath is : %@", destpath); } else { return; } } NSLog(@"MADE IT HERE!! -- destpath is : %@", destpath);
注意事项
- 如果项目使用ARC(自动引用计数),可以去掉
retain调用,避免内存泄漏;如果是手动引用计数,保留retain是正确的。 runModal方法在macOS 10.15+可用,若需兼容更低版本,可以使用runModalForWindow:(macOS 10.0+)。
内容的提问来源于stack exchange,提问作者RobK
相关产品推荐
相关产品推荐

