使用WCSession.transferFile从WatchOS传文件至iOS时didFinish回调丢失
问题场景
使用WCSession.transferFile(_:metadata:)从watchOS 10.5向iOS 17.5.1传输文件时,WCSessionDelegate的didFinish代理方法完全不触发。调试控制台输出错误:
[WCFileStorage persistOutgoingFileTransfer:] error serializing file transfer <WCSessionFileTransfer: 0x300155d60, session file: <WCSessionFile: 0x3001575c0, identifier: 0C8857EC-7D74-4E78-BA28-6C5526DE8949, file: FILE.EXT, hasMetadata: YES>, transferring: YES> due to Error Domain=NSCocoaErrorDomain Code=4866 "Caught exception during archival: This object may only be encoded by an NSXPCCoder. (...)"
该错误未转发至代理,且WCSession.outstandingFileTransfers显示所有任务一直处于排队状态。这是watchOS/iOS新版本的已知Bug,且模拟器不支持transferFile(_:metadata:)无法充分测试。
临时解决方案
1. 拆分metadata与文件传输,用transferUserInfo传递额外信息
放弃在transferFile中携带metadata,转而通过transferUserInfo(_:)单独传递文件的alternateId和type,在iOS端将用户信息与后续接收的文件关联:
// watchOS端:先传递用户信息 let fileUUID = UUID().uuidString session.transferUserInfo(["alternateId": file.alternateId, "type": file.type, "fileUUID": fileUUID]) // 再传输文件(metadata传nil) fileTransfer = session.transferFile(file.url, metadata: nil)
iOS端暂存用户信息,在接收文件时通过UUID关联:
var pendingFileInfos = [String: [String: Any]]() func session(_ session: WCSession, didReceiveUserInfo userInfo: [String : Any] = [:]) { guard let fileUUID = userInfo["fileUUID"] as? String else { return } pendingFileInfos[fileUUID] = userInfo } func session(_ session: WCSession, didReceive file: WCSessionFile) { if let info = pendingFileInfos.removeValue(forKey: file.fileIdentifier) { let alternateId = info["alternateId"] let type = info["type"] // 处理文件与关联信息 } // 文件存储等逻辑 }
2. 将metadata序列化为JSON字符串传入
把原本的metadata字典序列化为JSON字符串,作为单一值传入transferFile的metadata参数,iOS端接收后反序列化:
// watchOS端:序列化metadata do { let metaDict = ["alternateId": file.alternateId, "type": file.type] let jsonData = try JSONSerialization.data(withJSONObject: metaDict) let jsonStr = String(data: jsonData, encoding: .utf8) fileTransfer = session.transferFile(file.url, metadata: ["meta": jsonStr]) } catch { // 处理序列化错误 }
iOS端反序列化解析:
func session(_ session: WCSession, didReceive file: WCSessionFile) { guard let jsonStr = file.metadata?["meta"] as? String, let jsonData = jsonStr.data(using: .utf8), let metaDict = try? JSONSerialization.jsonObject(with: jsonData) as? [String: Any] else { return } let alternateId = metaDict["alternateId"] let type = metaDict["type"] // 处理文件与元数据 }
3. 定期轮询任务状态,重试超时任务
由于代理不返回错误,可定期检查outstandingFileTransfers,对超时排队的任务取消并重试:
// watchOS端设置定时器,每30秒检查一次 Timer.scheduledTimer(withTimeInterval: 30, repeats: true) { [weak self] timer in guard let self = self else { timer.invalidate(); return } let staleTransfers = self.session.outstandingFileTransfers.filter { // 判断任务是否超时(示例:超过5分钟) Date().timeIntervalSince($0.timestamp) > 300 } for transfer in staleTransfers { transfer.cancel() // 结合方案1或2重新发起传输 self.retryTransfer(for: transfer.file) } }
注意事项
- 方案1和2需保证信息与文件的关联准确性,优先用文件的唯一标识符匹配
- 方案3的轮询间隔需根据实际场景调整,避免过度消耗设备电量
内容的提问来源于stack exchange,提问作者cristallo

