Как вернуть значение от Alamofire

Я делаю URL-вызовы через API, который я создал с помощью swift следующим образом:

class API {

  let apiEndPoint = "endpoint"
  let apiUrl:String!
  let consumerKey:String!
  let consumerSecret:String!

  var returnData = [:]

  init(){
    self.apiUrl = "https://myurl.com/"
    self.consumerKey = "my consumer key"
    self.consumerSecret = "my consumer secret"
  }

  func getOrders() -> NSDictionary{
    return makeCall("orders")
  }

  func makeCall(section:String) -> NSDictionary{

    let params = ["consumer_key":"key", "consumer_secret":"secret"]

    Alamofire.request(.GET, "\(self.apiUrl)/\(self.apiEndPoint + section)", parameters: params)
        .authenticate(user: self.consumerKey, password: self.consumerSecret)
        .responseJSON { (request, response, data, error) -> Void in
            println("error \(request)")
            self.returnData = data! as NSDictionary
    }
    return self.returnData
  }

}

Я вызываю этот API в моем UITableViewController, чтобы заполнить таблицу библиотекой SwiftyJSON. Однако мой returnData из API всегда пуст. Нет проблем с вызовами Alomofire, так как я могу успешно получить значение. Моя проблема в том, как я должен перенести это data на свой контроллер табличного представления?

var api = API()
api.getOrders()
println(api.returnData) // returnData is empty

person u54r    schedule 09.12.2014    source источник
comment
Не имея отношения к вашему исходному вопросу, я совсем не уверен в том, как вы используете ключ потребителя и секрет (вы используете процесс Alamofire authenticate, но делаете что-то любопытное и с параметрами). Наверное, одно или другое. Это функция API вашего веб-сервиса, поэтому мы не можем здесь ответить, но это почти наверняка неверно.   -  person Rob    schedule 10.12.2014
comment
Так устроен сервисный API, который я использую. В документации предлагается передать ключ и секрет в качестве имени пользователя и пароля для базовой аутентификации.   -  person u54r    schedule 10.12.2014
comment
Хорошо, если он действительно использует аутентификацию BASIC, тогда используйте функцию authenticate, но тогда для чего нужен словарь params? Эй, все работает, но кажется любопытным сделать и authenticate, и снова передать ему данные аутентификации в качестве параметров запроса ...   -  person Rob    schedule 10.12.2014


Ответы (5)


Как указывает Мэтт, Alamofire возвращает данные асинхронно через шаблон «обработчик завершения», поэтому вы должны сделать то же самое. Вы не можете просто return значение сразу же, вместо этого вы хотите изменить свой метод, чтобы он ничего не возвращал, а вместо этого используйте шаблон закрытия обработчика завершения.

В настоящее время это может выглядеть так:

func getOrders(completionHandler: @escaping (Result<[String: Any]>) -> Void) {
    performRequest("orders", completion: completionHandler)
}

func performRequest(_ section: String, completion: @escaping (Result<[String: Any]>) -> Void) {
    let url = baseURL.appendingPathComponent(section)
    let params = ["consumer_key": "key", "consumer_secret": "secret"]

    Alamofire.request(url, parameters: params)
        .authenticate(user: consumerKey, password: consumerSecret)
        .responseJSON { response in
            switch response.result {
            case .success(let value as [String: Any]):
                completion(.success(value))

            case .failure(let error):
                completion(.failure(error))

            default:
                fatalError("received non-dictionary JSON response")
            }
    }
}

Затем, когда вы хотите его вызвать, вы используете этот параметр закрытия completion (в завершающем закрытии, если хотите):

api.getOrders { result in
    switch result {
    case .failure(let error):
        print(error)

    case .success(let value):
        // use `value` here
    }
}

// but don't try to use the `error` or `value`, as the above closure
// has not yet been called
//
person Rob    schedule 10.12.2014
comment
Привет, @Rob, я использовал ваш код почти один к одному, чтобы сделать асинхронный запрос. Моя цель для начала - получить [String] и использовать этот variable.count, чтобы определить, сколько numberOfRowsInSection в моем tableView. Ваш пример действительно помог мне, но в нем нет ничего, что действительно что-то возвращает. Когда я делаю ConnectionsHelper.getOrders() в своем собственном коде, я могу успешно распечатать сведения о моем responseObject, но никоим образом не могу вернуть их или назначить их переменной в моем файле ViewController. Не могли бы вы дать еще несколько указателей? Спасибо! - person zerohedge; 21.12.2015
comment
Верно, это ничего не возвращает. Не может, потому что работает асинхронно. Вызовите getOrders в viewDidLoad, например, и в блоке завершения getOrders (где я печатаю responseObject), вы (а) обновите свое свойство результатами асинхронного вызова; и (b) вызвать [self.tableView reloadData] из этого завершения завершения. Итак, ваши UITableViewDataSource методы будут вызываться дважды, один раз при первой загрузке (в этот момент данные еще не доступны); и снова после того, как вы позвоните reloadData. - person Rob; 21.12.2015
comment
Замечательно поработали, спасибо! Должен ли я писать отдельный обработчик завершения для каждой имеющейся у меня функции? (У меня будет более 10 в моем заявлении) - person zerohedge; 22.12.2015
comment
Да, везде, где вы вызываете асинхронный метод, да, вы обычно используете этот шаблон. - person Rob; 22.12.2015
comment
Ваш код выглядит хорошо, но дайте мне эту ошибку. Команда не удалась из-за сигнала: Ошибка сегментации: 11 почему? - person Mitesh Jain; 11.01.2016
comment
@Rob Я сейчас испытываю ту же проблему, что и Митеш. Как можно использовать точки останова для отладки ошибки сегментации, когда приложение не запущено (это приводит к сбою сборки) - person Jesus Rodriguez; 12.01.2016
comment
@Granola - Ах, я неправильно понял. Xcode дал вам ошибку сегментации, а не ваше приложение. Похоже, что обновленный синтаксис Alamofire вызывает проблемы у компилятора. Вы можете использовать оператор switch для независимой проверки .Success и .Failure, и это решит эту проблему. Он не такой компактный, но еще более понятен в обращении с Alamofire Result enum. - person Rob; 12.01.2016
comment
@Mitesh - см. Исправленный пример выше. Похоже, изменение в Alamofire вызвало сбой в Xcode. Я пересмотрел свой ответ, чтобы обойти эту ошибку Xcode. - person Rob; 12.01.2016
comment
@Rob Я протестировал исправленный код и теперь работает безупречно. - person Jesus Rodriguez; 12.01.2016
comment
@Rob У меня также есть тестовый обновленный код, и он работает нормально. Большое спасибо за быстрое обновление - person Mitesh Jain; 12.01.2016
comment
идеально! Спасибо за это. Мой код теперь намного чище и правильнее. Для всех асинхронных вызовов так должно быть. Просто внесите изменения, если вы хотите анализировать ответ как json: вместо completionHandler(value as? NSDictionary, nil) просто выполните completionHandler(value, nil) и замените NSDictionary в getOrders и makeCall на AnyObject. value - это объект responseObject как он есть. Теперь вы можете проанализировать это значение в обработчике следующим образом: let json = JSON(responseObject!) - person kishorer747; 28.05.2016
comment
@ kishorer747: где именно использовать json = JSON (responseObject!) ?? Извините, я новичок в этом. Спасибо! - person Jenita _Alice4Real; 12.10.2016
comment
@ Jenita_Alice4Real - я не поклонник SwiftyJSON, поэтому я бы вообще не стал добавлять эту строчку. Но если бы вы использовали его, вы бы часто делали это прямо перед вызовом закрытия, и вы бы изменили этот параметр, если бы закрытие из словаря на JSON. - person Rob; 12.10.2016
comment
@ Jenita_Alice4Real - вот ответ на использование SwiftyJSON для анализа json: stackoverflow.com/a/40012727/2177085 - person kishorer747; 13.10.2016
comment
Могу я спросить, почему в этом ответе используются 2 обработчика завершения, а не один? Поскольку это все равно будет работать, просто вызвав функцию makeCall. Просто любопытно - person Tunds; 28.12.2016
comment
Вам следует спросить ОП, поскольку эта часть была взята из его вопроса. Я предполагаю, что в getOrders были детали, которые были опущены для краткости, не относящиеся к непосредственному вопросу. Тем не менее, нет ничего необычного в том, что один метод выполняет работу с низким уровнем сети (создание некоторого стандартного запроса POST, обрабатывает индикаторы активности и т. Д.), А другой - обрабатывает данные на уровне приложения (например, выбор конечной точки, сериализацию JSON в объекты и т. Д.). Как бы то ни было, getOrders предлагает небольшую дополнительную ценность, но я подозреваю, что в его реальном приложении он делал некоторые более значимые вещи. - person Rob; 28.12.2016

Из README Alamofire (курсив мой):

Работа в сети в Alamofire осуществляется асинхронно. Асинхронное программирование может быть источником разочарования для программистов, незнакомых с этой концепцией, но для этого есть очень веские причины.

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

person mattt    schedule 09.12.2014

Ниже приведен полный процесс выполнения действия входа в систему с использованием Alamofire и Swift.

Alamofire v3.3 Swift 2.2 Xcode 7.3

Я использовал GCD и MBProgressHUD для собственного удобства. Реорганизуйте и используйте как хотите :)

func loginBtnTapped(sender: AnyObject) {

    MBProgressHUD.showHUDAddedTo(self.view, animated: true)

    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {

        let loginInfo : Dictionary<String,AnyObject> = ["email":"[email protected]","password":"abc123"]

        self.loginUser(loginInfo) { responseObject, error in

            print("\(responseObject) \n  \(error) ")

            // Parsing JSON Below
            let status = Int(responseObject?.objectForKey("status") as! String)
            if status == 1 {
                // Login Successfull...Move To New VC
            }
            else {
                print(responseObject?.objectForKey("message"))! as! String)
            }
            return
        }
        dispatch_async(dispatch_get_main_queue()) {
            MBProgressHUD.hideHUDForView(self.view, animated: true)
        }
    }

}


func loginUser(parameters:NSDictionary, completionHandler: (NSDictionary?, NSError?) -> ()) {

    self.postRequest("http://qa.company.com/project/index.php/user/login",
                     paramDict: parameters as? Dictionary<String, AnyObject>,
                     completionHandler: completionHandler)
}

func postRequest(urlString: String, paramDict:Dictionary<String, AnyObject>? = nil,
                 completionHandler: (NSDictionary?, NSError?) -> ()) {

    Alamofire.request(.POST, urlString, parameters: paramDict)
        .responseJSON { response in
            switch response.result {
            case .Success(let JSON):
                completionHandler(JSON as? NSDictionary, nil)
            case .Failure(let error):
                completionHandler(nil, error)
            }
    }

}
person n.by.n    schedule 08.04.2016

Подробности

xCode 9.1, Swift 4

Функции:

  • Легко читаемый код
  • Готовые шаблоны (легко добавить больше запросов)
  • Встроенное решение с асинхронной обработкой данных
  • Полные примеры

Образец 1

Вернуть данные с помощью закрытия

Data1.searchRequest(term: "jack johnson") { json, error  in
     print(error ?? "nil")
     print(json ?? "nil")
     print("Update views")
}

Полный образец 1

Класс данных

import Alamofire

class Data1 {

    static fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    static fileprivate let mainQueue = DispatchQueue.main

    fileprivate class func make(request: DataRequest, closure: @escaping (_ json: [String: Any]?, _ error: Error?)->()) {
        request.responseJSON(queue: Data1.queue) { response in

            // print(response.request ?? "nil")  // original URL request
            // print(response.response ?? "nil") // HTTP URL response
            // print(response.data ?? "nil")     // server data
            //print(response.result ?? "nil")   // result of response serialization

            switch response.result {
            case .failure(let error):
                Data1.mainQueue.async {
                    closure(nil, error)
                }

            case .success(let data):
                Data1.mainQueue.async {
                    closure((data as? [String: Any]) ?? [:], nil)
                }
            }
        }
    }

    class func searchRequest(term: String, closure: @escaping (_ json: [String: Any]?, _ error: Error?)->()) {
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        Data1.make(request: request) { json, error in
            closure(json, error)
        }
    }
}

UIViewController

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        Data1.searchRequest(term: "jack johnson") { json, error  in
            print(error ?? "nil")
            print(json ?? "nil")
            print("Update views")
        }
    }
}

Образец 2

Вернуть данные с помощью делегата

// ....
var data = Data2()
data.delegate = self
data.searchRequest(term: "jack johnson")
// ....

extension ViewController: Data2Delegate {
    func searchRequest(response json: [String : Any]?, error: Error?) {
        print(error ?? "nil")
        print(json ?? "nil")
        print("Update views")
    }
}

Полный образец 2

Класс данных

import Alamofire

protocol Data2Delegate: class {
    func searchRequest(response json: [String: Any]?, error: Error?)
}

class Data2 {

    fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    fileprivate let mainQueue = DispatchQueue.main

    weak var delegate: Data2Delegate?

    fileprivate func make(request: DataRequest, closure: @escaping (_ json: [String: Any]?, _ error: Error?)->()) {
        request.responseJSON(queue: queue) { response in

            // print(response.request ?? "nil")  // original URL request
            // print(response.response ?? "nil") // HTTP URL response
            // print(response.data ?? "nil")     // server data
            //print(response.result ?? "nil")   // result of response serialization

            switch response.result {
            case .failure(let error):
                self.mainQueue.async {
                    closure(nil, error)
                }

            case .success(let data):
                self.mainQueue.async {
                    closure((data as? [String: Any]) ?? [:], nil)
                }
            }
        }
    }

    func searchRequest(term: String) {
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        make(request: request) { json, error in
            self.delegate?.searchRequest(response: json, error: error)
        }
    }
}

UIViewController

import UIKit

class ViewController: UIViewController {
    private var data = Data2()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        data.delegate = self
        data.searchRequest(term: "jack johnson")
    }
}

extension ViewController: Data2Delegate {
    func searchRequest(response json: [String : Any]?, error: Error?) {
        print(error ?? "nil")
        print(json ?? "nil")
        print("Update views")
    }
}

Образец 3

Возврат данных с помощью PromiseKit

_ = data.searchRequest(term: "jack johnson").then { response in
      print(response.error ?? "nil")
      print(response.json ?? "nil")
      print("Update views")
      return .void
}

Полный образец 3

Импорт класса данных Alamofire import PromiseKit

class Data3 {

    fileprivate let queue = DispatchQueue(label: "requests.queue", qos: .utility)
    fileprivate let mainQueue = DispatchQueue.main

    fileprivate func make(request: DataRequest) -> Promise<(json:[String: Any]?, error: Error?)> {
         return Promise { fulfill, reject in
            request.responseJSON(queue: queue) { response in

                // print(response.request ?? "nil")  // original URL request
                // print(response.response ?? "nil") // HTTP URL response
                // print(response.data ?? "nil")     // server data
                //print(response.result ?? "nil")   // result of response serialization

                switch response.result {
                    case .failure(let error):
                        self.mainQueue.async {
                            fulfill((nil, error))
                        }

                    case .success(let data):
                        self.mainQueue.async {
                            fulfill(((data as? [String: Any]) ?? [:], nil))
                        }
                }
            }
        }
    }

    func searchRequest(term: String) -> Promise<(json:[String: Any]?, error: Error?)> {
        let request = Alamofire.request("https://itunes.apple.com/search?term=\(term.replacingOccurrences(of: " ", with: "+"))")
        return make(request: request)
    }
}

extension AnyPromise {

    class var void: AnyPromise {
        return AnyPromise(Promise<Void>())
    }
}

UIViewController

import UIKit
import PromiseKit

class ViewController: UIViewController {
    private var data = Data3()
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

        _ = data.searchRequest(term: "jack johnson").then { response in
            print(response.error ?? "nil")
            print(response.json ?? "nil")
            print("Update views")
            return .void
        }
    }
}
person Vasily Bodnarchuk    schedule 01.12.2017
comment
мне нравится образец 1, но я запутался .. как вставить заголовки, тело и метод? - person MAS. John; 21.12.2017
comment
Все примеры полные и рабочие. Создайте новый проект и поэкспериментируйте с кодом. Добавьте новые запросы в date class. И код использования помещен в UIViewController class - person Vasily Bodnarchuk; 21.12.2017

Вот как я это делаю, чтобы разобрать json с помощью Swifty JSON.

Для @Jenita _Alice4Real

func uploadScans(parameters: [String: AnyObject], completionHandler: (AnyObject?, NSError?) -> ()) {
    makePostCall(CommonFunctions().getSaveSKUDataUrl(), parameters: parameters,completionHandler: completionHandler)
}

func makePostCall(url: String, parameters: [String: AnyObject], completionHandler: (AnyObject?, NSError?) -> ()) {
    Alamofire.request(.POST, url, parameters: parameters)
        .responseJSON { response in
            switch response.result {
                case .Success(let value):
                    completionHandler(value, nil)
                case .Failure(let error):
                    completionHandler(nil, error)
            }
    }
}

uploadScans(params) { responseObject, error in
    let json = JSON(responseObject!)
}
person kishorer747    schedule 13.10.2016