Kingfisher Live Photo 加载与缓存实战:从 PHAsset 数据提取到 PHLivePhotoView 展示
Kingfisher Live Photo 加载与缓存实战从 PHAsset 数据提取到 PHLivePhotoView 展示【免费下载链接】KingfisherA lightweight, pure-Swift library for downloading and caching images from the web.项目地址: https://gitcode.com/GitHub_Trending/ki/Kingfisher本文基于 Kingfisher 官方文档 Topic_LivePhoto.md结合仓库源码深入讲解 Live Photo实况照片的完整加载链路如何从 Photos 框架提取数据并托管到服务器、如何用 Kingfisher 下载与缓存静态图 配对视频两份资源、最终如何渲染到PHLivePhotoView。读完本文你将掌握LivePhotoSource、LivePhotoResource、kf.setImage(with:)等 API 的用法以及磁盘缓存、文件扩展名推断、选项限制等底层实现细节可直接在 iOS 应用中落地 Live Photo 网络加载能力。Live Photo 由一张静态图片通常为 HEIC和一段配对视频通常为 MOV组成。Kingfisher 在普通图片下载缓存能力之上为PHLivePhotoView提供了专门的扩展将这两份资源统一纳入其下载与缓存体系。核心能力位于 PHLivePhotoViewKingfisher.swiftUI 扩展层与 KingfisherManagerLivePhoto.swift下载与缓存编排层。注意该能力面向 iOS/tvOS 平台依赖PhotosUI与Photos框架在 watchOS 上仅保留了占位类型参见源码中的#if os(watchOS)分支。一、Live Photo 数据准备从 PHAsset 提取资源并托管在网络加载之前需要先把 Live Photo 的原始数据准备好。通常的数据来源是 Photos 框架中的PHAsset先用PHAssetResource.assetResources(for:)拿到该 asset 的全部资源再通过PHAssetResourceManager.default().requestData(for:options:completionHandler:)逐份读取二进制数据最后上传到自己的服务器或 CDN。原文档给出的提取示例let asset: PHAsset // ... your PHAsset if !asset.mediaSubtypes.contains(.photoLive) { print(Not a live photo) return } let resources PHAssetResource.assetResources(for: asset) var allData [Data]() let group DispatchGroup() group.notify(queue: .main) { allData.forEach { data in // Upload data to your server serverRequest.upload(data) } } resources.forEach { resource in group.enter() var data Data() PHAssetResourceManager.default().requestData(for: resource, options: nil) { chunk in data.append(chunk) } completionHandler: { error in defer { group.leave() } if let error error { print(Error: \(error)) return } allData.append(data) } }数据准备阶段的要点先判断是否为 Live Photo用asset.mediaSubtypes.contains(.photoLive)过滤避免对普通照片执行无意义的提取。识别资源类型通过PHAssetResource.type可以区分每个资源。一个最小可用的 Live Photo 通常只需要两种资源类型为.photo的静态图以及类型为.pairedVideo的配对视频。不要改动元数据与数据本身PHLivePhoto对输入数据的完整性非常敏感。若在上传或中间处理环节修改了资源的元数据例如重新编码图片、改写 EXIF后续 Kingfisher 加载时PHLivePhoto可能无法再识别这些数据。这一点在 LivePhotoSource.swift 的类型注释中也有明确警告。URL 中尽量保留文件扩展名托管时建议让 URL 以.heic静态图和.mov视频结尾。虽然不是强制要求但准确的扩展名能让 Kingfisher 更可靠地识别文件类型后续会详细说明其内部推断逻辑。保留原始扩展名可通过PHAssetResource.originalFilename读取并保留原始文件扩展名避免上传时丢失。二、核心类型LivePhotoSource 与 LivePhotoResource理解了数据来源后再看 Kingfisher 如何描述一份 Live Photo。源码 LivePhotoSource.swift 定义了三个核心类型LivePhotoSource一份 Live Photo 的完整来源内部是resources: [LivePhotoResource]数组典型情况下恰好包含两份资源——静态图与视频。它提供多种初始化方式init(urls: [URL])直接传入两个 URL最常用init(resources: [any Resource])传入实现了Resource协议的对象init(_ resources: [LivePhotoResource])直接传入LivePhotoResource数组。LivePhotoResourceLive Photo 的单个组成资源包含三个关键属性dataSource: Source数据来源可以是.network(_:)网络下载或.provider(_:)由ImageDataProvider提供referenceFileType: FileType文件类型枚举为.heic、.mov或.other(String)内部还有cacheKey默认为 URL 的absoluteString与downloadURL。LivePhotoResource.FileType用于标注资源类型。它决定了两件事——文件以什么扩展名写入磁盘以及后续能否被PHLivePhoto正确识别。FileType的推断有三层策略源码中清晰可见显式指定优先通过init(downloadURL:cacheKey:fileType:)或init(resource:fileType:)显式传入fileType时直接采用从 URL 扩展名推断未指定时Resource.guessedFileType会读取 URL 的pathExtensionmov→.movheic→.heic其他则落入.other(ext)从文件签名推断当扩展名缺失.other()时determinedFileExtension(_:)会检查数据前 12 字节先找ftypbox第 48 字节再比对第 812 字节是heicHEIF还是qtQuickTime/MOV以此确定扩展名。相关的单元测试覆盖在 LivePhotoSourceTests.swift 中如testLivePhotoResourceFileTypeDeterminationForHEIC、testLivePhotoResourceFileTypeDeterminationForQT等。为什么扩展名这么重要源码注释指出PHLivePhoto的 request 方法要求磁盘上文件扩展名必须正确否则会抛出 PHPhotosError 3302invalidResource。因此 Kingfisher 在写盘时会用forcedExtension强制使用正确扩展名存储。三、加载 Live Photo三步接入 PHLivePhotoView数据托管好后客户端加载非常简单原文档给出了三步流程。第 1 步导入框架并创建 PHLivePhotoViewimport Kingfisher import PhotosUI let livePhotoView PHLivePhotoView(frame: CGRect(x: 0, y: 0, width: 300, height: 300)) view.addSubview(livePhotoView)PHLivePhotoView是 PhotosUI 提供的展示控件支持长按预览动态效果。官方 Demo LivePhotoViewController.swift 中同样使用 Auto Layout 布局了一个 300×300 的PHLivePhotoView并直接加载了仓库作者维护的测试样例资源live_photo_sample.HEIC与live_photo_sample.MOV是可直接运行验证的完整参考实现。第 2 步准备两个 URLlet imageURL URL(string: https://example.com/image.heic)! let videoURL URL(string: https://example.com/video.mov)! let urls [imageURL, videoURL]两个 URL 分别指向静态图与视频顺序与内容一一对应。第 3 步调用 kf.setImage 加载livePhotoView.kf.setImage(with: urls) { result in switch result { case .success(let retrieveResult): print(Live photo loaded: \(retrieveResult.livePhoto)) print(Cache type: \(retrieveResult.loadingInfo.cacheType)) case .failure(let error): print(Error: \(error)) } }setImage(with:options:completionHandler:)的定义与上述示例可在 PHLivePhotoViewKingfisher.swift 中看到其内部会把[URL]包装为LivePhotoSource(urls:)再委托给KingfisherManager.shared.retrieveLivePhoto(with:options:progressBlock:referenceTaskIdentifierChecker:)执行实际下载与缓存。更精细的控制直接使用 LivePhotoSource如果需要对单个资源做更精细的控制例如自定义 cacheKey 或显式指定文件类型可以改用setImage(with: source:)传入手工构建的LivePhotoSourcelet source LivePhotoSource(urls: [ URL(string: https://example.com/image.heic)!, // imageURL URL(string: https://example.com/video.mov)! // videoURL ]) livePhotoView.kf.setImage(with: source) { result in // 处理结果同前 }也可以构造带显式类型的资源let imageResource LivePhotoResource( downloadURL: URL(string: https://example.com/photo)!, cacheKey: user-123-photo, fileType: .heic ) let videoResource LivePhotoResource( downloadURL: URL(string: https://example.com/video)!, cacheKey: user-123-video, fileType: .mov ) let source LivePhotoSource([imageResource, videoResource]) livePhotoView.kf.setImage(with: source)四、加载结果与缓存机制为什么 Live Photo 只走磁盘缓存retrieveLivePhoto返回的LivePhotoLoadingInfoResult定义于 KingfisherManagerLivePhoto.swift包含fileURLs: [URL]两份资源落盘后的磁盘 URL供PHLivePhoto.request(withResourceFileURLs:...)使用cacheType: CacheType命中缓存时为.disk网络新鲜下载时为.nonesource/originalSource本次加载的来源信息data: () - [Data]可延时取出原始二进制数据的闭包注意此操作可能耗时若需多次使用建议先保存结果。setImage拿到fileURLs后会调用系统 APIPHLivePhoto.request(withResourceFileURLs:placeholderImage:targetSize:contentMode:resultHandler:)生成PHLivePhoto最终赋给livePhotoView.livePhoto同时把结果封装为RetrieveLivePhotoResult包含loadingInfo、livePhoto、info回调给调用方。缓存层面的两个关键设计Live Photo 只使用磁盘缓存不使用内存缓存。原因在源码注释中写得很明确Live Photo 包含视频数据体积过大放进内存缓存代价太高。因此cacheType只可能出现.disk或.none两种值。下载与写盘按资源并发进行。downloadAndCache(resources:options:)使用withThrowingTaskGroup同时下载缺失的各个资源然后通过cache.storeToDisk(_:forKey:processorIdentifier:forcedExtension:expiration:)以正确扩展名写入磁盘并遵守diskCacheExpiration的过期策略。正因为 Live Photo 的加载高度依赖磁盘缓存PHLivePhoto.request要求资源必须存在于本地文件原文档特别提示加载期间数据必须至少存在于磁盘上。如果不希望 Live Photo 数据长期占用磁盘可以用选项.diskCacheExpiration(.seconds(10))设置较短的过期时间或在用完后手动清理磁盘缓存。五、选项限制与边界情况Important Notes原文档的 Notes 部分指出了若干关键限制结合源码可以进一步明确其机制URL 必须有效可访问网络请求失败会以KingfisherError的形式返回。加载耗时较长尤其首次加载需要下载两份资源含视频耗时高于普通图片建议配合合理的加载体验设计。不支持自定义处理器ProcessorretrieveLivePhoto内部会做选项检查——如果用户传入了非默认处理器源码会触发assertionFailure([Kingfisher] Using of custom processors during loading of live photo resource is not supported.)并静默回退为内部的LivePhotoImageProcessor.default其定义见 ImageProcessor.swift该处理器仅透传数据、不做任何像素处理本身不对外开放。placeholder 与 progressBlock 暂不支持setImage(with:)的签名中这两项被注释为 Not supported yet会在未来版本实现。源码同时以 TODO 注释标明 retry 与进度回调目前也被忽略以控制复杂度。targetSize与contentMode可自定义PHLivePhotoView扩展暴露了这两个关联属性默认分别为.zero与.default它们会被透传给系统PHLivePhoto.request的对应参数用于控制生成的PHLivePhoto的目标尺寸与内容模式见 PHLivePhotoViewKingfisher.swift。强制刷新传入.forceRefresh选项时missingResources会跳过缓存检查、强制重新下载全部资源见 KingfisherManagerLivePhoto.swift 的missingResources(_:options:)实现。错误分类与错误码KingfisherError为 Live Photo 定义了专门的错误分支见 KingfisherError.swift可据此做精细的错误处理错误类型错误码触发场景requestError(.livePhotoTaskCancelled)1004Live Photo 任务被取消如视图复用导致旧任务作废cacheError(.missingLivePhotoResourceOnDisk)3012下载完成后磁盘上找不到预期缓存文件异常兜底imageSettingError(.notCurrentLivePhotoSourceTask)5005结果返回时任务标识已过期非当前请求imageSettingError(.livePhotoResultError)5006PHLivePhoto.request的 resultHandler 中携带PHLivePhotoInfoErrorKey错误其中任务取消的兜底很典型setImage内部为每次加载签发递增的Source.Identifier并在回调前用checkNotCurrentTask校验标识是否仍是当前任务——这保证了在列表快速滚动、视图被复用时过期请求的结果不会污染当前视图。六、结论至此一条完整的 Live Photo 网络加载链路已经清晰PHAsset 提取数据并托管保留 .heic/.mov 扩展名→ 构建LivePhotoSource两个 URL 或精细化的LivePhotoResource→livePhotoView.kf.setImage(with:)下载并写入磁盘缓存 → 系统PHLivePhoto.request组装为PHLivePhoto→ 渲染到PHLivePhotoView。得益于 Kingfisher 统一的下载、缓存与错误体系Live Photo 加载可以像普通图片一样获得磁盘缓存加速同时通过RetrieveLivePhotoResult.loadingInfo.cacheType可以判断资源来自网络还是缓存。建议在实际工程中对照官方 Demo LivePhotoViewController.swift 与测试用例 LivePhotoSourceTests.swift 验证 API 行为涉及文件类型推断、任务取消、磁盘缓存过期等边界时可回到 PHLivePhotoViewKingfisher.swift、KingfisherManagerLivePhoto.swift 与 LivePhotoSource.swift 中确认实现细节。【免费下载链接】KingfisherA lightweight, pure-Swift library for downloading and caching images from the web.项目地址: https://gitcode.com/GitHub_Trending/ki/Kingfisher创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考