FileManager .removeItem(at:) 需要很长时间——我怎么知道它何时完成?
FileManager .removeItem(at:) takes long – how do I know when it's done?
我的用户能够将文件本地存储在 Documents/
文件夹中。稍后,他们可以从列表中删除这些文件。我这样删除文件:
List {
ForEach(urls, id: \.self) { url in
Text(url.lastPathComponent)
}
.onDelete { row in
guard let i = row.first, let fileURL = urls[safe: i] else { return }
do {
try FileManager.default.removeItem(at: fileURL)
updateURLs() // → crashes here
}
catch { print(error.localizedDescription) }
}
}
// ...
func updateURLs() {
urls = FileManager.default.urls(
in: FileManager.default.documentsDirectory
.appendingPathComponent("unuploaded-documents")
)
}
// extension of FileManager:
extension FileManager {
public var documentsDirectory: URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: "")
}
public func urls(in directory: URL) -> [URL] {
var fileURLs: [URL] = []
if let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
if fileAttributes.isRegularFile ?? false {
fileURLs.append(fileURL)
}
} catch { print(error, fileURL) }
}
}
return fileURLs
}
}
问题
文件删除后,我需要更新文件列表(urls)。但是,我的应用程序在 updateURLs()
调用时崩溃,只提供此错误:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
解决方法
我认为这是因为 urls 在文件被完全删除之前更新了。然后,访问 url 文件现在不再存在;因此应用程序崩溃。
当像这样延迟包装 updateURLs()
调用时:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
updateURLs()
}
...它不会崩溃并且我的列表会正确更新。然而,这并不是真正的解决方案,因为
- 它会导致糟糕的用户体验——文件被快速删除时不必要的长时间等待
- 如果文件删除时间超过1s,会再次崩溃
研究
从 开始,我认为 .removeItem(:)
方法 应该是 同步的,而不是异步的。但是,我使用延迟的解决方法让我猜测不是这样。
问题
我如何知道文件实际删除的时间,以便我可以在正确的时间更新我的列表? .removeItem(:)
方法似乎没有完成处理程序...
崩溃的完整堆栈跟踪:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
frame #0: 0x00007fff5b5e19f3 SwiftUI`SwiftUI.SystemListDataSource.contextForItem(index: (Swift.Int, Swift.Int)) -> SwiftUI._RowVisitationContext<SwiftUI.SystemListDataSource<τ_0_0>> + 304
frame #1: 0x00007fff5b5e1a5d SwiftUI`protocol witness for SwiftUI.ListCoreDataSource.contextForItem(index: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index)) -> SwiftUI._RowVisitationContext<τ_0_0> in conformance SwiftUI.SystemListDataSource<τ_0_0> : SwiftUI.ListCoreDataSource in SwiftUI + 15
frame #2: 0x00007fff5b783db6 SwiftUI`SwiftUI.ShadowListDataSource.contextForItem(index: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index)) -> SwiftUI._RowVisitationContext<SwiftUI.ShadowListDataSource<τ_0_0>> + 752
frame #3: 0x00007fff5b783f0e SwiftUI`protocol witness for SwiftUI.ListCoreDataSource.contextForItem(index: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index)) -> SwiftUI._RowVisitationContext<τ_0_0> in conformance SwiftUI.ShadowListDataSource<τ_0_0> : SwiftUI.ListCoreDataSource in SwiftUI + 9
frame #4: 0x00007fff5b91910b SwiftUI`SwiftUI.ListCoreDataSource.visitRowAt<τ_0_0>(_: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index), visitor: (SwiftUI._RowVisitationContext<τ_0_0>) -> τ_1_0) -> τ_1_0 + 506
frame #5: 0x00007fff5b917e73 SwiftUI`SwiftUI.ListCoreDataSource.visitContent<τ_0_0>(atRow: Foundation.IndexPath, visitor: (SwiftUI._RowVisitationContext<τ_0_0>) -> τ_1_0) -> τ_1_0 + 344
frame #6: 0x00007fff5bb0d053 SwiftUI`SwiftUI.UITableViewListCoordinator.tableView(_: __C.UITableView, canEditRowAt: Foundation.IndexPath) -> Swift.Bool + 785
frame #7: 0x00007fff5bb114da SwiftUI`merged @objc SwiftUI.UITableViewListCoordinator.tableView(_: __C.UITableView, canEditRowAt: Foundation.IndexPath) -> Swift.Bool + 122
frame #8: 0x00007fff2516143d UIKitCore`-[UITableView _canEditRowAtIndexPath:] + 96
frame #9: 0x00007fff2518fbbb UIKitCore`-[UITableView _setupCell:forEditing:atIndexPath:animated:updateSeparators:] + 139
frame #10: 0x00007fff2517b6e2 UIKitCore`-[UITableView _setEditing:animated:forced:] + 1415
frame #11: 0x00007fff2517b125 UIKitCore`-[UITableView willMoveToSuperview:] + 126
frame #12: 0x00007fff254c81b4 UIKitCore`__UIViewWillBeRemovedFromSuperview + 548
frame #13: 0x00007fff254c7e4c UIKitCore`-[UIView(Hierarchy) removeFromSuperview] + 92
frame #14: 0x00007fff254481e4 UIKitCore`-[UIScrollView removeFromSuperview] + 62
frame #15: 0x00007fff254af574 UIKitCore`-[UIView dealloc] + 435
frame #16: 0x00007fff202f45f7 CoreFoundation`__RELEASE_OBJECTS_IN_THE_ARRAY__ + 115
frame #17: 0x00007fff202f453d CoreFoundation`-[__NSArrayM dealloc] + 275
frame #18: 0x00007fff2019a9f7 libobjc.A.dylib`objc_object::sidetable_release(bool, bool) + 177
frame #19: 0x00007fff2019c175 libobjc.A.dylib`AutoreleasePoolPage::releaseUntil(objc_object**) + 175
frame #20: 0x00007fff2019c064 libobjc.A.dylib`objc_autoreleasePoolPop + 185
frame #21: 0x00007fff2877283a QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 712
frame #22: 0x00007fff287aa007 QuartzCore`CA::Transaction::commit() + 699
frame #23: 0x00007fff287ab324 QuartzCore`CA::Transaction::flush_as_runloop_observer(bool) + 60
frame #24: 0x00007fff20362a0d CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #25: 0x00007fff2035d242 CoreFoundation`__CFRunLoopDoObservers + 541
frame #26: 0x00007fff2035d7f2 CoreFoundation`__CFRunLoopRun + 1126
frame #27: 0x00007fff2035cea9 CoreFoundation`CFRunLoopRunSpecific + 567
frame #28: 0x00007fff2c951cd3 GraphicsServices`GSEventRunModal + 139
frame #29: 0x00007fff24f2f72a UIKitCore`-[UIApplication _run] + 915
frame #30: 0x00007fff24f34192 UIKitCore`UIApplicationMain + 101
frame #31: 0x00007fff5b96a40f SwiftUI`closure #1 (Swift.UnsafeMutablePointer<Swift.Optional<Swift.UnsafeMutablePointer<Swift.Int8>>>) -> Swift.Never in SwiftUI.KitRendererCommon(Swift.AnyObject.Type) -> Swift.Never + 196
frame #32: 0x00007fff5b96a349 SwiftUI`SwiftUI.runApp<τ_0_0 where τ_0_0: SwiftUI.App>(τ_0_0) -> Swift.Never + 148
frame #33: 0x00007fff5b3b06c1 SwiftUI`static SwiftUI.App.main() -> () + 61
* frame #34: 0x0000000104fdba0e FMP Dokumente`static FMP_DokumenteApp.$main(self=FMP_Dokumente.FMP_DokumenteApp) at FMP_DokumenteApp.swift:10:1
frame #35: 0x0000000104fdbab9 FMP Dokumente`main at FMP_DokumenteApp.swift:0
frame #36: 0x0000000105c0ce1e dyld_sim`start_sim + 10
frame #37: 0x000000010b04a4d5 dyld`start + 421
我在原始 post 中链接的问题中引用的解决方案 matt 结果证明工作正常 - 只是不在模拟器中。它是:
extension FileManager {
public func removeItem(at url: URL, completion: @escaping (Bool, Error?) -> ()) {
DispatchQueue.global(qos: .utility).async {
do {
try self.removeItem(at: url)
} catch {
DispatchQueue.main.async {
completion(false, error)
}
}
DispatchQueue.main.async {
completion(true, nil)
}
}
}
}
...然后像这样使用:
FileManager.default.removeItem(at: fileURL) { success, error in
updateURLs()
}
即使使用此代码,该应用程序在我的 iOS 15.0 beta 模拟器中崩溃,但在我的 iPhone 和 iOS 15.0 beta 中不会崩溃。
我的用户能够将文件本地存储在 Documents/
文件夹中。稍后,他们可以从列表中删除这些文件。我这样删除文件:
List {
ForEach(urls, id: \.self) { url in
Text(url.lastPathComponent)
}
.onDelete { row in
guard let i = row.first, let fileURL = urls[safe: i] else { return }
do {
try FileManager.default.removeItem(at: fileURL)
updateURLs() // → crashes here
}
catch { print(error.localizedDescription) }
}
}
// ...
func updateURLs() {
urls = FileManager.default.urls(
in: FileManager.default.documentsDirectory
.appendingPathComponent("unuploaded-documents")
)
}
// extension of FileManager:
extension FileManager {
public var documentsDirectory: URL {
FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first ?? URL(fileURLWithPath: "")
}
public func urls(in directory: URL) -> [URL] {
var fileURLs: [URL] = []
if let enumerator = FileManager.default.enumerator(at: directory, includingPropertiesForKeys: [.isRegularFileKey], options: [.skipsHiddenFiles, .skipsPackageDescendants]) {
for case let fileURL as URL in enumerator {
do {
let fileAttributes = try fileURL.resourceValues(forKeys: [.isRegularFileKey])
if fileAttributes.isRegularFile ?? false {
fileURLs.append(fileURL)
}
} catch { print(error, fileURL) }
}
}
return fileURLs
}
}
问题
文件删除后,我需要更新文件列表(urls)。但是,我的应用程序在 updateURLs()
调用时崩溃,只提供此错误:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
解决方法
我认为这是因为 urls 在文件被完全删除之前更新了。然后,访问 url 文件现在不再存在;因此应用程序崩溃。
当像这样延迟包装 updateURLs()
调用时:
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
updateURLs()
}
...它不会崩溃并且我的列表会正确更新。然而,这并不是真正的解决方案,因为
- 它会导致糟糕的用户体验——文件被快速删除时不必要的长时间等待
- 如果文件删除时间超过1s,会再次崩溃
研究
从 .removeItem(:)
方法 应该是 同步的,而不是异步的。但是,我使用延迟的解决方法让我猜测不是这样。
问题
我如何知道文件实际删除的时间,以便我可以在正确的时间更新我的列表? .removeItem(:)
方法似乎没有完成处理程序...
崩溃的完整堆栈跟踪:
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
frame #0: 0x00007fff5b5e19f3 SwiftUI`SwiftUI.SystemListDataSource.contextForItem(index: (Swift.Int, Swift.Int)) -> SwiftUI._RowVisitationContext<SwiftUI.SystemListDataSource<τ_0_0>> + 304
frame #1: 0x00007fff5b5e1a5d SwiftUI`protocol witness for SwiftUI.ListCoreDataSource.contextForItem(index: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index)) -> SwiftUI._RowVisitationContext<τ_0_0> in conformance SwiftUI.SystemListDataSource<τ_0_0> : SwiftUI.ListCoreDataSource in SwiftUI + 15
frame #2: 0x00007fff5b783db6 SwiftUI`SwiftUI.ShadowListDataSource.contextForItem(index: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index)) -> SwiftUI._RowVisitationContext<SwiftUI.ShadowListDataSource<τ_0_0>> + 752
frame #3: 0x00007fff5b783f0e SwiftUI`protocol witness for SwiftUI.ListCoreDataSource.contextForItem(index: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index)) -> SwiftUI._RowVisitationContext<τ_0_0> in conformance SwiftUI.ShadowListDataSource<τ_0_0> : SwiftUI.ListCoreDataSource in SwiftUI + 9
frame #4: 0x00007fff5b91910b SwiftUI`SwiftUI.ListCoreDataSource.visitRowAt<τ_0_0>(_: (τ_0_0.SectionIDs.Index, τ_0_0.RowIDs.Index), visitor: (SwiftUI._RowVisitationContext<τ_0_0>) -> τ_1_0) -> τ_1_0 + 506
frame #5: 0x00007fff5b917e73 SwiftUI`SwiftUI.ListCoreDataSource.visitContent<τ_0_0>(atRow: Foundation.IndexPath, visitor: (SwiftUI._RowVisitationContext<τ_0_0>) -> τ_1_0) -> τ_1_0 + 344
frame #6: 0x00007fff5bb0d053 SwiftUI`SwiftUI.UITableViewListCoordinator.tableView(_: __C.UITableView, canEditRowAt: Foundation.IndexPath) -> Swift.Bool + 785
frame #7: 0x00007fff5bb114da SwiftUI`merged @objc SwiftUI.UITableViewListCoordinator.tableView(_: __C.UITableView, canEditRowAt: Foundation.IndexPath) -> Swift.Bool + 122
frame #8: 0x00007fff2516143d UIKitCore`-[UITableView _canEditRowAtIndexPath:] + 96
frame #9: 0x00007fff2518fbbb UIKitCore`-[UITableView _setupCell:forEditing:atIndexPath:animated:updateSeparators:] + 139
frame #10: 0x00007fff2517b6e2 UIKitCore`-[UITableView _setEditing:animated:forced:] + 1415
frame #11: 0x00007fff2517b125 UIKitCore`-[UITableView willMoveToSuperview:] + 126
frame #12: 0x00007fff254c81b4 UIKitCore`__UIViewWillBeRemovedFromSuperview + 548
frame #13: 0x00007fff254c7e4c UIKitCore`-[UIView(Hierarchy) removeFromSuperview] + 92
frame #14: 0x00007fff254481e4 UIKitCore`-[UIScrollView removeFromSuperview] + 62
frame #15: 0x00007fff254af574 UIKitCore`-[UIView dealloc] + 435
frame #16: 0x00007fff202f45f7 CoreFoundation`__RELEASE_OBJECTS_IN_THE_ARRAY__ + 115
frame #17: 0x00007fff202f453d CoreFoundation`-[__NSArrayM dealloc] + 275
frame #18: 0x00007fff2019a9f7 libobjc.A.dylib`objc_object::sidetable_release(bool, bool) + 177
frame #19: 0x00007fff2019c175 libobjc.A.dylib`AutoreleasePoolPage::releaseUntil(objc_object**) + 175
frame #20: 0x00007fff2019c064 libobjc.A.dylib`objc_autoreleasePoolPop + 185
frame #21: 0x00007fff2877283a QuartzCore`CA::Context::commit_transaction(CA::Transaction*, double, double*) + 712
frame #22: 0x00007fff287aa007 QuartzCore`CA::Transaction::commit() + 699
frame #23: 0x00007fff287ab324 QuartzCore`CA::Transaction::flush_as_runloop_observer(bool) + 60
frame #24: 0x00007fff20362a0d CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
frame #25: 0x00007fff2035d242 CoreFoundation`__CFRunLoopDoObservers + 541
frame #26: 0x00007fff2035d7f2 CoreFoundation`__CFRunLoopRun + 1126
frame #27: 0x00007fff2035cea9 CoreFoundation`CFRunLoopRunSpecific + 567
frame #28: 0x00007fff2c951cd3 GraphicsServices`GSEventRunModal + 139
frame #29: 0x00007fff24f2f72a UIKitCore`-[UIApplication _run] + 915
frame #30: 0x00007fff24f34192 UIKitCore`UIApplicationMain + 101
frame #31: 0x00007fff5b96a40f SwiftUI`closure #1 (Swift.UnsafeMutablePointer<Swift.Optional<Swift.UnsafeMutablePointer<Swift.Int8>>>) -> Swift.Never in SwiftUI.KitRendererCommon(Swift.AnyObject.Type) -> Swift.Never + 196
frame #32: 0x00007fff5b96a349 SwiftUI`SwiftUI.runApp<τ_0_0 where τ_0_0: SwiftUI.App>(τ_0_0) -> Swift.Never + 148
frame #33: 0x00007fff5b3b06c1 SwiftUI`static SwiftUI.App.main() -> () + 61
* frame #34: 0x0000000104fdba0e FMP Dokumente`static FMP_DokumenteApp.$main(self=FMP_Dokumente.FMP_DokumenteApp) at FMP_DokumenteApp.swift:10:1
frame #35: 0x0000000104fdbab9 FMP Dokumente`main at FMP_DokumenteApp.swift:0
frame #36: 0x0000000105c0ce1e dyld_sim`start_sim + 10
frame #37: 0x000000010b04a4d5 dyld`start + 421
我在原始 post 中链接的问题中引用的解决方案 matt 结果证明工作正常 - 只是不在模拟器中。它是:
extension FileManager {
public func removeItem(at url: URL, completion: @escaping (Bool, Error?) -> ()) {
DispatchQueue.global(qos: .utility).async {
do {
try self.removeItem(at: url)
} catch {
DispatchQueue.main.async {
completion(false, error)
}
}
DispatchQueue.main.async {
completion(true, nil)
}
}
}
}
...然后像这样使用:
FileManager.default.removeItem(at: fileURL) { success, error in
updateURLs()
}
即使使用此代码,该应用程序在我的 iOS 15.0 beta 模拟器中崩溃,但在我的 iPhone 和 iOS 15.0 beta 中不会崩溃。