macOS File Provider Extension:特定目录刷新失败(仅.workingSet有效)
macOS File Provider扩展:signalEnumerator仅对.workingSet生效的原因与解决方法
原因分析
- 枚举器活跃性限制:
signalEnumerator(for:)仅能向当前处于活跃状态的枚举器发送信号。.workingSet对应的枚举器是系统默认持续保持活跃的(它对应用户近期访问的文件集合),而其他目录的枚举器在完成一次枚举任务后会被系统自动释放,因此无法接收信号。 - 枚举器未实现信号响应:如果自定义的
FileProviderEnumerator没有重写signal()方法,即使枚举器处于活跃状态,也无法处理刷新信号。
解决方法
1. 先激活目标目录的枚举器
在调用signalEnumerator前,主动触发一次目标目录的枚举请求,让系统创建并保留枚举器实例:
func performAction(identifier actionIdentifier: NSFileProviderExtensionActionIdentifier, onItemsWithIdentifiers itemIdentifiers: [NSFileProviderItemIdentifier], completionHandler: @escaping (Error?) -> Void) -> Progress { switch actionIdentifier.rawValue { case "Refresh": guard let targetID = itemIdentifiers.first else { completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSInvalidArgumentError, userInfo: [NSLocalizedDescriptionKey: "No item identifier provided"])) return Progress() } // 先激活目标目录的枚举器 do { let enumerator = try self.enumerator(for: targetID, request: NSFileProviderRequest()) // 发起空枚举请求,让系统保留枚举器 enumerator.enumerateItems(for: NSFileProviderEnumerationRequest(pageSize: 0), startingAt: nil) { _, _, error in if let error = error { Logger.log.info("[ENUM] Failed to activate enumerator: \(String(describing: error), privacy: .public)") } } } catch { Logger.log.info("[ENUM] Failed to get enumerator: \(String(describing: error), privacy: .public)") } // 发送刷新信号 manager.signalEnumerator(for: targetID) { error in if let error = error { Logger.log.info("[SIGNAL] Failed to signal enumerator: \(String(describing: error), privacy: .public)") completionHandler(error) } else { Logger.log.info("[SIGNAL] Signalled Successfully: \(String(describing: targetID), privacy: .public)") completionHandler(nil) } } default: completionHandler(NSError(domain: NSCocoaErrorDomain, code: NSFeatureUnsupportedError, userInfo: [NSLocalizedDescriptionKey: "The action is not supported"])) } return Progress() }
2. 完善自定义枚举器的信号响应
在FileProviderEnumerator中重写signal()方法,收到信号后重新枚举目录内容并通知系统更新:
class FileProviderEnumerator: NSFileProviderEnumerator { private let enumeratedItemIdentifier: NSFileProviderItemIdentifier init(enumeratedItemIdentifier: NSFileProviderItemIdentifier) { self.enumeratedItemIdentifier = enumeratedItemIdentifier super.init() } override func invalidate() { // 清理资源 super.invalidate() } override func signal() { // 收到刷新信号后,重新枚举目录内容 self.enumerateItems(for: NSFileProviderEnumerationRequest(pageSize: 50), startingAt: nil) { items, nextPage, error in guard error == nil else { Logger.log.info("[ENUM] Failed to enumerate items: \(String(describing: error!), privacy: .public)") return } // 通知系统目录内容已更新 NSFileProviderManager.default.signalContentsChanged(for: self.enumeratedItemIdentifier) } } override func enumerateItems(for request: NSFileProviderEnumerationRequest, startingAt page: NSFileProviderPage?, completionHandler: @escaping ([NSFileProviderItem]?, NSFileProviderPage?, Error?) -> Void) { // 实现从远程服务器获取目录内容的逻辑 // ... completionHandler(fetchedItems, nil, nil) } }
3. 辅助方案:结合reloadItem强制刷新
如果signalEnumerator仍不生效,可以先调用reloadItem标记目录需要更新,再触发信号:
// 在signalEnumerator前添加 NSFileProviderManager.default.reloadItem(at: targetID) { error in if let error = error { Logger.log.info("[RELOAD] Failed to reload item: \(String(describing: error), privacy: .public)") } }
注意事项
- 确保目标目录的
NSFileProviderItem完整实现了itemIdentifier、parentItemIdentifier等核心属性,系统依赖这些信息定位枚举器。 - 避免频繁激活枚举器,系统对活跃枚举器的数量有上限,超出后会自动回收旧实例。
内容的提问来源于stack exchange,提问作者Swift User
相关产品推荐
相关产品推荐

