Using NSURLSession from a Swift command line program

9.9k views Asked by At

I'm trying to test a little proof-of-concept command line app prior to integrating it into a larger app. What I'm trying to do is download some data using NSURLSession using this example. However it appears that if I use the examples given in a simple OS X command line app then the app exits prior to the data being retrieved.

How can I download data from a stand-alone command line app using NSURLSession? What I've read about is using NSRunLoop however I've not yet found a clear example in Swift so if NSRunLoop is actually the way to go then any examples would be appreciated.

Any other strategies for downloading data from a URL for a Swift command line app is also welcome (infinite while loop?).

3

There are 3 answers

10
nielsbot On BEST ANSWER

You can use a semaphore to block the current thread and wait for your URL session to finish.

Create the semaphore, kick off your URL session, then wait on the semaphore. From your URL session completion callback, signal the semaphore.

You could use a global flag (declare a volatile boolean variable) and poll that from a while loop, but that is less optimal. For one thing, you're burning CPU cycles unnecessarily.

Here's a quick example I did using a playground:

import Foundation

var sema = DispatchSemaphore( value: 0 )

class Delegate : NSObject, URLSessionDataDelegate
{
    func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data)
    {
        print("got data \(String(data: data, encoding: .utf8 ) ?? "<empty>")");
        sema.signal()
    }
}

let config = URLSessionConfiguration.default
let session = URLSession(configuration: config, delegate: Delegate(), delegateQueue: nil )

guard let url = URL( string:"http://apple.com" ) else { fatalError("Could not create URL object") }

session.dataTask( with: url ).resume()    

sema.wait()
0
thiagoh On

Try this

let sema = DispatchSemaphore( value: 0)

let url = URL(string: "https://upload.wikimedia.org/wikipedia/commons/4/4d/Cat_November_2010-1a.jpg")!;

let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
  print("after image is downloaded");
  sema.signal(); // signals the process to continue
};

task.resume();
sema.wait(); // sets the process to wait
2
Vexy On

For proof of concept(s) or tryouts/testing purposes, you can simplify asynchronous complexity by hard coding some timeout period until your stuff finishes. (see notes below)

SWIFT 5

    //...your magic here
    // add a little iness to make it fun at least...
    RunLoop.main.run(until: Date() + 0x10)  //oh boi, default init && hex craze 
    // yeah, 16 seconds timeout

    // or even worse (!)
    RunLoop.main.run(until: .distantFuture)

SWIFT 3 or earlier

    //...your stuff here
    RunLoop.main.run(until: Date(timeIntervalSinceNow: 15))  //will execute things on main loop for 15 seconds

NOTES :

  1. DO NOT USE THIS IN PRODUCTION
  2. respect the first rule

This is very quick and dirty way to overcome serious concerns of parallelism. Explore better and more complex solutions described in other answers of this question.