Быстрый запрос POST в том же потоке

#swift #multithreading #post

#быстрый #многопоточность #Публикация

Вопрос:

Надеюсь, вы сможете мне помочь. Я хочу функцию swift, которая выполняет post-запрос и возвращает данные json

итак, вот мой класс

 import Foundation


class APICall {

    //The main Url for the api
    var mainApiUrl = "http://url.de/api/"

    func login(username: String, password: String) -> String {
        let post = "user=(username)amp;password=(password)";
        let action = "login.php";
        let ret = getJSONForPOSTRequest(action: action, post: post)
        return ret;
    }

    //Function to call a api and return the json output
    func getJSONForPOSTRequest(action: String, post: String) -> String {
        var ret: String?

        let apiUrl = mainApiUrl   action;

        let myUrl = URL(string: apiUrl);
        var request = URLRequest(url:myUrl!);
        request.httpMethod = "POST";

        let postString = post;

        request.httpBody = postString.data(using: String.Encoding.utf8);

        let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in

            if error != nil
            {
                print("error=(error)")
                return
            }

            print("response=(response)")

            do {
                let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary

                if let parseJSON = json {
                    let login = parseJSON["Login"] as? String
                    print("login: (login)")
                    ret = login
                }
            } catch {
                print(error)
            }
        }
        task.resume()
        return ret!;
    }

}
  

Но ret равен нулю. В отладчике видно, что внутренняя часть задачи вызывается позже другим потоком?

Как это можно исправить?

Спасибо, ребята

Комментарии:

1. Вы хотите заблокировать свой основной поток и дождаться результата?

2. нет ли шансов, что я смогу сделать это в том же потоке? тогда да, я хочу подождать

3. вы могли бы сделать это в том же потоке, но я бы настоятельно не советовал вам этого делать.

4. хорошо, но можете ли вы объяснить мне, как устроен рабочий процесс? я хочу данные из запроса php post и вызываю функцию. Как это работает без того же потока?

Ответ №1:

Закрытие задачи завершения обработки данных вызывается в другом потоке и после завершения выполнения метода, поэтому вам нужно немного переделать свой код. Вместо того, чтобы возвращать строковое значение для вашего getJSONForPOSTRequest , ничего не возвращайте, а вместо этого используйте дополнительный аргумент, который является закрытием, и вызывайте его из вашего закрытия dataTask.

 func getJSONForPOSTRequest(action: String, post: String, completion: (string: String) -> Void) {

    // ...

    let task = URLSession.shared.dataTask(with: request) { (data: Data?, response: URLResponse?, error: Error?) in

        // ... (Convert data to string etc.)

        completion(string: myString)
    }

    task.resume()
}
  

Помните, что это означает, что обработчик завершения будет вызван после завершения сетевого запроса, а не сразу.

Редактировать:

Давайте рассмотрим это с самого начала. Когда вы загружаете что-то из сети в iOS, вы обычно используете NSURLSession . NSURLSession имеет ряд доступных методов для различных средств взаимодействия с сетью, но все эти методы используют другой поток, обычно фоновый поток, который будет выполнять работу независимо от остальной части вашего кода.

Имея это в виду, когда вы вызываете dataTask метод, вы заметите, что вам нужно добавить закрытие завершения в качестве одного из параметров (обратите внимание, что в вашем примере вы используете нечто, называемое «завершающим закрытием», которое является закрытием, которое является последним аргументом в вызове метода, который не попадает в круглые скобки метода с остальными аргументами). Думайте о закрытии как о фрагменте кода, который выполняется в другое время, он не выполняется в соответствии с остальным кодом вокруг него (см. Документацию Swift по закрытиям здесь). В этом случае закрытие будет вызвано после завершения сетевого запроса. Сетевые запросы не выполняются мгновенно, поэтому мы обычно используем фоновый поток для их выполнения, пока пользователю отображается индикатор активности и т.д., И он все еще может использовать приложение. Если мы дождались завершения сетевого запроса в том же потоке, что и остальная часть нашего кода, то это приводит к тому, что приложение становится зависшим и даже зависшим, что ужасно для пользователей.

Итак, возвращаясь к вашему примеру; когда вы вызываете свой getJSONForPOSTRequest метод, код в этом методе завершится и вернется до завершения сетевого запроса, поэтому нам не нужно использовать возвращаемое значение. Как только сетевой запрос завершится, будет вызван ваш код закрытия. Поскольку закрытие вызывается позже, оно также вызывается из совершенно другого места в коде, в данном случае оно вызывается из сетевого кода iOS. Потому что, если это, если вы возвращаете значение из замыкания, вы будете пытаться вернуть значение в сетевой код, который не является тем, что вы хотите, вы хотите вернуть значение в свой собственный код.

Чтобы вернуть значение сетевого ответа вашему коду, вам нужно определить замыкание (или делегат, но я не собираюсь вдаваться в это здесь) самостоятельно. Если вы посмотрите на приведенный выше пример кода, я удалил возвращаемое значение из вашего getJSONForPOSTRequest метода и добавил новый аргумент с именем ‘завершение’, и если вы посмотрите на тип этого аргумента, вы увидите, что это (string: String) -> Void , это определяет замыкание, которое передается в строке (строка, которую вы скачали из сети). Теперь, когда у нас есть закрытие в вашем методе, мы можем использовать это для обратного вызова вызывающего getJSONForPOSTRequest с данными, которые мы загрузили из сети.

Давайте возьмем ваш login метод и посмотрим, как мы используем getJSONForPOSTRequest в нем:

 func login(username: String, password: String, completion: (success: Bool) -> Void) {

    let post = "user=(username)amp;password=(password)";
    let action = "login.php";

    let ret = getJSONForPOSTRequest(action: action, post: post) { string in 

        // This will be called once the network has responded and 'getJSONForPOSTRequest' has processed the data
        print(string)

        completion(success: true)
    }
}
  

Посмотрите, что снова мы ничего не возвращаем напрямую из метода входа, поскольку он должен полагаться на синхронность вызова в сеть.

К настоящему времени может показаться, что вы начинаете попадать в нечто, называемое «ад обратного вызова», но это стандартный способ работы с сетью. В вашем коде пользовательского интерфейса вы вызовете login , и это будет конец цепочки. Например, вот некоторый гипотетический код пользовательского интерфейса:

 func performLogin() {

    self.activityIndicator.startAnimating()

    self.apiCaller.login(username: "Joe", password: "abc123") { [weak self] success in 

        print(success)

        // This will get called once the login request has completed. The login might have succeeded of failed, but here you can make the decision to show the user some indication of that
        self?.activityIndicator.stopAnimating()
        self?.loginCompleted()
    }
}
  

Надеюсь, это прояснит некоторые вещи, если у вас есть какие-либо другие вопросы, просто спросите.

Комментарии:

1. sry я не понимаю … у меня та же проблема, что и раньше, верно?

2. Ну нет, у вас есть способ передать данные обратно вызывающему, если метод. Вы пытаетесь заблокировать основной поток, чтобы вернуть данные? Если это так, я бы не советовал этого делать, поскольку любой пользовательский интерфейс, который у вас есть, будет зависать до тех пор, пока данные не вернутся, и если устройство имеет медленное соединение, это займет некоторое время.

3. Итак, в вызывающем я делаю цикл while, пока завершение не станет пустым?

4. Ха-ха-ха, нет, все это обрабатывается вызовом метода dataTask. Я думаю, что вам не хватает небольшой ссылки в ваших знаниях здесь, но не волнуйтесь, мы все с чего-то начинаем 🙂 В данный момент я в поезде, но когда я приступлю к работе, я напишу для вас лучший пример.

5. Внес изменения в ответ, надеюсь, это заполнит для вас несколько пробелов.