傻瓜式 dataTaskWithURL

dataTaskWithURL for dummies

我一直在学习 iDev,但我仍然无法处理 http 请求。 这似乎很疯狂,但我谈论同步请求的每个人都不理解我。好的,尽可能保持后台队列以提供流畅的 UI 非常重要。但在我的例子中,我从服务器加载 JSON 数据,我需要立即使用这些数据。 我实现它的唯一方法是信号量。可以吗?或者我必须使用其他东西?我尝试了 NSOperation,但实际上我有很多小请求,所以为我创建每个 class 似乎不是容易阅读的代码。

func getUserInfo(userID: Int) -> User {
    var user = User()
    let linkURL = URL(string: "https://server.com")!
    let session = URLSession.shared
    let semaphore = DispatchSemaphore(value: 0)
    let dataRequest = session.dataTask(with: linkURL) { (data, response, error) in
        let json = JSON(data: data!)
        user.userName = json["first_name"].stringValue
        user.userSurname = json["last_name"].stringValue
        semaphore.signal()
    }
    dataRequest.resume()
    semaphore.wait(timeout: DispatchTime.distantFuture)
    return user
}

不要试图强制网络连接同步工作。它总是会导致问题。进行上述调用的任何代码都可能被阻止长达 90 秒(30 秒 DNS 超时 + 60 秒请求超时),等待该请求完成或失败。那是永恒。如果该代码在 iOS 上的主线程上 运行,操作系统将在您到达 90 秒标记之前很久就彻底终止您的应用程序。

相反,将您的代码设计为异步处理响应。基本上:

  • 创建数据结构来保存各种请求的结果,例如从用户那里获取信息。
  • 启动这些请求。
  • 当每个请求返回时,检查您是否拥有做某事所需的所有数据,然后再做。

举一个非常简单的例子,如果你有一个方法用登录用户的名字更新 UI,而不是:

[self updateUIWithUserInfo:[self getUserInfoForUser:user]];

您可以将其重新设计为:

[self getUserInfoFromServerAndRun:^(NSDictionary *userInfo) {
    [self updateUIWithUserInfo:userInfo];
}];

以便当对请求的响应到达时,它会执行 UI 更新操作,而不是尝试启动 UI 更新操作并阻止它等待来自服务器的数据。

如果您需要两件事——比如用户信息和用户已阅读的书籍列表,您可以这样做:

[self getUserInfoFromServerAndRun:^(NSDictionary *userInfo) {
    self.userInfo = userInfo;
    [self updateUI];
}];
[self getBookListFromServerAndRun:^(NSDictionary *bookList) {
    self.bookList = bookList;
    [self updateUI];
}];
...
(void)updateUI
{
    if (!self.bookList) return;
    if (!self.userInfo) return;

    ...
}

或其他什么。积木在这里是你的朋友。 :-)

是的,重新考虑异步工作的代码很痛苦,但最终结果要可靠得多,并且会产生更好的用户体验。

你写别人不理解你,但另一方面也暴露了你不理解异步网络请求是如何工作的

例如,假设您正在为特定时间设置闹钟。

现在您有两种选择来度过接下来的时间。

  1. 什么都不做,坐在闹钟前等待闹钟响起。你做过吗?当然不是,但这正是您对网络请求的看法。
  2. 忽略闹钟直到它响起,做一些有用的事情。这就是异步任务的工作方式。

就编程语言而言,您需要一个完成处理程序,在加载数据时由网络请求调用。在 Swift 中,您为此目的使用了闭包。


为方便起见,为 successfailure 情况声明一个具有关联值的枚举,并将其用作完成处理程序中的 return 值

enum RequestResult {
  case Success(User), Failure(Error)
}

向您的函数添加一个完成处理程序,包括错误情况。 强烈建议始终处理异步任务的错误参数。 当数据任务 returns 它调用完成闭包传递 usererror视情况而定。

func getUserInfo(userID: Int, completion:@escaping (RequestResult) -> ())  {

  let linkURL = URL(string: "https://server.com")!
  let session = URLSession.shared
  let dataRequest = session.dataTask(with: linkURL) { (data, response, error) in
    if error != nil {
      completion(.Failure(error!))
    } else {

      let json = JSON(data: data!)
      var user = User()
      user.userName = json["first_name"].stringValue
      user.userSurname = json["last_name"].stringValue
      completion(.Success(user))
    }
  }
  dataRequest.resume()
}

现在您可以使用以下代码调用该函数:

getUserInfo(userID: 12) { result in
  switch result {
  case .Success(let user) :
    print(user)
    // do something with the user

  case .Failure(let error) :
    print(error)
    // handle the error
  }
}

实际上,您 semaphore 之后的时间点与完成块中的 switch result 行完全相同。

永远不要使用信号量作为不处理异步模式的借口

我希望闹钟示例能够阐明异步数据处理的工作原理,以及为什么获得通知(主动)比等待(被动)更有效。