You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

iOS-Swift 如何唯一标识应用存入系统相册的照片/视频以避免重复存储

判断已下载资源是否存入系统相册的实现方案

你要判断指定的图片/视频是否已经存入系统相册,没有系统自带的直接关联源URL的标识,需要自己实现关联校验,推荐两种可行方案:

方案1:本地关联唯一标识(性能最优、推荐)

  • 每个聊天附件资源在服务端都会有全局唯一的file_id,或者你可以提前计算待存储资源的哈希值(SHA256/MD5都可以)作为唯一标识
  • 每次存储资源成功后,把当前资源的唯一标识和本次存储生成的PHAsset的localIdentifier做关联,存入本地存储(可以用UserDefaults、Core Data或者本地沙盒plist都可以)
  • 自动下载逻辑触发时,先拿当前待存资源的唯一标识去本地查询,如果已经有对应的localIdentifier,说明该资源已经存入过相册,直接跳过存储逻辑即可

注意:PHAsset.localIdentifier是系统为相册内每个资源分配的永久唯一标识,只要资源没有被用户手动删除,即使调整相册分类、系统重启,该标识都不会发生变化,关联可靠性很高。

方案2:直接校验相册资源(适合不想维护本地存储的场景)

如果不想维护本地关联记录,可以在存储前直接遍历相册资源做匹配校验,步骤如下:

  1. 提前计算好待存储资源的哈希值,同时记录资源的元数据(图片分辨率、视频时长、文件大小)
  2. 用PHFetchOptions筛选相册内和待存资源同类型、元数据匹配的资源,缩小遍历范围
  3. 拉取筛选后的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.29 08:48:03