从磁盘读取文件时捕获错误 - 您没有权限

Catch Error Reading File From Disk - You Don't Have Permissions

我在 AppDel 中使用以下代码。当用户点击 gpx 文件或使用共享选项与我的应用程序共享文件时会触发此事件。此时是用户指定他们允许我的应用程序访问该文件,所以我有点困惑为什么这仍然被拒绝。非常感谢任何建议。

func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool {
    
    if let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first {
        let fileURL = dir.appendingPathComponent(url.lastPathComponent)
        if FileManager.default.fileExists(atPath: fileURL.path) {
            print("File already exists")
        }
        else {
            do {
                try FileManager.default.copyItem(at: url, to: fileURL)
                print("Did write file to disk")
            }
             catch {
                 DispatchQueue.main.async(){
                         print("Catch error writing file to disk: \(error)")
                 }
             }
        }
    }
    return true
}

错误打印到控制台如下:

Error Domain=NSCocoaErrorDomain Code=257 "The file “Beech_Hill_Long_Route.gpx” couldn’t be opened because you don’t have permission to view it." UserInfo={NSFilePath=/private/var/mobile/Library/Mobile Documents/com~apple~CloudDocs/Desktop/Beech_Hill_Long_Route.gpx, NSUnderlyingError=0x282c19a70 {Error Domain=NSPOSIXErrorDomain Code=1 "Operation not permitted"}}

我不太了解它在 iOS 上的具体工作原理,但我确实了解它在 macOS 上的工作原理,两者应该相似。对于 macOS 沙盒应用程序,用户必须 select 沙盒外的文件,特别是通过 NSOpenPanelNSSavePanel。 iOS 上的等效项是 UIDocumentPickerViewController,但我认为显式共享也可以。基于此,如果我遇到您的问题,我会如何考虑它以及我会尝试什么:

您有两个 URL 参与了对 FileManager.default.copy() 的调用。目前尚不清楚是哪一个产生了错误,所以我会考虑两者。

先来看看fileURL。您正在构建它以将文件放入用户的 Documents 目录中。至少在 macOS 上,该目录在应用程序的沙箱中是 而不是 ,这意味着通过 NSSavePanel 询问用户(这也意味着他们可能决定将其放在其他地方)。您可能必须执行 UIKit 等效操作,或者只需确保您选择的位置在您的沙盒中。

要对此进行测试,请尝试写入 fileURL 以仅隔离那个副本,而不是进行复制。例如:

do { try "TestString".data(using: .utf8)?.write(url: fileURL) }
catch { print("Write failed: \(error.localizedDescription)") }

如果失败,那么您的问题出在 fileURL,在这种情况下,您可能需要使用 UIDocumentPickerViewController 来保存它,或者选择一个肯定在应用程序沙盒中的位置。

如果测试成功,肯定是传入的问题URL

我假设 url 已经在安全范围内,因为我不确定共享在其他情况下会如何工作。我认为最有可能在幕后发生的是,当用户与您的应用程序共享 URL 时,iOS 从 URL 创建一个安全范围的书签并发送书签而不是URL 到您的应用程序。然后在您的应用程序端,该书签用于在将 URL 传递给您的应用程序的委托之前重构它。如果我是对的,您需要打开和关闭安全范围才能使用它:

url.startAccessingSecurityScopedResource()

// access your file here

url.stopAccessingSecurityScopedResource()

请注意,stopAccessingSecurityScopeResource() 必须在主线程上调用,因此如果您的代码是异步发生的,则需要将其安排到 运行 那里:

url.startAccessingSecurityScopedResource()

// access your file here

DispatchQueue.main.async { url.stopAccessingSecurityScopedResource() }

如果您需要保存 URL 本身以便在您的程序的未来 运行 中使用...您不能。好吧,你可以,但它不会有效,所以你会马上回到权限错误。相反,您必须保存书签,然后在将来 运行,从书签重建 URL

let bookmark = try url.bookmarkData(
    options: .withSecurityScope, 
    includingResourceValuesForKeys: nil, 
    relativeTo: nil
)

bookmarkData 的实例,因此您可以将其写入 UserDefaults 或您可能想要保存的任何位置。稍后要从中取回 URL

var isStale = false
let url = try URL(
    resolvingBookmarkData: bookmark, 
    options: .withSecurityScope, 
    relativeTo: nil, 
    bookmarkDataIsStale: &isStale
)

if isStale 
{
    let newBookmark = try url.bookmarkData(
        options: .withSecurityScope, 
        includingResourceValuesForKeys: nil, 
        relativeTo: nil
    )
    // Save the new bookmark
}

注意如果初始化returns后isStaletrue,那么需要重新制作并重新保存书签

很遗憾我们不得不经历这么多麻烦,但我们生活在一个有些人坚持对其他人的设备和数据做坏事的世界,所以这是我们必须处理的保护用户来自恶意数据泄露。