请求 AVAssets 时维护顺序

Maintaining order when requesting AVAssets

我正在尝试构建一个视频合并应用程序,它允许用户从集合视图中 select 多个短片,然后生成所有合并成一个视频的预览。我正在使用照片框架 (PHCachingImageManager) 来填充集合视图,并将 selected PHAssets 的数组传递给下面的函数,以请求低质量的 AVAssets(用于合并和生成预览)。

问题是,我需要按照用户 select 编辑它们的顺序保存 AVAssets,但是 "requestAVAsset" 函数是异步的,完成处理程序通常会被多次调用。我以前从未使用过 Dispatch Groups,但尝试在下面使用它们……而且 AVAssets 有时仍然出现故障。

func requestAVAssets(assets: [PHAsset]) -> [AVAsset] {
    var videoArray: [AVAsset] = []
    let dispatchGroup = DispatchGroup()
    let videoOptions = PHVideoRequestOptions()
    videoOptions.isNetworkAccessAllowed = true
    videoOptions.deliveryMode = .fastFormat
    for asset in assets {
        dispatchGroup.enter()
        self.imageManager.requestAVAsset(forVideo: asset, options: videoOptions, resultHandler: { (video, audioMix, info) in
            guard video != nil else { return }
            videoArray.append(video!)
            dispatchGroup.leave()
        })
    }
    dispatchGroup.wait()
    return videoArray
}

我猜我可能放错了一些代码,或者是以完全错误的方式处理这个问题!任何建议表示赞赏。

完全回避调度问题是因为时间晚了,我今天过得很糟糕,但是如果您保留与视频关联的 "correct" 索引,然后对其进行排序怎么办?我认为这样的事情会奏效。

struct SelectedVideo {
    let index: Int
    let asset: AVAsset
}

func requestAVAssets(assets: [PHAsset]) -> [AVAsset] {
    var videoArray: [SelectedVideo] = []
    let dispatchGroup = DispatchGroup()
    let videoOptions = PHVideoRequestOptions()
    videoOptions.isNetworkAccessAllowed = true
    videoOptions.deliveryMode = .fastFormat
    for (index, asset) in assets.enumerated() {
        dispatchGroup.enter()
        self.imageManager.requestAVAsset(forVideo: asset, options: videoOptions, resultHandler: { (videoMb, audioMixMb, infoMb) in
            guard let video = videoMb else return
            videoArray.append(SelectedVideo(index, video))
            dispatchGroup.leave()
        })
    }
    dispatchGroup.wait()
    return videoArray.sort { [=10=].index < .index}.map({[=10=].video})
}

这有点骇人听闻(甚至还没有尝试编译它),但就像我说的,这是糟糕的一天。

需要注意的几个小变化:我将闭包的参数更改为 "Mb",这意味着 "maybe",这是我见过的用于命名传递给闭包的可选参数的一个很好的约定。此外,与其在 "guard video != nil" 之后进行强制展开,不如执行 "guard let video = videoMb",然后视频是非可选的。

如果您在迭代 AVAsset 时捕获当前索引,则可以插入而不是追加。至少我是这样做的。

func requestAVAssets(assets: [PHAsset]) -> [AVAsset] {
    var videoArray = [AVAsset?](repeating: nil, count: assets.count)
    let videoOptions = PHVideoRequestOptions()
    videoOptions.isNetworkAccessAllowed = true
    videoOptions.deliveryMode = .fastFormat
    for (i, asset) in assets.enumerated() {
        self.imageManager.requestAVAsset(forVideo: asset, options: videoOptions, resultHandler: { (video, audioMix, info) in
            guard let video = video else { return }
            videoArray.remove(at: i)
            videoArray.insert(video, at: i)
        })
    }
    return videoArray.flatMap { [=10=] }
}

为数组提供所需的项目数 nil 将阻止它在插入项目时出错,然后在下载完成后,删除现有的 nil 值并将其替换为实际的 AVAsset.

最后,对结果数组进行 flatMap 以解压缩选项(并可选择通过将其与传入的 assets 数组进行比较来检查您是否具有所需数量的项目)。