'Swift Combine HTTP request

Trying to figure out how to make quick http requests using combine. I've mostly been looking at this doc by Apple. I haven't made any progress though and it seems simple enough so I don't know what I'm doing wrong.

I'm use to JavaScript and using the Fetch API
Also use to http pkg in Golang
But for some reason doing it in Swift is really confusing to me like it's not very streamlined like the two I mentioned

I'm using SwiftUI with the MVVM architecture so my goal is to implement a way of communicating to my Golang server in my view model.

This requires mostly POST requests with Accept and Authorization headers and a JSON body

I tried this code:

let url = URL(string: "https://api.example.com/user/login")!
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Accept")
request.setValue(String(format: "Bearer %s", idToken!), forHTTPHeaderField: "Authorization")
        
_ = URLSession.shared.dataTaskPublisher(for: request)
    .tryMap() { element -> Data in
        // not worried about the status code just trying to get any response.
        guard let httpResponse = element.response as? HTTPURLResponse, httpResponse.statusCode >= 200
        else {
            throw URLError(.badServerResponse)
        }
        return element.data
    }
    // .decode(type: LoginResponseData.self, decoder: JSONDecoder()) commenting this out because I just want to see any data.
    .sink(
        receiveCompletion: { print ("Received completion: \($0).") },
        receiveValue: { data in print ("Received user: \(data).")}
    )

And get this console message: nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Which I read here may have nothing to do with my issue however they were talking about firebase I think.

I would like to know if there is anything wrong with my code?
Or a working example with some JSON API or even a http GET request of some website would be fine.

Thanks!



Solution 1:[1]

Let's make a very rudimentary reduction of your code:

    let request = URLRequest(url: URL(string:"https://www.apple.com")!)
    _ = URLSession.shared.dataTaskPublisher(for: request)
        .sink(receiveCompletion: {_ in print("completion")},
              receiveValue: { print($0) })

Result: Nothing prints at all.


Okay, now let's do what I suggested in my comment. We have an instance property:

var storage = Set<AnyCancellable>()

And we change the code to look like this:

    URLSession.shared.dataTaskPublisher(for: request)
        .sink(receiveCompletion: {_ in print("completion")},
              receiveValue: { print($0) })
        .store(in:&self.storage)

And we get this in the console (well, at least I do):

(data: 74909 bytes, response: <NSHTTPURLResponse: 0x600002e479e0> { URL: https://www.apple.com/ } { Status Code: 200, Headers {
    "Cache-Control" =     (
        "max-age=151"
    );
    "Content-Encoding" =     (
        gzip
    );
    "Content-Length" =     (
        10974
    );
    "Content-Type" =     (
        "text/html; charset=UTF-8"
    );
    Date =     (
        "Fri, 04 Dec 2020 17:45:50 GMT"
    );
    Expires =     (
        "Fri, 04 Dec 2020 17:48:21 GMT"
    );
    Server =     (
        Apache
    );
    "Set-Cookie" =     (
        "geo=US; path=/; domain=.apple.com",
        "ccl=uQisGvuhtgEjEZERqJarcpJqZcmsz2JEaxjveIr8V14=; path=/; domain=.apple.com"
    );
    "Strict-Transport-Security" =     (
        "max-age=31536000; includeSubDomains"
    );
    Vary =     (
        "Accept-Encoding"
    );
    "x-content-type-options" =     (
        nosniff
    );
    "x-frame-options" =     (
        SAMEORIGIN
    );
    "x-xss-protection" =     (
        "1; mode=block"
    );
} })
completion

To understand the difference, perhaps it will help to think about Heraclitus. "You can't step in the same river twice, because different and different waters keep flowing." That river is time.

  • In your code, the publisher-to-sink pipeline is created and thrown away — because it is purely local to this method, which creates the pipeline and instantly ends. Meanwhile, the river is flowing! So the pipeline is gone before the request even had a chance to get started. It never starts so it never finishes. It never finishes so it never prints.

  • In my code, the publisher-to-sink pipeline is preserved (in an instance property). That is what store does. Thus the request has a chance to start, and still later it has a chance to finish, long after the pipeline was created, while the river keeps flowing and flowing.

Solution 2:[2]

Hoping can help someone:

I did paste by error:

@State var cancellableUploadTask: AnyCancellable?

instead of:

private var cancellableUploadTask: AnyCancellable?

and it failed...

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 matt
Solution 2 ingconti