如何从Alamofire返回值

63

我正在使用Swift创建的API进行URL调用,方法如下:

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
  }

}

我在我的UITableViewController中使用SwiftyJSON库调用此API来填充表格。然而,我的API的returnData总是为空的。Alomofire调用没有问题,因为我可以成功检索到值。我的问题是我应该如何将这个data传递到我的表视图控制器中?

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

与您最初的问题无关,我对您如何使用消费者密钥和秘密完全没有信心(您正在使用Alamofire“authenticate”过程,但也在做一些奇怪的参数处理)。这可能是其中之一。这是您的Web服务API的功能,因此我们无法在此回答,但几乎可以肯定这不正确。 - Rob
这是我使用的服务API的设计方式。文档建议将密钥和密码作为用户名和密码进行基本身份验证。 - u54r
好的,如果它确实使用基本身份验证,则使用authenticate函数,但是params字典的目的是什么?嘿,无论如何都可以,但是似乎很奇怪既要使用authenticate,又将身份验证详细信息作为参数传递给查询... - Rob
5个回答

105
如mattt所指出的那样,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
//

3
没错,这个方法不会“返回”任何东西,因为它是异步运行的。例如,在 viewDidLoad 中调用 getOrders 方法,并在 getOrders 的完成块中(打印responseObject的地方)更新你的属性与异步调用的结果,并从该完成闭包中调用 [self.tableView reloadData]。这样,你的 UITableViewDataSource 方法最终会被调用两次:一次是在你首次加载表格视图时(此时尚无数据可用); 另一次是在你调用 reloadData 之后。 - Rob
1
你的代码看起来不错,但是给了我这个错误。命令由于信号而失败:分段错误: 11,为什么会出现这种情况? - Mitesh Jain
2
@Granola - 啊,我误解了。是Xcode给你报了那个分段错误,而不是你的应用程序。看起来更新后的Alamofire语法会导致编译器出现问题。你可以使用switch语句独立检查.Success.Failure,这样就可以避免这个问题。虽然不够紧凑,但更清楚地处理Alamofire Result enum - Rob
1
@Mitesh - 请参考上面修订后的示例。看起来Alamofire的更改导致了Xcode的崩溃。为了解决这个Xcode的bug,我已经修改了我的答案。 - Rob
2
太好了!感谢您的帮助。我的代码现在更加简洁和正确了。对于所有异步调用,这应该是正确的方式。如果您想将响应解析为JSON,则只需更改completionHandler(value as? NSDictionary, nil)completionHandler(value, nil),并将getOrdersmakeCall中的NSDictionary替换为AnyObject。value就是responseObject本身。您现在可以像这样在处理程序中解析此值:let json = JSON(responseObject!) - kishorer747
显示剩余11条评论

14

从Alamofire的README中可以看出(强调添加):

使用Alamofire进行网络传输是异步完成的。对于不熟悉异步编程的程序员来说,异步编程可能会带来一些困扰,但这样做有非常好的理由。

与其阻塞执行以等待服务器响应,我们可以指定一个回调函数来处理接收到的响应。请求结果仅在响应处理程序的范围内可用。任何取决于从服务器接收到的响应或数据的执行必须在处理程序内完成


3

详细信息

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和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
        }
    }
}

我喜欢示例1,但我有点困惑...如何插入头部、正文和方法? - MAS. John
所有的示例都是完整且可用的。创建一个新项目并尝试使用代码。向“日期类”添加新的查询。并将使用代码放置在“UIViewController类”中。 - Vasily Bodnarchuk

3
以下是使用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":"abc@g.com","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)
            }
    }

}

1

使用Swifty JSON解析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!)
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接