如何从Swift / Objective C中的HTTP请求返回数据
我正在尝试使用Coinbase的API获取有关我的在线比特币钱包的信息,我正在尝试使用Swift的NSURLSession对象来实现这一点。 也许我在Apple文档中遗漏了一些明显的东西,但在阅读了有关NSURLSession和NSURLSessionTask的信息后,我仍然不明白如何发出HTTP请求然后返回响应的主体,以便身体可以在整个生命中持续存在我的应用程序 截至目前,我只看到能够使用返回void的完成块,或者自己返回void或使用也返回void的完成块的委托。 我想使用稍后在应用程序中从响应中获得的数据,但因为我正在使用完成块,所以我必须在响应到达后立即处理响应数据。
为了说清楚,我想按照下面的伪代码function做一些事情:
func makeHTTPCall(urlString : String) -> String? { create NSURLSession object create request with proper headers and using the passed-in urlString use the session object to send out the request get the response object, extract the response body as a string, and return it }
然后,我可以这样打电话:
let myObject : MyObject = MyObject() let respData : String = myObject.makeHTTPCall("https://coinbase.com/api/v1/account/balance")
此数据返回一个JSON对象字符串,这是我希望在响应及其完成块的生命周期之后保留的字符串。 我怎么能在Swift或Objective C中做到这一点,因为我可以在Xcode 6中使用它?
编辑:已经发布了两个答案,但他们错过了这个问题的基本点。 我需要返回从响应中收到的数据。 这两个答案(以及我在SO上看到的所有其他答案)只是打印收到的数据。 我希望代码不使用返回void的完成处理程序,而是返回数据,以便以后可以在应用程序的生命周期中使用它。 如果我的问题有任何不清楚的地方,请告诉我,虽然我看不出如何更清楚。
在编辑问题时,您说:
我需要返回从响应中收到的数据。 这两个答案(以及我在SO上看到的所有其他答案)只是打印收到的数据。 我希望代码不使用返回void的完成处理程序,而是返回数据,以便以后可以在应用程序的生命周期中使用它。 如果我的问题有任何不清楚的地方,请告诉我,虽然我看不出如何更清楚。
我理解这种策略的吸引力,因为它感觉非常符合逻辑。 问题是您的网络请求应始终异步运行(例如,使用您提到的完成处理程序模式)。
虽然有一些技术使函数“等待”异步请求完成(即使异步NSURLSession
方法同步运行或使用旧的同步网络请求方法之一),但由于以下原因,这是一个非常糟糕的主意:
-
如果你从主线程执行此操作,则会导致糟糕的用户体验(当请求正在进行时,应用程序将无响应,并且用户将不知道应用程序是否忙于执行某些操作或是否因某些未知原因而冻结)。
-
同样,如果你从主线程执行此操作,你也有可能让iOS“看门狗”进程终止你的应用程序(因为如果你在错误的时间阻止主队列超过几秒钟,特别是当应用程序来到前台,操作系统将毫不客气地终止你的应用程序)。 有关执行同步网络请求的问题的讨论,请参阅技术问答#1693 。
-
我们通常更喜欢异步网络技术,因为它们提供了同步技术不可用的更多function(例如,使请求可取消,在使用基于委托的网络请求时提供进度更新等)。
您确实应该使用其他问题建议的完成处理程序模式,并在这些处理程序中管理应用程序的更改状态。 在那些绝对不能让用户继续进行某些网络请求的情况下(例如,在你确认他们的比特币余额之前你不能让用户购买东西,并且在他们登录之前你不能这样做),然后改变用于指示此类请求正在进行的UI。 例如,调暗UI,禁用控件,弹出活动指示器视图(也称为“微调器”)等。只有这样才会启动请求。 完成请求后,您将恢复UI。 我知道这似乎很多,但是当用户完全无法继续直到先前的请求完成时,这是正确的方法。
我还要考虑是否真的必须强制用户等待先前的网络请求完成。 有时,在网络请求正在进行时,您可以让用户执行/查看其他内容。 是的,有时候这是不可能的,但如果你能在你的应用程序中找到那些机会,你最终会得到一个更优雅的用户体验。
我知道这个问题并将此代码用于同步请求:
func synchronousRequest() -> NSDictionary { //creating the request let url: NSURL! = NSURL(string: "exampledomain/...") var request = NSMutableURLRequest(URL: url) request.HTTPMethod = "GET" request.addValue("application/json", forHTTPHeaderField: "Content-Type") var error: NSError? var response: NSURLResponse? let urlData = NSURLConnection.sendSynchronousRequest(request, returningResponse: &response, error: &error) error = nil let resultDictionary: NSDictionary = NSJSONSerialization.JSONObjectWithData(urlData!, options: NSJSONReadingOptions.MutableContainers, error: &error) as! NSDictionary return resultDictionary }
您要求的是同步网络请求。 有很多方法可以做到这一点,比如… NSData的init(contentsOfURL aURL:NSURL!)NSURLConnection的同步请求方法……等等。
这些方法将阻止当前线程直到它们完成 – 这可能是一个潜在的长时间。 网络请求可能会有很长的超时,可能是设备放弃前几分钟。 带有URL内容的NSData的init将返回NSData,而不是void,并且不会异步执行。 它将阻塞直到它完成,这就是为什么建议不要从主线程执行这些类型的请求。 UI将被冻结,直到完成为止。
通常,不鼓励使用同步网络方法。 出于多种原因,异步网络请求是非常优选的。 使用将完成块作为参数的异步方法不会阻止您在应用程序的其他位置使用返回的数据。 当网络请求完成(成功或失败)时执行该块,并传递数据,响应元数据和错误。 您可以自由地使用该数据执行所需操作 – 没有什么可以阻止您将其保留,将其传递给另一个对象等。根据您的注释,听起来您想要获取由网络请求和将它设置为某个属性的值 – 使用将块作为完成处理程序的异步方法完全可行。
在objective-C中,您可以使用__block并在操作完成时获取数据:
__block NSData *myData; NSURLSession *session = [NSURLSession sharedSession]; [[session dataTaskWithURL:[NSURL URLWithString:urlString] completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { myData = data; }] resume];