• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

有没有一种方法可以使用URLSession.shared.dataTask并行请求多个不同的资源

用户头像
it1352
帮助5

问题说明

我在这里找到了这段代码,该代码有关如何同时下载图像而没有任何破损,

I found this piece of code here on how to download images simultaneously without any breakages,

    func loadImageRobsAnswer(with urlString: String?) {
    // cancel prior task, if any


    weak var oldTask = currentTask
    currentTask = nil
    oldTask?.cancel()



    // reset imageview's image

    self.image = nil

    // allow supplying of `nil` to remove old image and then return immediately

    guard let urlString = urlString else { return }

    // check cache



    if let cachedImage = DataCache.shared.object(forKey: urlString) {



        self.transition(toImage: cachedImage as? UIImage)
        //self.image = cachedImage
        return
    }

    // download

    let url = URL(string: urlString)!
    currentURL = url

    let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
        self?.currentTask = nil



        if let error = error {


            if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
                return
            }

            print(error)
            return
        }

        guard let data = data, let downloadedImage = UIImage(data: data) else {
            print("unable to extract image")
            return
        }

        DataCache.shared.saveObject(object: downloadedImage, forKey: urlString)

        if url == self?.currentURL {

            DispatchQueue.main.async {

                self?.transition(toImage: downloadedImage)

            }
        }
    }

    // save and start new task

    currentTask = task
    task.resume()
}

但是此代码用于UIImageView扩展

However this code is used in a UIImageView extension,

    public extension UIImageView {
  private static var taskKey = 0
  private static var urlKey = 0

  private var currentTask: URLSessionTask? {
    get { return objc_getAssociatedObject(self, &UIImageView.taskKey) as? URLSessionTask }
    set { objc_setAssociatedObject(self, &UIImageView.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}

private var currentURL: URL? {
    get { return objc_getAssociatedObject(self, &UIImageView.urlKey) as? URL }
    set { objc_setAssociatedObject(self, &UIImageView.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}}}

这就是我试图使此代码动态化的方式,因此它不仅限于UIImageView,而且可以用于下载多个资源.

This is how i have tried to make this code dynamic so it wont be limited to only an UIImageView but can be used to download multiple resources.

class DataRequest {
private static var taskKey = 0
private static var urlKey = 0
static let shared = DataRequest()
    typealias ImageDataCompletion = (_ image: UIImage?, _ error: Error? ) -> Void

private var currentTask: URLSessionTask? {
    get { return objc_getAssociatedObject(self, &DataRequest.taskKey) as? URLSessionTask }
    set { objc_setAssociatedObject(self, &DataRequest.taskKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}

private var currentURL: URL? {
    get { return objc_getAssociatedObject(self, &DataRequest.urlKey) as? URL }
    set { objc_setAssociatedObject(self, &DataRequest.urlKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) }
}


 func downloadImage(with urlString: String?, completion: @escaping ImageDataCompletion) {



    weak var oldTask = currentTask
    currentTask = nil
    oldTask?.cancel()





    guard let urlString = urlString else { return }





    if let cachedImage = DataCache.shared.object(forKey: urlString) {
         DispatchQueue.main.async {
        completion(cachedImage as? UIImage ,nil)
        }
       // self.transition(toImage: cachedImage as? UIImage)
        //self.image = cachedImage
        return
    }

    // download

    let url = URL(string: urlString)!
    currentURL = url

    let task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
        self?.currentTask = nil



        if let error = error {


            if (error as NSError).domain == NSURLErrorDomain && (error as NSError).code == NSURLErrorCancelled {
                return
            }

             completion(nil,nil)
            return
        }

        guard let data = data, let downloadedImage = UIImage(data: data) else {
            print("unable to extract image")
            return
        }

        DataCache.shared.saveObject(object: downloadedImage, forKey: urlString)

        if url == self?.currentURL {

            DispatchQueue.main.async {

                 completion(downloadedImage ,nil)

            }
        }
    }

    // save and start new task

    currentTask = task
    task.resume()
}

这样我现在就可以在像这样的UIImageview扩展中使用它

So that i can now use it in an UIImageview extension like this

    extension UIImageView {
       func setImage(url: String?) {

    self.image = nil
    DataRequest.shared.downloadImage(with: url) { (image, error) in
        DispatchQueue.main.async {
            self.image = image


        }
    }

}
    }

在UICollectionView上使用我的方法的结论是将错误的图像显示到单元格中并复制,如何防止这种情况发生?

Concluding using my approach on a UICollectionView is displaying the wrong images into a cell and duplicating ,How do i prevent this?

正确答案

#1

您问:

是否有一种方法可以使用URLSession.shared.dataTask

默认情况下,它会并行执行请求.

By default, it does perform requests in parallel.

让我们退后一步:在您之前的问题中,您正在询问如何实现类似于Kingfisher的UIImageView扩展.在我的答案中,我提到使用objc_getAssociatedObjectobjc_setAssociatedObject来实现这一目标.但是在这里的问题中,您已经采用了关联的对象逻辑并将其放在您的DataRequest对象中.

Let’s step back for a second: In your prior question, you were asking how to implement a Kingfisher-like UIImageView extension. In my answer, I mentioned using objc_getAssociatedObject and objc_setAssociatedObject to achieve that. But in your question here, you’ve taken that associated object logic and put it in your DataRequest object.

您的思维过程中,将异步图像检索逻辑从UIImageView中拉出是一个好主意:您可能需要请求按钮的图像.您可能会使用通用的异步获取图像"例程,该例程与任何UIKit对象完全分离.因此,从扩展中抽象网络层代码是一个好主意.

Your thought process, to pull the asynchronous image retrieval logic out of the UIImageView is a good idea: You may want to request images for buttons. You might a general "fetch image asynchronously" routine, completely separate from any UIKit objects. So abstracting the network layer code out of the extension is an excellent idea.

但是异步图像检索UIImageView/UIButton扩展背后的整个想法是,我们想要一个UIKit控件,它不仅可以执行异步请求,而且如果重用带有控件的单元格,它将取消在启动下一个异步请求之前(如果有的话).这样,如果我们快速向下滚动至图像80至99,则对单元0至79的请求将被取消,可见图像也不会积压在所有这些旧图像请求之后.

But the whole idea behind asynchronous image retrieval UIImageView/UIButton extensions is that we want a UIKit control where not only can it perform asynchronous requests, but that if the cell with the control is reused, that it will cancel the prior asynchronous request (if any) before starting the next one. That way, if we scroll quickly down to images 80 through 99, the requests for cells 0 through 79 will be canceled, and the visible images won’t get backlogged behind all these old image requests.

但是要实现这一点,这意味着控件需要某种方式来以某种方式跟踪对该重用单元格的先前请求.而且因为我们无法在UIImageView扩展名中添加存储的属性,所以这就是我们使用objc_getAssociatedObjectobjc_setAssociatedObject模式的原因.但这必须在图像视图中.

But to achieve that, that means that the control needs some way to keep track of the prior request for that reused cell somehow. And because we can’t add stored properties in a UIImageView extension, that’s why we use the objc_getAssociatedObject and objc_setAssociatedObject pattern. But that has to be in the image view.

不幸的是,在上面的代码中,关联的对象位于DataRequest对象中.首先,正如我试图概述的那样,整个想法是图像视图必须跟踪对该控件的先前请求.在"DataRequest"对象中放置跟踪先前的请求"会破坏该目的.其次,值得注意的是,您不需要自己的类型(如DataRequest)中的关联对象.您只会拥有一个存储的财产.扩展UIImageView之类的另一种类型时,只需要遍历此关联的对象无聊即可.

Unfortunately, in your code above, the associated object is in your DataRequest object. First, as I’ve tried to outline, the whole idea is that the image view must keep track of the prior request for that control. Putting this "keep track of the prior request" inside the DataRequest object defeats that purpose. Second, it’s worth noting that you don’t need associated objects in your own types, like DataRequest. You’d just have a stored property. You only need to go through this associated object silliness when extending another type, such as UIImageView.

下面是一个简短的示例,我快速地展示了用于异步图像检索的UIImageView扩展名.请注意,这并没有从扩展名中提取网络代码,但是请注意,用于跟踪先前请求的关联对象逻辑必须保留在扩展名中.

Below, is a quick example that I whipped together showing a UIImageView extension for asynchronous image retrieval. Note, this doesn’t have the abstraction of the network code out of the extension, but do note that the associated object logic to keep track of the prior request must remain with the extension.

private var taskKey: Void?

extension UIImageView {
    private static let imageProcessingQueue = DispatchQueue(label: Bundle.main.bundleIdentifier!   ".imageprocessing", attributes: .concurrent)

    private var savedTask: URLSessionTask? {
        get { return objc_getAssociatedObject(self, &taskKey) as? URLSessionTask }
        set { objc_setAssociatedObject(self, &taskKey, newValue, .OBJC_ASSOCIATION_RETAIN) }
    }

    /// Set image asynchronously.
    ///
    /// - Parameters:
    ///   - url: `URL` for image resource.
    ///   - placeholder: `UIImage` of placeholder image. If not supplied, `image` will be set to `nil` while request is underway.
    ///   - shouldResize: Whether the image should be scaled to the size of the image view. Defaults to `true`.

    func setImage(_ url: URL, placeholder: UIImage? = nil, shouldResize: Bool = true) {
        savedTask?.cancel()
        savedTask = nil

        image = placeholder
        if let image = ImageCache.shared[url] {
            DispatchQueue.main.async {
                UIView.transition(with: self, duration: 0.1, options: .transitionCrossDissolve, animations: {
                    self.image = image
                }, completion: nil)
            }
            return
        }

        var task: URLSessionTask!
        let size = bounds.size * UIScreen.main.scale
        task = URLSession.shared.dataTask(with: url) { [weak self] data, response, error in
            guard
                error == nil,
                let httpResponse = response as? HTTPURLResponse,
                (200..<300) ~= httpResponse.statusCode,
                let data = data
            else {
                return
            }

            UIImageView.imageProcessingQueue.async { [weak self] in
                var image = UIImage(data: data)
                if shouldResize {
                    image = image?.scaledAspectFit(to: size)
                }

                ImageCache.shared[url] = image

                DispatchQueue.main.async {
                    guard
                        let self = self,
                        let savedTask = self.savedTask,
                        savedTask.taskIdentifier == task.taskIdentifier
                    else {
                        return
                    }
                    self.savedTask = nil

                    UIView.transition(with: self, duration: 0.1, options: .transitionCrossDissolve, animations: {
                        self.image = image
                    }, completion: nil)
                }
            }
        }
        task.resume()
        savedTask = task
    }
}

class ImageCache {
    static let shared = ImageCache()

    private let cache = NSCache<NSURL, UIImage>()
    private var observer: NSObjectProtocol?

    init() {
        observer = NotificationCenter.default.addObserver(forName: UIApplication.didReceiveMemoryWarningNotification, object: nil, queue: nil) { [weak self] _ in
            self?.cache.removeAllObjects()
        }
    }

    deinit {
        NotificationCenter.default.removeObserver(observer!)
    }

    subscript(url: URL) -> UIImage? {
        get {
            return cache.object(forKey: url as NSURL)
        }

        set {
            if let data = newValue {
                cache.setObject(data, forKey: url as NSURL)
            } else {
                cache.removeObject(forKey: url as NSURL)
            }
        }
    }
}

这是我调整大小的例程:

And this is my resizing routine:

extension UIImage {

    /// Resize the image to be the required size, stretching it as needed.
    ///
    /// - parameter newSize:      The new size of the image.
    /// - parameter contentMode:  The `UIView.ContentMode` to be applied when resizing image.
    ///                           Either `.scaleToFill`, `.scaleAspectFill`, or `.scaleAspectFit`.
    ///
    /// - returns:                Return `UIImage` of resized image.

    func scaled(to newSize: CGSize, contentMode: UIView.ContentMode = .scaleToFill) -> UIImage? {
        switch contentMode {
        case .scaleToFill:
            return filled(to: newSize)

        case .scaleAspectFill, .scaleAspectFit:
            let horizontalRatio = size.width  / newSize.width
            let verticalRatio   = size.height / newSize.height

            let ratio: CGFloat!
            if contentMode == .scaleAspectFill {
                ratio = min(horizontalRatio, verticalRatio)
            } else {
                ratio = max(horizontalRatio, verticalRatio)
            }

            let sizeForAspectScale = CGSize(width: size.width / ratio, height: size.height / ratio)
            let image = filled(to: sizeForAspectScale)
            let doesAspectFitNeedCropping = contentMode == .scaleAspectFit && (newSize.width > sizeForAspectScale.width || newSize.height > sizeForAspectScale.height)
            if contentMode == .scaleAspectFill || doesAspectFitNeedCropping {
                let subRect = CGRect(
                    x: floor((sizeForAspectScale.width - newSize.width) / 2.0),
                    y: floor((sizeForAspectScale.height - newSize.height) / 2.0),
                    width: newSize.width,
                    height: newSize.height)
                return image?.cropped(to: subRect)
            }
            return image

        default:
            return nil
        }
    }

    /// Resize the image to be the required size, stretching it as needed.
    ///
    /// - parameter newSize:   The new size of the image.
    ///
    /// - returns:             Resized `UIImage` of resized image.

    func filled(to newSize: CGSize) -> UIImage? {
        let format = UIGraphicsImageRendererFormat()
        format.opaque = false
        format.scale = scale

        return UIGraphicsImageRenderer(size: newSize, format: format).image { _ in
            draw(in: CGRect(origin: .zero, size: newSize))
        }
    }

    /// Crop the image to be the required size.
    ///
    /// - parameter bounds:    The bounds to which the new image should be cropped.
    ///
    /// - returns:             Cropped `UIImage`.

    func cropped(to bounds: CGRect) -> UIImage? {
        // if bounds is entirely within image, do simple CGImage `cropping` ...

        if CGRect(origin: .zero, size: size).contains(bounds) {
            return cgImage?.cropping(to: bounds * scale).flatMap {
                UIImage(cgImage: $0, scale: scale, orientation: imageOrientation)
            }
        }

        // ... otherwise, manually render whole image, only drawing what we need

        let format = UIGraphicsImageRendererFormat()
        format.opaque = false
        format.scale = scale

        return UIGraphicsImageRenderer(size: bounds.size, format: format).image { _ in
            let origin = CGPoint(x: -bounds.minX, y: -bounds.minY)
            draw(in: CGRect(origin: origin, size: size))
        }
    }

    /// Resize the image to fill the rectange of the specified size, preserving the aspect ratio, trimming if needed.
    ///
    /// - parameter newSize:   The new size of the image.
    ///
    /// - returns:             Return `UIImage` of resized image.

    func scaledAspectFill(to newSize: CGSize) -> UIImage? {
        return scaled(to: newSize, contentMode: .scaleAspectFill)
    }

    /// Resize the image to fit within the required size, preserving the aspect ratio, with no trimming taking place.
    ///
    /// - parameter newSize:   The new size of the image.
    ///
    /// - returns:             Return `UIImage` of resized image.

    func scaledAspectFit(to newSize: CGSize) -> UIImage? {
        return scaled(to: newSize, contentMode: .scaleAspectFit)
    }

    /// Create smaller image from `Data`
    ///
    /// - Parameters:
    ///   - data: The image `Data`.
    ///   - maxSize: The maximum edge size.
    ///   - scale: The scale of the image (defaults to device scale if 0 or omitted.
    /// - Returns: The scaled `UIImage`.

    class func thumbnail(from data: Data, maxSize: CGFloat, scale: CGFloat = 0) -> UIImage? {
        guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else {
            return nil
        }

        return thumbnail(from: imageSource, maxSize: maxSize, scale: scale)
    }

    /// Create smaller image from `URL`
    ///
    /// - Parameters:
    ///   - data: The image file URL.
    ///   - maxSize: The maximum edge size.
    ///   - scale: The scale of the image (defaults to device scale if 0 or omitted.
    /// - Returns: The scaled `UIImage`.

    class func thumbnail(from fileURL: URL, maxSize: CGFloat, scale: CGFloat = 0) -> UIImage? {
        guard let imageSource = CGImageSourceCreateWithURL(fileURL as CFURL, nil) else {
            return nil
        }

        return thumbnail(from: imageSource, maxSize: maxSize, scale: scale)
    }

    private class func thumbnail(from imageSource: CGImageSource, maxSize: CGFloat, scale: CGFloat) -> UIImage? {
        let scale = scale == 0 ? UIScreen.main.scale : scale
        let options: [NSString: Any] = [
            kCGImageSourceThumbnailMaxPixelSize: maxSize * scale,
            kCGImageSourceCreateThumbnailFromImageAlways: true
        ]
        if let scaledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) {
            return UIImage(cgImage: scaledImage, scale: scale, orientation: .up)
        }
        return nil
    }

}

extension CGSize {
    static func * (lhs: CGSize, rhs: CGFloat) -> CGSize {
        return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
    }
}

extension CGPoint {
    static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
        return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
    }
}

extension CGRect {
    static func * (lhs: CGRect, rhs: CGFloat) -> CGRect {
        return CGRect(origin: lhs.origin * rhs, size: lhs.size * rhs)
    }
}

话虽如此,我们确实应该将并发请求限制在合理的范围内(一次4-6),以使它们不会尝试在之前的请求完成(或取消)之前开始以避免超时.典型的解决方案是使用异步Operation子类包装请求,将其添加到操作队列中,并将maxConcurrentOperationCount约束为您选择的任何值.


That having been said, we really should constrain our concurrent requests to something reasonable (4-6 at a time) so that they don’t try to start until the prior requests are done (or are canceled) to avoid timeouts. The typical solution is wrapping the requests with asynchronous Operation subclasses, add them to an operation queue, and constrain the maxConcurrentOperationCount to whatever value you choose.

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /reply/detail/tanhcbabjf
系列文章
更多 icon
同类精品
更多 icon
继续加载