Использование NSURLSession из программы командной строки Swift

Я пытаюсь протестировать небольшое приложение командной строки для проверки концепции, прежде чем интегрировать его в более крупное приложение. То, что я пытаюсь сделать, это загрузить некоторые данные, используя NSURLSession, используя этот пример. Однако, похоже, что если я использую примеры, приведенные в простом приложении командной строки OS X, то приложение завершается до получения данных.

Как я могу загрузить данные из автономного приложения командной строки, используя NSURLSession? То, о чем я читал, - это использование NSRunLoop, однако я еще не нашел четкого примера в Swift, так что, если NSRunLoop действительно является подходящим вариантом, то любые примеры будут оценены.

Любые другие стратегии загрузки данных из URL для приложения командной строки Swift также приветствуются (бесконечный цикл while?).

4 ответа

Решение

Вы можете использовать семафор, чтобы заблокировать текущий поток и дождаться окончания сеанса URL.

Создайте семафор, начните сеанс URL, затем дождитесь семафора. От вашего обратного вызова завершения сеанса URL, сигнализируйте семафор.

Вы можете использовать глобальный флаг (объявить переменную логической переменной) и опрашивать его из цикла while, но это менее оптимально. Во-первых, вы сжигаете циклы процессора без необходимости.

Вот быстрый пример, который я сделал на детской площадке:

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()

Попробуй это

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

Если это только для целей тестирования, вы можете избежать использования семафора, если жестко запрограммируете "время выполнения" приложения командной строки, например:

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

Это очень быстрый и грязный способ "включить" приложения командной строки для ожидания завершения других потоков. Кроме того, ваше приложение будет нормально завершать работу после истечения времени ожидания без необходимости явного уничтожения или отмены процесса приложения.

ПРИМЕЧАНИЕ:

  1. Вы можете изменить период ожидания, если сетевым задачам требуется больше времени для выполнения.
  2. Это "решение" - определенно плохое решение, если вам нужен более серьезный механизм ожидания (иначе. НЕ ИСПОЛЬЗУЙТЕ ЭТО В ПРОИЗВОДСТВЕ)

Я обошел это с помощью цикла while и флага bool, который устанавливается в true, когда сеанс URL возвращается

Xcode > MacOS > консольное приложение

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
Другие вопросы по тегам