swiftuikitswift-concurrency

Get rid of warning: Passing argument of non-sendable type 'AVAssetExportSession' outside of main actor-isolated context may introduce data races


How can I get rid of this warning for await exportSession.export():

enter image description here

Code:

func extractAudioFromImportedVideo(videoURL: URL, success: @escaping ((URL) -> Void), failure: @escaping ((Error?) -> Void)) async {
        
        do {
            let asset = AVURLAsset(url: videoURL)
            let audioTracks = try await asset.loadTracks(withMediaType: .audio)
            
            guard !audioTracks.isEmpty else {
                failure(NSError(domain: "Audio Extraction Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "Video does not contain audio"]))
                return
            }
            
            guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
                failure(NSError(domain: "Audio Extraction Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to create export session"]))
                return
            }
            
            guard let audioURL = makeFileOutputURL(fileName: "ExtractedAudio.m4a") else {
                failure(NSError(domain: "Audio Extraction Error", code: 0, userInfo: [NSLocalizedDescriptionKey: "Failed to create output URL"]))
                return
            }
            
            print("audioTracks", audioTracks)
            exportSession.outputURL = audioURL
            exportSession.outputFileType = .m4a

            
            await exportSession.export()
            print("exportSession.status", exportSession.status.rawValue)
            if exportSession.status == .completed {
                // Audio extraction succeeded
                print("Audio extraction succeeded")
                success(audioURL)
            } else {
                // Audio extraction failed
                print("Audio extraction failed: \(exportSession.error?.localizedDescription ?? "")")
                failure(exportSession.error)
            }
        }
        catch {
            print("extractAudioFromImportedVideo Error:", error.localizedDescription)
        }
    }

Solution

  • The error is:

    Passing argument of non-sendable type 'AVAssetExportSession' outside of main actor-isolated context may introduce data races; this is an error in the Swift 6 language mode

    This is telling you that the problem is arising because you are performing this in a main actor-isolated function.

    The easy fix is to remove this actor-isolation, to make the function nonisolated:

    nonisolated func extractAudioFromImportedVideo(videoURL: URL, success: @escaping ((URL) -> Void), failure: @escaping ((Error?) -> Void)) async {
        …
    }
    

    Personally, I would eliminate these closures and just follow async patterns:

    nonisolated func extractAudioFromImportedVideo(videoURL: URL) async throws -> URL {
        do {
            let asset = AVURLAsset(url: videoURL)
            let audioTracks = try await asset.loadTracks(withMediaType: .audio)
    
            guard !audioTracks.isEmpty else {
                throw AudioExtractionError.noAudio
            }
    
            guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
                throw AudioExtractionError.exportFailed
            }
    
            guard let audioURL = makeFileOutputURL(fileName: "ExtractedAudio.m4a") else {
                throw AudioExtractionError.outputUrlFailed
            }
    
            print("audioTracks", audioTracks)
            exportSession.outputURL = audioURL
            exportSession.outputFileType = .m4a
    
    
            await exportSession.export()
            print("exportSession.status", exportSession.status.rawValue)
            if let error = exportSession.error {
                throw error
            }
    
            print("Audio extraction succeeded")
            return audioURL
        } catch {
            print("extractAudioFromImportedVideo Error:", error.localizedDescription)
            throw error
        }
    }
    

    Note, I also retired NSError, replacing it with:

    enum AudioExtractionError: LocalizedError {
        case noAudio
        case exportFailed
        case outputUrlFailed
    
        nonisolated var errorDescription: String? {
            return switch self {
            case .noAudio: String(localized: "Video does not contain audio", comment: "AudioExtractionError")
            case .exportFailed: String(localized: "Failed to create export session", comment: "AudioExtractionError")
            case .outputUrlFailed: String(localized: "Failed to create output URL", comment: "AudioExtractionError")
            }
        }
    }
    

    That is actually localized, isolates the details from this function, and enumerated errors support richer error handling.


    FWIW, once you have this working, you can simplify further:

    nonisolated func extractAudioFromImportedVideo(videoURL: URL) async throws -> URL {
        let asset = AVURLAsset(url: videoURL)
        let audioTracks = try await asset.loadTracks(withMediaType: .audio)
    
        guard !audioTracks.isEmpty else {
            throw AudioExtractionError.noAudio
        }
    
        guard let exportSession = AVAssetExportSession(asset: asset, presetName: AVAssetExportPresetAppleM4A) else {
            throw AudioExtractionError.exportFailed
        }
    
        guard let audioURL = makeFileOutputURL(fileName: "ExtractedAudio.m4a") else {
            throw AudioExtractionError.outputUrlFailed
        }
    
        exportSession.outputURL = audioURL
        exportSession.outputFileType = .m4a
    
        await exportSession.export()
        if let error = exportSession.error {
            throw error
        }
    
        return audioURL
    }