启用App Sandbox的macOS SwiftUI应用,如何用Shell命令+书签创建文件夹?
App Sandbox下使用Security-Scoped书签后,Shell命令创建文件夹失败的解决方案
问题描述
我做的应用逻辑是:用户通过NSOpenPanel选目录,用security-scoped bookmark保存访问权限,再通过Process执行mkdir命令在选中目录里创建文件夹——书签本来应该能保证应用重启后还能访问该目录。但启用App Sandbox后功能直接失效,关掉沙箱就正常。哪怕已经解析了书签、拿到了访问权限,还是会弹出「Operation not permitted」错误,明显是沙箱限制了Shell命令对目录的操作。相关代码如下:
import SwiftUI struct ContentView: View { @State private var folderName: String = "" @State private var bookmarkData: Data? = nil @State private var errorMessage: String? = nil var body: some View { VStack(spacing: 20) { TextField("Enter folder name", text: $folderName) .padding() Button("Choose Desktop Folder") { chooseDesktopFolder() } Button("Create Folder") { createFolder() } .disabled(bookmarkData == nil || folderName.isEmpty) if let errorMessage = errorMessage { Text(errorMessage) .foregroundColor(.red) } } .padding() .frame(width: 400, height: 200) } private func chooseDesktopFolder() { let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.canChooseFiles = false openPanel.allowsMultipleSelection = false openPanel.prompt = "Select Folder" openPanel.begin { response in if response == .OK, let selectedURL = openPanel.url { do { let bookmark = try selectedURL.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil) self.bookmarkData = bookmark self.errorMessage = "Folder selected successfully." } catch { self.errorMessage = "Failed to create bookmark: \(error.localizedDescription)" } } else { self.errorMessage = "No folder selected." } } } private func createFolder() { guard let bookmarkData = bookmarkData else { errorMessage = "No folder selected." return } do { var isStale = false let folderURL = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) guard folderURL.startAccessingSecurityScopedResource() else { errorMessage = "Failed to access folder." return } defer { folderURL.stopAccessingSecurityScopedResource() } let newFolderURL = folderURL.appendingPathComponent(folderName) let process = Process() process.executableURL = URL(fileURLWithPath: "/bin/mkdir") process.arguments = [newFolderURL.path] try process.run() process.waitUntilExit() if process.terminationStatus == 0 { errorMessage = "Folder created successfully." } else { errorMessage = "Failed to create folder with mkdir command." } } catch { errorMessage = "Error: \(error.localizedDescription)" } } }
问题根源
security-scoped的资源访问权限只属于当前应用进程,通过Process启动的子进程(比如mkdir)根本继承不了这个权限,所以子进程没有访问目标目录的资格,自然报错。
解决方案
直接用Foundation框架的FileManagerAPI创建文件夹,不要调用Shell命令——这样就能在沙箱和权限范围内完成操作。
修改createFolder()方法里的文件夹创建逻辑,替换掉原来的Process相关代码:
private func createFolder() { guard let bookmarkData = bookmarkData else { errorMessage = "No folder selected." return } do { var isStale = false let folderURL = try URL(resolvingBookmarkData: bookmarkData, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale) guard folderURL.startAccessingSecurityScopedResource() else { errorMessage = "Failed to access folder." return } defer { folderURL.stopAccessingSecurityScopedResource() } let newFolderURL = folderURL.appendingPathComponent(folderName) // 用FileManager替代Shell命令创建文件夹 try FileManager.default.createDirectory(at: newFolderURL, withIntermediateDirectories: true) errorMessage = "Folder created successfully." } catch { errorMessage = "Error: \(error.localizedDescription)" } }
额外说明
- 沙箱环境下,子进程无法继承主进程的security-scoped权限,所有文件操作最好都在主进程内用系统API完成。
FileManager.default.createDirectory本身支持沙箱权限,只要主进程已经通过startAccessingSecurityScopedResource()拿到了目录访问权,就能正常执行创建。- 如果确实需要调用子进程处理文件,得用XPC服务的方式,给XPC配置对应沙箱权限,但这会增加复杂度,单纯创建文件夹的话,用FileManager是最省心的方案。
内容的提问来源于stack exchange,提问作者swiftPunk
相关产品推荐
相关产品推荐

