1

I am obviously missing something very fundamental/naïve/etc., but for the life of me I cannot figure out how to make simple GET requests.

I'm trying to make an HTTP GET request with Swift 5. I've looked at these posts/articles: one, two, but I can't get print() statements to show anything. When I use breakpoints to debug, the entire section within the URLSession.shared.dataTask section is skipped.
I am looking at the following code (from the first link, above):

func HTTP_Request() {
    let url = URL(string: "http://www.stackoverflow.com")!

    let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
        guard let data = data else { return }
        print(String(data: data, encoding: .utf8)!)
    }
    task.resume()
}

HTTP_Request()

I am running this in a MacOS Command Line Project created through XCode.

I would greatly appreciate any help I can get on this, thank you.

Josh Loecker
  • 196
  • 2
  • 14

3 Answers3

2

Right now, if there is an error, you are going to silently fail. So add some error logging, e.g.,

func httpRequest() {
    let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http

    let task = URLSession.shared.dataTask(with: url) { data, response, error in
        guard
            error == nil,
            let data = data,
            let string = String(data: data, encoding: .utf8)
        else {
            print(error ?? "Unknown error")
            return
        }

        print(string)
    }
    task.resume()
}

That should at least give you some indication of the problem.

A few other considerations:

  1. If command line app, you have to recognize that the app may quit before this asynchronous network request finishes. One would generally start up a RunLoop, looping with run(mode:before:) until the network request finishes, as advised in the run documentation.

    For example, you might give that routine a completion handler that will be called on the main thread when it is done. Then you can use that:

    func httpRequest(completion: @escaping () -> Void) {
        let url = URL(string: "https://www.stackoverflow.com")! // note, https, not http
    
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            defer {
                DispatchQueue.main.async {
                    completion()
                }
            }
    
            guard
                error == nil,
                let data = data,
                let string = String(data: data, encoding: .utf8)
            else {
                print(error ?? "Unknown error")
                return
            }
    
            print(string)
        }
        task.resume()
    }
    
    var finished = false
    
    httpRequest {
        finished = true
    }
    
    while !finished {
        RunLoop.current.run(mode: .default, before: .distantFuture)
    }
    
  2. In standard macOS apps, you have to enable outgoing (client) connections in the “App Sandbox” capabilities.

  3. If playground, you have to set needsIndefiniteExecution.

  4. By default, macOS and iOS apps disable http requests unless you enable "Allow Arbitrary Loads” in your Info.plist. That is not applicable to command line apps, but you should be aware of that should you try to do this in standard macOS/iOS apps.

    In this case, you should just use https and avoid that consideration altogether.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • This is wonderful. Thank you so much! None of the documentation I read differentiated between iOS apps, Mac apps, and Command Line apps. I'm not sure what everything here means exactly, but it is a great place for me to learn more :) Thank you very much, again. – Josh Loecker Apr 07 '21 at 05:25
  • 1
    Yeah, when I wrote this, you hadn't yet said what your target was, and I decided to leave those target-specific caveats in there for future readers. I hope I didn't make it more confusing than necessary. The bottom line, is that the standard macOS/iOS/iPadOS/etc. apps have their own run loop (i.e. they keep running until the user explicitly quits the app), so the `RunLoop` caveat doesn't apply on those targets. But for command line app, if you want to wait for some asynchronous task like this, you have to do it yourself, as outlined in point 1 above. – Rob Apr 07 '21 at 05:28
1

Make sure the response get print before exiting the process, you could try to append

RunLoop.main.run()

or

sleep(UINT32_MAX)

in the end to make sure the main thread won't exit. If you want to print the response and exit the process immediately, suggest using DispatchSemaphore:

let semphare = DispatchSemaphore(value: 0)

func HTTP_Request() {
    let url = URL(string: "http://www.stackoverflow.com")!

    let task = URLSession.shared.dataTask(with: url) {(data: Data?, response: URLResponse?, error: Error?) in
        guard let data = data else { return }
        print(String(data: data, encoding: .utf8)!)
        semphare.signal()
    }
    task.resume()
}

HTTP_Request()

_ = semphare.wait(timeout: .distantFuture)
Itachi
  • 5,777
  • 2
  • 37
  • 69
-1

This works for me many times I suggest you snippet for future uses!

let url = URL(string: "https://google.com")
    let task = URLSession.shared.dataTask(with: ((url ?? URL(string: "https://google.com"))!)) { [self] (data, response, error) in
        
        do {
            let jsonResponse = try JSONSerialization.jsonObject(with: data!, options: [])
            print(jsonResponse)
            guard let newValue = jsonResponse as? [String:Any] else {
                print("invalid format")
                }
            }

        
        catch let error {
            print("Error: \(error)")
        }
        
    task.resume()
}