Search code examples
iosswiftfile-uploadurlsession

How to check error if uploading is failed using session.uploadTask in swift?


I am uploading video file in the background but I dnon't know how to check if the video file is failed to upload. I am using URLSession to upload the file. If anybody has some idea please help me out.

private func uploadMediaWith(mediaURL: URL, mediaMimeType: MediaMimeType, AWSPresignedURL: URL) {
    let finalMediaURL = mediaURL.updatedSandboxURL()
    Log.echo(key: "\(self.KEY)", text: "setup upload for \(mediaURL.lastPathComponent)")
    let url = AWSPresignedURL // https://dentiscope-development.s3.us-west-2.amazonaws.com/exams/42/videos/BottomLeftOutside-b121986d-bd5c-4b6f-bed2-f030978f03f0.mp4?x-amz-acl=private&x-amz-meta-exam_id=42&x-amz-meta-uuid=b121986d-bd5c-4b6f-bed2-f030978f03f0&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIA5WF3XEUH4WAYWLEI%2F20211115%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20211115T070402Z&X-Amz-SignedHeaders=host%3Bx-amz-acl%3Bx-amz-meta-exam_id%3Bx-amz-meta-uuid&X-Amz-Expires=432000&X-Amz-Signature=a6ede8cd558cd1df5c5d6e62be1237f995b7ac97e235112acc263e3b0de1531f
    let boundary = UUID().uuidString
    var request = URLRequest(url: url)
    request.httpMethod = "PUT"
    
    request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
    if let authToken = SignedUserInfo.sharedInstance?.authToken {
      request.setValue("Authorization", forHTTPHeaderField: "Bearer " + authToken)
    }
    
    let config = URLSessionConfiguration.background(withIdentifier: boundary)
    config.waitsForConnectivity = true
    config.shouldUseExtendedBackgroundIdleMode = true
//    config.httpMaximumConnectionsPerHost = 4
//    config.networkServiceType = .background
    
    let session = URLSession(configuration: config, delegate: self, delegateQueue: .main)
    
    var data = Data()
    if(mediaMimeType == MediaMimeType.video) {
      do {
        let videoData = try Data(contentsOf: finalMediaURL)
        data.append(videoData)
      } catch {
        Log.echo(key: "MediaMimeType.video", text: error.localizedDescription)
        fatalError("\(self.KEY) COULD NOT GET DATA OF THE VIDEO || \(finalMediaURL.lastPathComponent)")
      }
    }
    
    request.httpBody = data
    DispatchQueue.global().async {
      let task = session.uploadTask(withStreamedRequest: request)
      // how to check if failled to uload
      self.uploadingURL = finalMediaURL
      task.resume()
      Log.echo(key: "\(self.KEY)", text: "execute upload for \(mediaURL.lastPathComponent) || session id \(boundary)")
    }
  }

If I am using this code:

let task = session.dataTask(with: request) { (data, urlResp, error) in
        if let error = error {
          print(error)
        }
      } //uploadTask(withStreamedRequest: request)
      self.uploadingURL = finalMediaURL
      task.resume()

then getting this error: Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.' terminating with uncaught exception of type NSException


Solution

  • Using a delegate and converting it to completion block you could use something like the following:

    class UploadContainer: NSObject, URLSessionTaskDelegate {
        
        let session: URLSession
        let request: URLRequest
        
        private var completion: ((_ error: Error?) -> Void)?
        private var task: URLSessionUploadTask?
        
        private var selfRetain: Any? // This is to persist lifetime of this class for the duration of request execution
        
        init(request: URLRequest, session: URLSession = .shared, completion: @escaping ((_ error: Error?) -> Void)) {
            self.request = request
            self.session = session
            self.completion = completion
            super.init()
            self.upload()
        }
        
        private func upload() {
            let task = session.uploadTask(withStreamedRequest: self.request)
            self.task = task
            task.delegate = self
            self.selfRetain = self
            task.resume()
        }
        
        func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
            self.selfRetain = nil
            completion?(error)
            completion = nil
        }
        
    }
    

    Then when you generate your request you can simply call

    UploadContainer(request: request) { error in
        if let error = error {
            print("Error occurred uploading file \(error)")
        } else {
            print("File was successfully uploaded")
        }
    }
    

    But there are many ways to achieve this. Point being is that once you create a task you need to assign a delegate. In provided code you can see this line task.delegate = self. Whoever is assigned as delegate (in this example self as UploadContainer) needs to implement this delegate which is done by this : NSObject, URLSessionTaskDelegate. The class can now implement some methods documented in URLSessionTaskDelegate. But we are only interested in completing the upload (with or without an error) which seems to be a method called func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?). I hope this works for you.