URLSession uploadTask not showing progress

I'm having an issue with showing progress in a upload task. I have created a local express app to test it's behavior. But progress / delegate always throughs the same results. I'm also conforming to URLSessionTaskDelegate. This is the way I configure URLSession it's always .default

 private lazy var session: URLSession = {
     .init(
        configuration: self.configuration,
        delegate: self,
        delegateQueue: OperationQueue.main
    )
}()
private var observation: NSKeyValueObservation?

Then I call it using this func and convert it into a publisher

func loadData1(path: String, method: RequestMethod, params: [String: String]) -> AnyPublisher<Data, Error> {
    let publisher = PassthroughSubject<Data, Error>()
    guard let imageUrl = Bundle.main.url(forResource: "G0056773", withExtension: "JPG"),
          let imageData = try? Data(contentsOf: imageUrl) else { return Fail(error: NSError(domain: "com.test.main", code: 1000)).eraseToAnyPublisher() }
    var request = URLRequest(url: URL(string: "http://localhost:3000/")!)
    request.httpMethod = "POST"
    let data = request.createFileUploadBody(parameters: [:], boundary: UUID().uuidString, data: imageData, mimeType: "image/jpeg", fileName: "video")
    let task = session.uploadTask(with: request, from: data) { data, response, error in
        if let error = error {
            return publisher.send(completion: .failure(error))
        }
        guard let response = response as? HTTPURLResponse else { return publisher.send(completion: .failure(ServiceError.other)) }
        guard 200..<300 ~= response.statusCode else { return publisher.send(completion: .failure(ServiceError.other)) }
        guard let data = data else { return publisher.send(completion: .failure(ServiceError.custom("No data"))) }
        
        return publisher.send(data)
    }
    observation = task.progress.observe(\.fractionCompleted) { progress, _ in
        print("progress: ", progress.totalUnitCount)
        print("progress: ", progress.completedUnitCount)
        print("progress: ", progress.fractionCompleted)
        print("***********************************************")
    }
    task.resume()
    return publisher.eraseToAnyPublisher()
}

request.createFileUploadBody is just an extension of URLRequest that I have created to create the form data's body.

Delegate

    func urlSession(_ session: URLSession, task: URLSessionTask, didSendBodyData bytesSent: Int64, totalBytesSent: Int64, totalBytesExpectedToSend: Int64) {
    print(bytesSent)
    print(totalBytesSent)
    print(totalBytesExpectedToSend)
    print("*********************************************")
}

No matter how big or small is the file that I'm uploading I always get the same results. The delegate is only called once. And the progress is also always the same.

progress:  100
progress:  0
progress:  0.0095
***********************************************
progress:  100
progress:  0
progress:  4.18053577746524e-07
***********************************************
2272436
2272436
2272436
*********************************************
progress:  100
progress:  95
progress:  0.95
***********************************************
progress:  100
progress:  100
progress:  1.0
***********************************************

^^^^^^ Output from prints

Does anyone knows what might I be doing wrong? Thanks in advance

Replies

How big is your file?

The fact that it’s a JPEG suggests its not actually that big. If so, getting upload progress is tricky because URLSession can only report the info it gets from the lower-level networking stack. When it does an upload like this, the session reads data from the file and writes it to the socket buffer [1]. On modern systems, socket buffers are quite large, so that write completes immediately. At that point the progress goes straight from 0% to 100%, which is accurate, from a certain point of view, but not super helpful.

If you upload a large file, say 1 GB or more, do you get better results?

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] An modern systems URLSession doesn’t use BSD Socket but you’ll see the same phenomenon with our user-space networking stack.

@eskimo Thanks a lot for your quick reply. Are InputStreams the only way to limit the size of the socket buffer?

Are InputStreams the only way to limit the size of the socket buffer?

URLSession does not provide any socket buffer size configuration.

You can use an input stream to limit how much it buffers, but that’s not the same thing. Specifically:

  • It switches the transfer coding to chunked, which can cause problems on the server side.

  • Limiting the rate that data comes out of your stream will hurt performance on fast networks.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

@eskimo Thanks a lot for your quick reply