macOS开发:如何获取桌面目录文件的永久访问权限
macOS应用永久保留目录访问权限实现方案
问题背景
开发一款统计macOS桌面目录下文件与文件夹总数的应用,已搭建最小可复现测试项目,需求如下:
- 应用首次启动时引导用户选择桌面路径完成授权
- 后续应用重启(进程被杀死后重新打开)无需再次引导选择路径,无需重复触发系统权限弹窗
- 一次用户授权后永久保留目录访问权限,实现类似终端应用在系统隐私设置中常驻授权的效果

当前已关闭应用Sandbox,测试代码如下:
struct ContentView: View { @State private var contents: [String]? = nil var body: some View { VStack(spacing: 10.0) { Text("Count of contents of Desktop is:") Text(contents != nil ? String(contents!.count) : "Unknown").bold() } .frame(width: 200.0, height: 200.0) .padding() .onAppear { contents = directoryContentsReader(folderLocation: "/Users/myNameHere/Desktop") } } } func directoryContentsReader(folderLocation: String) -> [String]? { do { return try FileManager.default.contentsOfDirectory(atPath: folderLocation) } catch(let error) { print(error.localizedDescription) return nil } }
测试过程中macOS会随机弹出目录访问权限申请弹窗,频繁打扰用户,需要实现用户完成一次授权后,应用永久拥有对应目录访问权限,不再重复弹出权限申请弹窗。

问题原因
当前代码硬编码桌面路径直接访问,触发的是macOS TCC(Transparency, Consent, Control,透明同意控制)安全机制的临时授权流程。这类临时授权不会被系统永久记录,在应用重启、TCC缓存重置、系统版本更新等场景下会重新触发权限判定,因此会反复弹出授权提示。
实现方案
方案1:使用安全范围书签(官方推荐,适配所有场景)
安全范围书签是苹果官方提供的用户目录访问权限持久化方案,无论应用是否开启沙盒都可使用,一次授权后权限永久生效,授权条目会自动同步到系统隐私设置列表,不会重复弹窗,实现步骤如下:
- 首次启动时不直接硬编码路径访问,通过
NSOpenPanel引导用户主动选择目标目录,获取用户明确授权 - 对用户选中的目录URL生成安全范围书签数据,将书签数据持久化存储到本地(UserDefaults、应用配置文件均可)
- 后续应用启动时,直接读取本地存储的书签数据解析为可访问URL,开启安全范围访问权限后即可正常读写目录,无需重复授权
核心实现代码:
import SwiftUI import Security // 持久化书签存储键 private let desktopDirBookmarkKey = "com.yourApp.desktopAccessBookmark" struct ContentView: View { @State private var contentsCount: String = "未知" var body: some View { VStack(spacing: 10) { Text("桌面目录文件/文件夹总数:") Text(contentsCount).bold() Button("重新选择目录授权") { triggerFolderSelectFlow() } } .frame(width: 250, height: 200) .padding() .onAppear { // 优先读取本地已存储的书签恢复权限 guard let savedBookmark = UserDefaults.standard.data(forKey: desktopDirBookmarkKey) else { triggerFolderSelectFlow() return } var isStale = false do { let accessURL = try URL( resolvingBookmarkData: savedBookmark, options: [.withSecurityScope, .withoutUI], relativeTo: nil, bookmarkDataIsStale: &isStale ) // 书签过期时重新走授权流程 if isStale { triggerFolderSelectFlow() return } // 开启安全范围访问 guard accessURL.startAccessingSecurityScopedResource() else { triggerFolderSelectFlow() return } // 读取目录内容 if let contents = directoryContentsReader(folderLocation: accessURL.path) { contentsCount = String(contents.count) } // 访问完成后可按需调用停止访问方法 // accessURL.stopAccessingSecurityScopedResource() } catch { triggerFolderSelectFlow() } } } // 唤起目录选择面板,生成并持久化安全书签 private func triggerFolderSelectFlow() { let openPanel = NSOpenPanel() openPanel.canChooseDirectories = true openPanel.canChooseFiles = false openPanel.directoryURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first openPanel.prompt = "授权访问" openPanel.begin { response in guard response == .OK, let selectedURL = openPanel.url else { return } do { // 生成带安全范围的永久书签 let bookmarkData = try selectedURL.bookmarkData( options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil ) // 持久化存储到本地 UserDefaults.standard.set(bookmarkData, forKey: desktopDirBookmarkKey) // 开启权限读取目录 selectedURL.startAccessingSecurityScopedResource() if let contents = directoryContentsReader(folderLocation: selectedURL.path) { contentsCount = String(contents.count) } } catch { print("书签生成失败:\(error.localizedDescription)") } } } } func directoryContentsReader(folderLocation: String) -> [String]? { do { return try FileManager.default.contentsOfDirectory(atPath: folderLocation) } catch { print("目录读取失败:\(error.localizedDescription)") return nil } }
方案2:Info.plist声明固定目录权限(适配固定访问系统目录场景)
如果应用不需要支持用户自定义选择目录,仅固定访问桌面、文稿、下载这类系统默认受保护目录,可直接在Info.plist中添加对应目录的访问权限说明,配置完成后用户首次访问时仅会弹出一次授权提示,用户同意后权限永久生效,不会重复弹窗。
需要添加的权限键值:
- 访问桌面目录:添加键
NSDesktopFolderUsageDescription,值为权限用途说明,例如「需要访问桌面目录以统计文件与文件夹数量」 - 其他常用受保护目录对应权限键:
NSDocumentsFolderUsageDescription:访问文稿目录NSDownloadsFolderUsageDescription:访问下载目录NSRemovableVolumesUsageDescription:访问外接存储设备NSNetworkVolumesUsageDescription:访问网络存储
注意:该方案仅适用于访问系统默认枚举的受保护目录,若需要访问用户自定义的任意目录,必须使用方案1的安全范围书签实现。
内容的提问来源于stack exchange,提问作者swiftPunk
相关产品推荐
相关产品推荐

