【问题标题】:iOS - Asynchronous requests and UI interactionsiOS - 异步请求和 UI 交互
【发布时间】:2015-08-05 17:14:57
【问题描述】:

我有一个使用 Alamofire 从 Google Image Search API 检索图像并将它们显示在 collectionView 中的页面。一切正常,但是当请求仍在加载时,我根本无法与 UI 交互,无论是单击按钮还是滚动 collectionView。这是正常行为吗?如果请求需要很长时间,我希望用户至少能够按“后退”按钮,或者在处理新请求时向上和向下滚动 collectionView。

Alamofire.request(.GET, googleImageApiUrl, parameters: [:]).responseJSON { (request, response, JSON, error) in
    if let results = JSON as? NSDictionary {
        if let images = results["responseData"]!["results"] as? NSArray {
            for image in images {
                let url = NSURL(string: image["unescapedUrl"] as! String)!
                if let data = NSData(contentsOfURL: url) {
                    self.imageData.append(data)
                }
            }

            dispatch_async(dispatch_get_main_queue()) {
                self.collectionView.reloadData()
            }
        }
    }
}

总而言之,一旦发起请求,用户无法以任何方式与页面交互,直到 collectionView 重新加载其数据。我想如果请求是异步的,那么我仍然可以与 UI 交互。这是 Alamofire 限制、一般限制还是我做错了什么?

【问题讨论】:

    标签: ios swift asynchronous uicollectionview alamofire


    【解决方案1】:

    我相信您现在知道,主队列的内部dispatch_async 是多余的,因为您已经在主线程上。而且,正如其他人指出的那样,您绝对不想在此块内执行NSData(contentsOfURL:...),而是也异步请求这些图像。正如 Matt 指出的 (+1),Alamofire 触手可及,所以请使用它。

    但这里有一个更深层次的问题:您正在预先加载图像!你不应该那样做。您也不应该将它们加载到数组中。你应该改为:

    • 延迟加载:您应该让每个单元格仅在需要该单元格时才异步请求自己的图像,而不是提前尝试这样做。这意味着让cellForItemAtIndexPath 为这些单元格请求单个图像,而不是让viewDidLoad 将它们全部加载。

    • 缓存:而不是将图像保存在数组中(在这种情况下,您很容易遇到内存问题),您应该将它们保存在 NSCache 或类似的东西中。这样,如果您在遇到内存压力时必须清除它们,您可以安全地这样做,但要保持应用程序平稳运行。

      除非您的图片都 (a) 非常小;并且 (b) 数量不多,您需要一个模型来支持您的集合视图,该模型假设您可能无法同时将所有图像保存在内存中。

    • 优先考虑可见单元格的图像:如果某个单元格被重复使用,您应该取消之前对该单元格的请求。否则,如果您在集合视图中快速滚动,对可见单元格的请求将积压在对早已滚动出视图的单元格的请求之后。在慢速网络连接时,这成为一个关键的改进。

    • 防止超时:确保防止请求超时。如果您注意前一点,这个问题会有所减少,但特别是在集合视图上,它仍然是一个真正的风险。您可以增加网络请求的超时时间,或者更好的是,将请求包装在异步 NSOperation 对象中,然后使用 NSOperationQueue 来管理请求,并使用 4 或 5 等合理值的 maxConcurrentOperationCount

    • 预热:之前,我建议延迟加载。实际上,如果你想变得更漂亮,你可以将可见单元格的延迟加载与可见单元格附近的单元格“预热”(或预取或急切获取)结合起来,以便在用户滚动时准备好。

      这是一个相当复杂的概念,所以这是您目前最不应该担心的事情。但是,如果您对该概念感兴趣,请参阅 WWDC 2014 视频Introducing the Photos Frameworks,了解复杂预热机制的外观示例。 (注意,我不是建议你使用 Photos.framework,但它是一个聪明的抓取系统的例子。)

    我在上面的许多观察中暗示我们需要针对实际场景优化我们的应用程序。我们使用高速 wifi 连接进行了大部分测试,有时忘记了在现实世界中,用户正在处理参差不齐的蜂窝连接。因此,我建议使用模拟非常弱的蜂窝连接的网络链接调节器运行该应用程序。只有当您这样做时,上述关于延迟加载、优先可见单元格和超时的要点的重要性才会显着减轻。


    顺便说一句,如果您不想在其中一些问题中迷失方向,可以考虑使用为异步图像检索设计的UIImageView 类别,例如AlamofireImage。它最大限度地减少了您必须编写的代码量。

    【讨论】:

    • 当之无愧的慢拍
    【解决方案2】:

    尝试下面的方法可能会解决您的问题,它可以将请求发送到具有高优先级的后台队列,使其在不锁定 UI 的情况下尽可能快

    Alamofire.request(.GET, googleImageApiUrl, parameters: [:]).responseJSON { (request, response, JSON, error) in
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
        if let results = JSON as? NSDictionary {
            if let images = results["responseData"]!["results"] as? NSArray {
                for image in images {
                    let url = NSURL(string: image["unescapedUrl"] as! String)!
                    if let data = NSData(contentsOfURL: url) {
                        self.imageData.append(data)
                    }
                }
    
                dispatch_async(dispatch_get_main_queue()) {
                    self.collectionView.reloadData()
                }
            }
        }
      }
    }
    

    【讨论】:

      【解决方案3】:

      Alamofire 网络异步。那里不用担心。不过,问题可能就在这里:

      let url = NSURL(string: image["unescapedUrl"] as! String)!
      if let data = NSData(contentsOfURL: url) {
      

      如果我怀疑该 URL 是远程 URL,那么您现在正在重复网络同步NSData(contentsOfURL:) 适合在主线程上通过网络获取内容。如果您要在网络上获取更多数据,请使用适当的网络代码 - 甚至可能更多地使用 Alamofire...

      【讨论】:

      • 那么在全局并发队列而不是主队列上使用 NSData 获取图像会有什么缺点?
      • 嗯?您没有在全局并发队列中获取图像;那就是问题所在。你自己说过:如果主队列被阻塞,用户无法交互(实际上系统会立即将你的应用杀死)。
      • 对,所以我在问我是否可以将这些 NSData 请求发送到不同的队列而不是主队列,这样主队列就不会被阻塞。
      • 当然,这就是我要说的。事实上,此时其他人已经窃取了我的答案并将其转化为实际代码。试试那个代码!
      • 所以澄清一下 - 实际的 Alamofire 请求是异步的,但是当它返回时,其中的内容会在主队列上串行执行(当然,除非我将它发送到不同的队列手动)。正确的?嗯。。说的对,哈哈。现在这一切都为我而来。谢谢。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2019-07-30
      • 1970-01-01
      • 2010-12-16
      • 2016-04-06
      • 2014-07-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多