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

Swift:如何等待requestAVAsset完成后再添加至数组

解决PHAsset视频加载异步导致数据为空的问题

你遇到的核心问题是:requestAVAsset是异步回调模式,而你的函数是async函数,回调还没执行完就直接返回了albumAssets,导致视频的mediaasset和mediaduration是空值。图片能正常工作是因为你给requestImageOptions设置了isSynchronous = true,强制同步执行,但视频的请求没有这个设置(也没法设置同步),所以需要把异步回调转换成async/await模式来等待结果。

关键解决方案:将异步回调包装为async函数

我们可以给PHImageManager写一个扩展,把requestAVAsset(forVideo:options:resultHandler:)和requestImage(for:targetSize:contentMode:options:resultHandler:)包装成支持async/await的方法,这样就能在你的async函数里用await等待结果完成。

修改后的完整代码

import Photos
import SwiftUI
import AVFoundation

// 给PHImageManager添加async扩展,适配回调转async/await
extension PHImageManager {
    func requestImageAsync(for asset: PHAsset, targetSize: CGSize, contentMode: PHImageContentMode, options: PHImageRequestOptions?) async -> UIImage? {
        await withCheckedContinuation { continuation in
            self.requestImage(for: asset, targetSize: targetSize, contentMode: contentMode, options: options) { image, _ in
                continuation.resume(returning: image)
            }
        }
    }
    
    func requestAVAssetAsync(for asset: PHAsset, options: PHVideoRequestOptions?) async -> (AVAsset?, AVAudioMix?) {
        await withCheckedContinuation { continuation in
            self.requestAVAsset(forVideo: asset, options: options) { asset, audioMix, _ in
                continuation.resume(returning: (asset, audioMix))
            }
        }
    }
}

func getAlbumAssets(albumName: String) async -> [AlbumAsset] {
    var albumAssets: [AlbumAsset] = []
    
    let fetchOptions = PHFetchOptions()
    fetchOptions.predicate = NSPredicate(format: "title = %@", albumName)
    
    let albumCollection = PHAssetCollection.fetchAssetCollections(with: .album, subtype: .any, options: fetchOptions)
    guard let targetAlbum = albumCollection.firstObject else {
        return albumAssets
    }
    
    let albumAssetsResult = PHAsset.fetchAssets(in: targetAlbum, options: nil)
    guard albumAssetsResult.count > 1 else { // 确保存在索引为1的资源
        return albumAssets
    }
    
    let asset = albumAssetsResult.object(at: 1)
    
    // 图片请求配置(移除同步设置,改用async等待)
    let requestImageOptions = PHImageRequestOptions()
    requestImageOptions.version = .current
    requestImageOptions.resizeMode = .exact
    requestImageOptions.deliveryMode = .highQualityFormat
    requestImageOptions.isNetworkAccessAllowed = true
    requestImageOptions.progressHandler = { progress, error, stop, info in
        if progress == 1.0 {
            print("Downloaded Shared Image from iCloud")
        } else {
            print("\(progress)")
        }
    }
    
    // 视频请求配置
    let requestVideoOptions = PHVideoRequestOptions()
    requestVideoOptions.version = .current
    requestVideoOptions.deliveryMode = .highQualityFormat
    requestVideoOptions.isNetworkAccessAllowed = true
    requestVideoOptions.progressHandler = { progress, error, stop, info in
        if progress == 1.0 {
            print("Downloaded Shared Video from iCloud")
        } else {
            print("\(progress)")
        }
    }
    
    let imageManager = PHImageManager()
    let imageSize = CGSize(width: asset.pixelWidth/2, height: asset.pixelHeight/2)
    
    var converted = asset.mediaType == .image ? Image("iva") : Image("vai")
    var mediaasset = ""
    var mediaduration = CMTimeGetSeconds(.zero)
    
    // 用await等待异步操作完成,确保数据就绪
    if asset.mediaType == .image {
        if let image = await imageManager.requestImageAsync(for: asset, targetSize: imageSize, contentMode: .aspectFill, options: requestImageOptions) {
            converted = Image(uiImage: image)
        }
    } else if asset.mediaType == .video {
        let (avAsset, _) = await imageManager.requestAVAssetAsync(for: asset, options: requestVideoOptions)
        if let avaURLasset = avAsset as? AVURLAsset {
            mediaasset = avaURLasset.url.absoluteString
            mediaduration = CMTimeGetSeconds(avaURLasset.duration)
        }
    }
    
    // 修复原代码位置信息未赋值的问题
    let location: CLLocationCoordinate2D
    if let assetLocation = asset.location {
        location = assetLocation.coordinate
    } else {
        location = CLLocationCoordinate2D(latitude: 0.0, longitude: 0.0)
    }
    
    // 所有数据准备完毕后,再创建AlbumAsset
    let newAsset = AlbumAsset(
        index: 0,
        album: albumName,
        name: asset.localIdentifier,
        createDate: asset.creationDate ?? Date(),
        asset: mediaasset,
        duration: mediaduration,
        image: converted,
        imageType: asset.mediaType,
        imageWidth: CGFloat(asset.pixelWidth),
        imageHeight: CGFloat(asset.pixelHeight),
        location: location,
        cover: false
    )
    albumAssets.append(newAsset)
    
    return albumAssets
}

// 假设你的AlbumAsset结构体定义如下(可根据实际情况调整)
struct AlbumAsset {
    let index: Int
    let album: String
    let name: String
    let createDate: Date
    let asset: String
    let duration: Double
    let image: Image
    let imageType: PHAssetMediaType
    let imageWidth: CGFloat
    let imageHeight: CGFloat
    let location: CLLocationCoordinate2D
    let cover: Bool
}

核心修改点说明

  1. 回调转async/await:用withCheckedContinuation将PHImageManager的异步回调API包装成支持await的方法,从根本上解决异步等待问题。
  2. 统一异步处理逻辑:移除图片请求的isSynchronous = true,用await统一等待图片/视频加载完成,代码更规范。
  3. 确保数据就绪再创建对象:视频处理部分必须等await拿到AVAsset后,才赋值相关字段,避免空值。
  4. 修复位置信息赋值漏洞:原代码中判断了位置但未给location变量赋值,现在补上逻辑确保字段有效。

内容的提问来源于stack exchange,提问作者KentinCopa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 15:10:58