Skip to content
On this page

macOS sandbox 文件夹授权

macOS如果想上苹果市场发布的话,那么必须要遵守苹果的沙盒协议,这样应用的存储默认都是沙盒路径,隔离了用户的文件系统,那么这个时候我需要访问 /User/xxx/Library/Developer/ 这种文件夹的时候,直接访问是会被拒绝的,那既然这样就肯定要授权了,等同意了在访问就名正言顺了。

那么要如何进行这种操作呢,首先想到的肯定是先把这个文件夹打开再说,然后当次的权限就有了,然后就是解决后续依然有权限访问的问题,这个时候就用到了 bookmark,这个东东就是转门干这个用的,下面一步一步的来实现这个功能。

本例子以授权 /user/xxx/Library/Developer/ 路径为例。

首先获取到文件绝对路径。

swift
func getAbsolutePath(path: String) -> String? {
    let pw = getpwuid(getuid())
    guard let home = pw?.pointee.pw_dir else {
        return nil
    }
    let homePath = FileManager.default.string(withFileSystemRepresentation: home, length: Int(strlen(home)))
    return "\(homePath)/\(path)"
}

let path = getAbsolutePath("/Library/Developer")
// /User/xxx/Library/Developer/

打开文件夹获取权限

swift
func openFinder() {
    let fm = FileManager.default
    let filepath = getAbsolutePath(path: "Library/Developer/")!
    print("filepath is \(filepath)")
    let url = URL(filePath: filepath)
    
    let dialog = NSOpenPanel()
    dialog.message = "文件夹授权以提供服务"
    dialog.prompt = "选择"
    dialog.allowsOtherFileTypes = false
    dialog.canChooseFiles = false
    dialog.canChooseDirectories = true
    dialog.directoryURL = url
    
    dialog.begin { response in
        if response == .OK, let folderPath = dialog.url {
            // 可以判断一下是不是想要的文件夹,这个时候已经有权限了。
            // 保存书签权限
        }
    }
}

保存书签权限

把书签数据保存在bookmark中,以后用的时候再取出来用。

swift
private func saveBookmarkData(for workDir: URL) {
    do {
        let bookmarkData = try workDir.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)

        print("save book mark data")
        // save in UserDefaults
        UserDefaults.standard.setValue(bookmarkData, forKey: "bookmark")
    } catch {
        print("Failed to save bookmark data for \(workDir)", error)
    }
}

判断路径是否授权过

swift
private func restoreFileAccess() -> Bool {
    do {
        if let bookmark = UserDefaults.standard.object(forKey: "bookmark") as? Data {
            var isStale = false
            let url = try URL(resolvingBookmarkData: bookmark, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
            guard url.startAccessingSecurityScopedResource() else {
                return false
            }
            return true
        }
        return false
    } catch {
        print("Error resolving bookmark:", error)
        return false
    }
}

完整代码

以下为完整代码

swift
import SwiftUI

struct ContentView: View {
    @State var fileSize: Int = 0
    @State var canAccess: Bool = false
    
    func getAbsolutePath(path: String) -> String? {
        let pw = getpwuid(getuid())
        guard let home = pw?.pointee.pw_dir else {
            return nil
        }
        let homePath = FileManager.default.string(withFileSystemRepresentation: home, length: Int(strlen(home)))
        return "\(homePath)/\(path)"
    }
    
    private func saveBookmarkData(for workDir: URL) {
        do {
            let bookmarkData = try workDir.bookmarkData(options: .withSecurityScope, includingResourceValuesForKeys: nil, relativeTo: nil)

            print("save book mark data")
            // save in UserDefaults
            UserDefaults.standard.setValue(bookmarkData, forKey: "bookmark")
        } catch {
            print("Failed to save bookmark data for \(workDir)", error)
        }
    }
    
    private func restoreFileAccess() -> Bool {
        do {
            if let bookmark = UserDefaults.standard.object(forKey: "bookmark") as? Data {
                var isStale = false
                let url = try URL(resolvingBookmarkData: bookmark, options: .withSecurityScope, relativeTo: nil, bookmarkDataIsStale: &isStale)
                guard url.startAccessingSecurityScopedResource() else {
                    return false
                }
                return true
            }
            return false
        } catch {
            print("Error resolving bookmark:", error)
            return false
        }
    }
    
    func openFinder() {
        let fm = FileManager.default
        let filepath = getAbsolutePath(path: "Library/Developer/")!
        print("filepath is \(filepath)")
        let url = URL(filePath: filepath)
        
        let dialog = NSOpenPanel()
        dialog.message = "Choose your directory"
        dialog.prompt = "Choose"
        dialog.allowsOtherFileTypes = false
        dialog.canChooseFiles = false
        dialog.canChooseDirectories = true
        dialog.directoryURL = url
        
        dialog.begin { response in
            if response == .OK, let folderPath = dialog.url {
                saveBookmarkData(for: folderPath)
            }
        }
    }
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("占用容量: \(fileSize)")
            
            Button {
                if !canAccess {
                    openFinder()
                }
            } label: {
                if canAccess {
                    Text("已授权")
                } else {
                    Text("授权")
                }
            }
        }
        .padding()
        .onAppear {
            if restoreFileAccess() {
                canAccess = true
            } else {
                canAccess = false
            }
            print("access is \(canAccess)")
        }
    }
}

#Preview {
    ContentView()
}