I'm trying to get the asset
property from an AVAssetTrack
object, but it's nil
sometimes. It seems like the problem occurs only after I use Dispatch.main.async
.
According to the documentation, it's necessary to use loadValuesAsynchronously(forKeys:, completion:)
to avoid blocking the main thread, and return to the main thread after loading is done.
let asset = AVURLAsset(url: videoInAppBundleURL)
let track = asset.tracks(withMediaType: .video).first!
assert(track.asset != nil) // passes
track.loadValuesAsynchronously(forKeys: [#keyPath(AVAssetTrack.asset)]) {
assert(track.asset != nil) // passes
DispatchQueue.main.async {
assert(track.asset != nil) // FAILS
// [...]
}
}
What I found out is:
Here is a working demo project.
I'm also wondering: why is AVAssetTrack
's asset
property optional? (all!! the other properties are non optional)
Note: this question has been edited after reading Matt's helpful comments and further investigation.
I reproduced the issue, with some tweaking of your github example, like this:
let asset = AVURLAsset(url: videoInAppBundleURL)
let tracksKey = #keyPath(AVAsset.tracks)
asset.loadValuesAsynchronously(forKeys: [tracksKey]) {
let track = asset.tracks(withMediaType: .video).first!
DispatchQueue.main.async {
assert(track.asset != nil) // fails
}
}
Okay, but now watch closely as I perform an amazing trick:
let asset = AVURLAsset(url: videoInAppBundleURL)
let tracksKey = #keyPath(AVAsset.tracks)
asset.loadValuesAsynchronously(forKeys: [tracksKey]) {
let track = asset.tracks(withMediaType: .video).first!
DispatchQueue.main.async {
print(asset) // <-- amazing trick
assert(track.asset != nil) // passes!
}
}
Whoa! All I did was add a print
statement — and now suddenly the very same assertion passes. This in fact is parallel to your original statement (which you later edited out) that "Sometimes the problems are gone, when stepping through the code with the debugger.”
So, now, my suspicions being thoroughly aroused, I did something unbelievably clever (even if I do say so myself). I removed the print(asset)
, but I switched the scheme’s configuration from Debug to Release. Presto, the assertion still passes.
So what you’ve found is a quirk of the compiler — dare I call it a bug?
But wait, there’s more. You asked, quite reasonably, why asset
is Optional. It’s because it’s weak
:
weak open var asset: AVAsset? { get }
So there’s your answer. The track has only a weak reference to its asset. If we pass the track down into an asynchronous queue, and we do not bring the asset itself along with us, then the weak reference lets go and the asset is lost — in a Debug build.
Hope this helps. You are probably waiting for me to make some grand conclusory statement about whether this constitutes a bug, but I’m not going to, sorry. I’ve provided two workarounds (use a Release build, or deliberately carry the asset reference down into the async queue) and that’s as far as I can go.