iOS Picture Framework only makes one network request when requesting the same URL at the same time

When uiimageview from many cell s in a tableview requests pictures with the same address, how to ensure that only one network request is made for the same url to avoid unnecessary network requests to improve efficiency.

For this problem, if for the same url request, when any one request has not completed saving the local cache, the other requests first look at the local cache, which is not found at this time, so network requests will also be sent, which really has efficiency problems. (

How do I solve this problem with my own picture requests? The first thing I think about is using a dictionary with url as the key to find the key before every network request. If not, I request a network download. If not, then there is a problem to be solved, that is, how do I set the image without requesting it and at the right time? The awkward way to think of this is, of course, to tell another uiimage to drop the set image method again when the first request is completed and the local cache is set. This is a very efficient way to get cache access. It also has to inherit the uiimageview and respond to notifications in the new uiimageview, which is obviously not a good way. Check the literature to see how third-party picture loading libraries work.

The general idea is to maintain a dictionary with URLs as keys, value is a fallback block array corresponding to the same url, add key-value to the dictionary when the first request is made, remove the value array by key when the second request is made with the same url, add a new callbackBlock to the array, walk through the callback array at the end of the party request, and delete the key-value.

Processing of swift picture frame KingFisher:

//KingFisher Processing Same URL Multiple requests when not returned
        let downloadTask: DownloadTask
        if let existingTask = sessionDelegate.task(for: url) {
//Url as key, if a task with the same URL already exists, take out the requested task downloadTask
= sessionDelegate.append(existingTask, url: url, callback: callback) } else { let sessionDataTask = session.dataTask(with: request) sessionDataTask.priority = options.downloadPriority downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback) }
//Remove Request Task from url
func task(for url: URL) -> SessionDataTask? { lock.lock() defer { lock.unlock() } return tasks[url] }
//When the first request was made, no task added a new requested task
func add( _ dataTask: URLSessionDataTask, url: URL, callback: SessionDataTask.TaskCallback)
-> DownloadTask { lock.lock() defer { lock.unlock() } // Create a new task if necessary. let task = SessionDataTask(task: dataTask) task.onCallbackCancelled.delegate(on: self) { [unowned task] (self, value) in let (token, callback) = value let error = KingfisherError.requestError(reason: .taskCancelled(task: task, token: token)), [callback])) // No other callbacks waiting, we can clear the task now. if !task.containsCallbacks { let dataTask = task.task self.remove(dataTask) } } let token = task.addCallback(callback) tasks[url] = task return DownloadTask(sessionTask: task, cancelToken: token) }

Execute the requested task if the request has not started

// Start the session task if not started yet.
        if !sessionTask.started {
            sessionTask.onTaskDone.delegate(on: self) { (self, done) in
                // Underlying downloading finishes.
                // result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback]
                let (result, callbacks) = done

                // Before processing the downloaded data.
                do {
                    let value = try result.get()
                        didFinishDownloadingImageForURL: url,
                        with: value.1,
                        error: nil
                } catch {
                        didFinishDownloadingImageForURL: url,
                        with: nil,
                        error: error

                switch result {
                // Download finished. Now process the data to an image.
                case .success(let (data, response)):
                    let processor = ImageDataProcessor(
                        data: data, callbacks: callbacks, processingQueue: options.processingQueue)
                    processor.onImageProcessed.delegate(on: self) { (self, result) in
                        // `onImageProcessed` will be called for `callbacks.count` times, with each
                        // `SessionDataTask.TaskCallback` as the input parameter.
                        // result: Result<Image>, callback: SessionDataTask.TaskCallback
                        let (result, callback) = result

                        if let image = try? result.get() {
                            self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response)

                        let imageResult = { ImageLoadingResult(image: $0, url: url, originalData: data) }
                        let queue = callback.options.callbackQueue
                        queue.execute { callback.onCompleted?.call(imageResult) }
                    processor.process()//Traverse around here to execute correspondence URl All callback

                case .failure(let error):
callbacks.forEach { callback
in let queue = callback.options.callbackQueue queue.execute { callback.onCompleted?.call(.failure(error)) } } } } delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request) sessionTask.resume() }
A specific traversal executes all callback s for the corresponding URl
func process() {

    private func doProcess() {
        var processedImages = [String: Image]()
        for callback in callbacks {
            let processor = callback.options.processor
            var image = processedImages[processor.identifier]
            if image == nil {
                image = processor.process(item: .data(data), options: callback.options)
                processedImages[processor.identifier] = image

            let result: Result<Image, KingfisherError>
            if let image = image {
                var finalImage = image
                if let imageModifier = callback.options.imageModifier {
                    finalImage = imageModifier.modify(image)
                if callback.options.backgroundDecode {
                    finalImage = finalImage.kf.decoded
                result = .success(finalImage)
            } else {
                let error = KingfisherError.processorError(
                    reason: .processingFailed(processor: processor, item: .data(data)))
                result = .failure(error)
  , callback))


Posted by dnszero on Wed, 18 May 2022 19:59:40 +0300