"how to use urlsessionstreamtask with urlsession for chunked-encoding transfer" Code Answer


received lots of info courtesy of quinn "the eskimo" at apple.

alas, you have the wrong end of the stick here. urlsessionstreamtask is for wrangling a naked tcp (or tls over tcp) connection, without the http framing on top. you can think of it as a high-level equivalent to the bsd sockets api.

the chunked transfer encoding is part of http, and is thus supported by all of the other urlsession task types (data task, upload task, download task). you don’t need to do anything special to enable this. the chunked transfer encoding is a mandatory part of the http 1.1 standard, and is thus is always enabled.

you do, however, have an option as to how you receive the returned data. if you use the urlsession convenience apis (datatask(with:completionhandler:) and so on), urlsession will buffer all the incoming data and then pass it to your completion handler in one large data value. that’s convenient in many situations but it doesn’t work well with a streamed resource. in that case you need to use the urlsession delegate-based apis (datatask(with:) and so on), which will call the urlsession(_:datatask:didreceive:) session delegate method with chunks of data as they arrive.

as for the specific endpoint i was testing, the following was uncovered: it seems that the server only enables its streaming response (the chunked transfer encoding) if the client sends it a streaming request. that’s kinda weird, and definitely not required by the http spec.

fortunately, it is possible to force urlsession to send a streaming request:

  1. create your task with uploadtask(withstreamedrequest:)

  2. implement the urlsession(_:task:neednewbodystream:) delegate method to return an input stream that, when read, returns the request body

  3. profit!

i’ve attached some test code that shows this in action. in this case it uses a bound pair of streams, passing the input stream to the request (per step 2 above) and holding on to the output stream.

if you want to actually send data as part of the request body you can do so by writing to the output stream.

class networkmanager : nsobject, urlsessiondatadelegate {

static var shared = networkmanager()

private var session: urlsession! = nil

override init() {
    let config = urlsessionconfiguration.default
    config.requestcachepolicy = .reloadignoringlocalcachedata
    self.session = urlsession(configuration: config, delegate: self, delegatequeue: .main)

private var streamingtask: urlsessiondatatask? = nil

var isstreaming: bool { return self.streamingtask != nil }

func startstreaming() {
    precondition( !self.isstreaming )

    let url = url(string: "enter streaming url here")!
    let request = urlrequest(url: url)
    let task = self.session.uploadtask(withstreamedrequest: request)
    self.streamingtask = task

func stopstreaming() {
    guard let task = self.streamingtask else {
    self.streamingtask = nil

var outputstream: outputstream? = nil

private func closestream() {
    if let stream = self.outputstream {
        self.outputstream = nil

func urlsession(_ session: urlsession, task: urlsessiontask, neednewbodystream completionhandler: @escaping (inputstream?) -> void) {

    var instream: inputstream? = nil
    var outstream: outputstream? = nil
    stream.getboundstreams(withbuffersize: 4096, inputstream: &instream, outputstream: &outstream)
    self.outputstream = outstream


func urlsession(_ session: urlsession, datatask: urlsessiondatatask, didreceive data: data) {
    nslog("task data: %@", data as nsdata)

func urlsession(_ session: urlsession, task: urlsessiontask, didcompletewitherror error: error?) {
    if let error = error as nserror? {
        nslog("task error: %@ / %d", error.domain, error.code)
    } else {
        nslog("task complete")

and you can call the networking code from anywhere such as:

class mainviewcontroller : uitableviewcontroller {

override func tableview(_ tableview: uitableview, didselectrowat indexpath: indexpath) {
    if networkmanager.shared.isstreaming {  
    } else {
    self.tableview.deselectrow(at: indexpath, animated: true)

hope this helps.

By thorr18 on February 7 2022
Only authorized users can answer the Search term. Please sign in first, or register a free account.