Swift中从PHAsset高效获取EXIF数据的优化方案咨询
快速提取PHAsset的相机信息(EXIF/视频元数据)
问题根源
你当前的方法都存在效率问题:
- 方法1:请求完整图片数据(尤其是RAW文件),云端下载耗时极长
- 方法2:缩略图不包含完整EXIF,无法提取相机信息
- 方法3:
requestContentEditingInput是为编辑场景设计的,操作重,不适合批量读取元数据
优化方案
图片的高效提取
方案1:从请求结果的info字典直接获取元数据
系统在请求图片时,会把元数据放在info字典的PHImageResultMetadataKey中,无需解析图片数据,速度最快:
func fetchCameraInfo(asset: PHAsset, completion: @escaping (Result<String?, Error>) -> Void) { if asset.mediaType == .image { let options = PHImageRequestOptions() options.deliveryMode = .fastFormat options.isNetworkAccessAllowed = true options.version = .current PHCachingImageManager().requestImageDataAndOrientation(for: asset, options: options) { _, _, _, info in // 优先从info字典拿元数据 if let metadata = info?[PHImageResultMetadataKey] as? [String: Any] { let cameraInfo = self.extractCameraDataFromExif(metadata) completion(.success(cameraInfo)) return } // fallback:解析图片数据(仅当info无元数据时) guard let data = info?[PHImageResultImageDataKey] as? Data, let imageSource = CGImageSourceCreateWithData(data as CFData, nil), let metaData = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any] else { completion(.failure(PhotoLibraryError.unknown)) return } let cameraInfo = self.extractCameraDataFromExif(metaData) completion(.success(cameraInfo)) } } // 视频部分见下方 } func extractCameraDataFromExif(_ exif: [String: Any]) -> String? { let make = exif[kCGImagePropertyMake as String] as? String let model = exif[kCGImagePropertyModel as String] as? String return make.map { "\($0) \(model ?? "")" } ?? model }
方案2:用PHAssetResourceManager获取轻量数据解析
针对RAW文件或超大图片,仅请求足够解析元数据的部分数据,避免下载完整文件:
func fetchCameraInfo(asset: PHAsset, completion: @escaping (Result<String?, Error>) -> Void) { if asset.mediaType == .image { guard let resource = PHAssetResource.assetResources(for: asset).first else { completion(.failure(PhotoLibraryError.unknown)) return } let resourceOptions = PHAssetResourceRequestOptions() resourceOptions.isNetworkAccessAllowed = true resourceOptions.deliveryMode = .fastFormat var collectedData = Data() let requestID = PHAssetResourceManager.default().requestData(for: resource, options: resourceOptions) { data in collectedData.append(data) // 尝试解析元数据,一旦成功就停止接收数据 if let imageSource = CGImageSourceCreateWithData(collectedData as CFData, nil), let metaData = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, nil) as? [String: Any] { let cameraInfo = self.extractCameraDataFromExif(metaData) completion(.success(cameraInfo)) // 取消请求,避免下载多余数据 PHAssetResourceManager.default().cancelRequest(with: requestID) } } progressHandler: { _, _ in } } // 视频部分见下方 }
视频的高效提取
直接过滤需要的元数据键,避免遍历所有条目,同时使用fastFormat模式快速获取AVAsset:
func fetchCameraInfo(asset: PHAsset, completion: @escaping (Result<String?, Error>) -> Void) { if asset.mediaType == .video { let options = PHVideoRequestOptions() options.isNetworkAccessAllowed = true options.deliveryMode = .fastFormat options.version = .current PHCachingImageManager().requestAVAsset(forVideo: asset, options: options) { avAsset, _, _ in guard let avAsset else { completion(.failure(PhotoLibraryError.unknown)) return } let cameraMake = avAsset.commonMetadata.first { $0.commonKey?.rawValue == AVMetadataKey.commonKeyMake }?.stringValue let cameraModel = avAsset.commonMetadata.first { $0.commonKey?.rawValue == AVMetadataKey.commonKeyModel }?.stringValue let cameraInfo = cameraMake.map { "\($0) \(cameraModel ?? "")" } ?? cameraModel completion(.success(cameraInfo)) } } }
额外优化建议
- 批量处理用缓存:使用
PHCachingImageManager提前缓存元数据,重复请求直接读取缓存 - 云端照片异步处理:通过
info[PHImageResultIsInCloudKey]判断是否为云端资源,先显示占位符,后台异步获取 - 后台队列执行:把元数据提取操作放到后台DispatchQueue,避免阻塞主线程
内容的提问来源于stack exchange,提问作者Mev
相关产品推荐
相关产品推荐

