从磁盘读取文件时捕获错误 - 您没有权限
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 沙盒外的文件,特别是通过 NSOpenPanel
或 NSSavePanel
。 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
)
bookmark
是 Data
的实例,因此您可以将其写入 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后isStale
是true
,那么需要重新制作并重新保存书签
很遗憾我们不得不经历这么多麻烦,但我们生活在一个有些人坚持对其他人的设备和数据做坏事的世界,所以这是我们必须处理的保护用户来自恶意数据泄露。
我在 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 沙盒外的文件,特别是通过 NSOpenPanel
或 NSSavePanel
。 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
)
bookmark
是 Data
的实例,因此您可以将其写入 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后isStale
是true
,那么需要重新制作并重新保存书签
很遗憾我们不得不经历这么多麻烦,但我们生活在一个有些人坚持对其他人的设备和数据做坏事的世界,所以这是我们必须处理的保护用户来自恶意数据泄露。