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?).
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()
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
If it's just for testing purposes, you can avoid semaphore usage if you hard code "execution time" of your command line app like this :
SWIFT 3
//put at the end of your main file
RunLoop.main.run(until: Date(timeIntervalSinceNow: 15)) //will run your app for 15 seconds only
This is very quick and dirty way to "enable" commandline apps to wait for other threads to finish. Also, your app will exit normally after timeout expires, without the need to explicitly kill or cancel app process.
NOTE :
- You can alter 'timeout' period if your networking tasks require more time to complete.
- This 'solution' is definitely poor decision if you want more serious waiting mechanism (aka. DON'T USE THIS IN PRODUCTION)
I got around this with a while loop and bool flag which is set to true when url session returns
Xcode > MacOS > console app
main.swift
import Foundation
//calls api to send push message but call is async and console app may have died before ws call returns
func sendRequest() {
print("sendRequest called")
let sessionConfig = URLSessionConfiguration.default
let session = URLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
guard var URL = URL(string: "https://api.pushover.net/1/messages.json") else {return}
let URLParams = [
"user": "USER",
"token": "TOKEN",
"message": "New+PUSH",
]
//helper method to append params
URL = URL.appendingQueryParameters(URLParams)
var request = URLRequest(url: URL)
request.httpMethod = "POST"
/* Start a new Task */
print("sendRequest called: POST ")
//FLAG to use to keep app around till WS returns
var keepRunning = true
let task = session.dataTask(with: request,
completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
if (error == nil) {
// Success
print("sendRequest called: POST RETURNED Success")
let statusCode = (response as! HTTPURLResponse).statusCode
print("URL Session Task Succeeded: HTTP \(statusCode)")
}
else {
// Failure
print("URL Session Task Failed: %@", error!.localizedDescription);
}
//WS - returns async and reponse handled - flip bool to kill while loop below and also kills app
print("WS reponse handled set keepRunning to false")
keepRunning = false
})
task.resume()
//session.finishTasksAndInvalidate()
//Issue: console app will die when doSearch() ends, but ws call may not have returned so session may die and ws call may fail so add while loop to prevent that
while keepRunning {
print("keepRunning: true: loop")
}
print("keepRunning:\(keepRunning) - sendRequest() ends")
}
//main.swift - starts here
print("call sendRequest")
sendRequest()
call sendRequest
sendRequest called
sendRequest called: POST
keepRunning: true: loop
keepRunning: true: loop
....
keepRunning: true: loop
keepRunning: true: loop
keepRunning: true: loop
.....
keepRunning: true: loop
keepRunning: true: loop
sendRequest called: POST RETURNED Success
keepRunning: true: loop
keepRunning: true: loop
.....
keepRunning: true: loop
URL Session Task Succeeded: HTTP 200
keepRunning: true: loop
WS reponse handled set keepRunning to false
keepRunning: true: loop
keepRunning:false - sendRequest() ends
Program ended with exit code: 0