iOS-Swift 如何唯一标识应用存入系统相册的照片/视频以避免重复存储
判断已下载资源是否存入系统相册的实现方案
你要判断指定的图片/视频是否已经存入系统相册,没有系统自带的直接关联源URL的标识,需要自己实现关联校验,推荐两种可行方案:
方案1:本地关联唯一标识(性能最优、推荐)
- 每个聊天附件资源在服务端都会有全局唯一的file_id,或者你可以提前计算待存储资源的哈希值(SHA256/MD5都可以)作为唯一标识
- 每次存储资源成功后,把当前资源的唯一标识和本次存储生成的
PHAsset的localIdentifier做关联,存入本地存储(可以用UserDefaults、Core Data或者本地沙盒plist都可以) - 自动下载逻辑触发时,先拿当前待存资源的唯一标识去本地查询,如果已经有对应的
localIdentifier,说明该资源已经存入过相册,直接跳过存储逻辑即可
注意:
PHAsset.localIdentifier是系统为相册内每个资源分配的永久唯一标识,只要资源没有被用户手动删除,即使调整相册分类、系统重启,该标识都不会发生变化,关联可靠性很高。
方案2:直接校验相册资源(适合不想维护本地存储的场景)
如果不想维护本地关联记录,可以在存储前直接遍历相册资源做匹配校验,步骤如下:
- 提前计算好待存储资源的哈希值,同时记录资源的元数据(图片分辨率、视频时长、文件大小)
- 用
PHFetchOptions筛选相册内和待存资源同类型、元数据匹配的资源,缩小遍历范围 - 拉取筛选后的PHAsset的原始数据计算哈希,和待存资源的哈希做对比,一致则说明已经存储过
以下是校验逻辑的参考代码:
/// 校验目标资源是否已经存在于相册 /// - Parameters: /// - targetHash: 待存资源预先计算的SHA256哈希值 /// - isVideo: 资源是否为视频 /// - metaData: 资源元数据(分辨率、时长等,用于快速过滤) /// - Returns: 是否存在 func checkAssetExist(targetHash: String, isVideo: Bool, metaData: [String: Any]) -> Bool { let fetchOptions = PHFetchOptions() // 先按媒体类型筛选 let mediaType = isVideo ? PHAssetMediaType.video.rawValue : PHAssetMediaType.image.rawValue fetchOptions.predicate = NSPredicate(format: "mediaType == %d", mediaType) let fetchResult = PHAsset.fetchAssets(with: fetchOptions) var isExist = false let semaphore = DispatchSemaphore(value: 0) fetchResult.enumerateObjects { asset, _, stop in // 先对比元数据快速排除不匹配的资源,避免不必要的哈希计算 if let pixelWidth = metaData["pixelWidth"] as? Int, asset.pixelWidth != pixelWidth { return } if let pixelHeight = metaData["pixelHeight"] as? Int, asset.pixelHeight != pixelHeight { return } if isVideo, let duration = metaData["duration"] as? Double, abs(asset.duration - duration) > 0.1 { return } // 元数据匹配后再计算哈希对比 let assetManager = PHImageManager.default() if isVideo { assetManager.requestAVAsset(forVideo: asset, options: nil) { avAsset, _, _ in defer { semaphore.signal() } guard let urlAsset = avAsset as? AVURLAsset, let fileHash = calculateFileHash(url: urlAsset.url) else { return } if fileHash == targetHash { isExist = true stop.pointee = true } } semaphore.wait() } else { assetManager.requestImageDataAndOrientation(for: asset, options: nil) { data, _, _, _ in defer { semaphore.signal() } guard let data = data, let dataHash = data.sha256().toHexString() else { return } if dataHash == targetHash { isExist = true stop.pointee = true } } semaphore.wait() } } return isExist }
注意:该方案会有一定的性能消耗,相册内资源越多遍历时间越长,推荐和方案1结合使用:优先查本地关联记录,不存在再走相册校验逻辑,既保证性能又能处理用户手动删除已存资源的异常场景。
常见问题补充
系统自带的PHAsset不会存储你下载资源用的源URL,所以不存在直接和URL对应的系统级标识符,必须自己做关联或者哈希校验。
内容的提问来源于stack exchange,提问作者Mrugesh Tank
相关产品推荐
相关产品推荐

