arraysswiftnsurlsessionnsurlsessiondownloadtask

How to retrieve array of images from array of url swift


I'm trying to retrieve images from array of url..

I have this function that do the same as I won't but it doesn't work so I tried to use URLSession but didn't know how exactly to make it >>

func downloadImages(imageUrls: [String], completion: @escaping (_ images: [UIImage?]) -> Void) {
        
    var imageArray: [UIImage] = []
    var downloadCounter = 0
    
    for link in imageUrls {
        let url = NSURL(string: link)
        let  downloadQueue = DispatchQueue(label: "imageDowmloadQueue")
        
        downloadQueue.sync {
            downloadCounter += 1
            let data = NSData(contentsOf: url! as URL)
            
            if data != nil {
                //image data ready and need to be converted to UIImage
                imageArray.append(UIImage(data: data! as Data)!)
                
                if downloadCounter == imageArray.count {
                    DispatchQueue.main.async {
                        completion(imageArray)
                        
                    }
                }
            } else {
                print("couldnt download image")
                completion(imageArray)
            }
        }
    }
}

The function I work on :

public func imagesFromURL(urlString: [String],completion: @escaping (_ images: [UIImage?]) -> Void) {
        
        var imageArray: [UIImage] = []
        var downloadCounter = 0
        let  downloadQueue = DispatchQueue(label: "imageDowmloadQueue")

        for link in urlString {
            downloadQueue.sync {
            downloadCounter += 1
        let dataTask = URLSession.shared.dataTask(with: NSURL(string: link)! as URL, completionHandler: { (data, response, error ) in

            if error != nil {
                print(error ?? "No Error")
                return
            }
            
            if data != nil {
                imageArray.append(UIImage(data: data! as Data)!)
                if downloadCounter == imageArray.count {
                    completion(imageArray)

                    }
                } else {
                            print("couldnt download image")
                            completion(imageArray)
                           }
                
            } dataTask.resume()
                }
            }
}

i want to call the function in the collection cell and get the display the first image only from each artwork array..


        //download the first image only to display it:
        if artwork.ImgLink != nil && artwork.ImgLink.count > 0 {
            
            downloadImages(imageUrls: [artwork.ImgLink.first!]) { (images) in
                self.artworkImage.image = images.first as? UIImage
            }
        }


Solution

    1. If you intend to use only the first available UIImage from an array of urls, you do not design a function trying to download all of them. Instead, try to download from the first url, return the downloaded UIImage if it succeeds, or continue with the second url if it fails, repeat until you get an UIImage.
    2. Creating a DispatchQueue in a local function looks dangerous to me. A more common practice is to maintain a queue somewhere else and pass it to the function as a parameter, or reuse one of the predefined global queues using DispatchQueue.global(qos:) if you don't have a specific reason.
    3. Be careful with sync. sync blocks the calling thread until your block finishes in the queue. Generally you use async.
    4. Use a Int counter to control when to finish multiple async tasks (when to call the completion block) works but can be improved by using DispatchGroup, which handles multiple async tasks in a simple and clear way.

    Here's two functions. Both work. firstImage(inURLs:completion:) only return the first UIImage that it downloads successfully. images(forURLs:completion:) tries to download and return them all.

    func firstImage(inURLs urls: [String], completion: @escaping (UIImage?) -> Void) {
        DispatchQueue.global().async {
            for urlString in urls {
                if let url = URL(string: urlString) {
                    if let data = try? Data(contentsOf: url), let image = UIImage(data: data) {
                        DispatchQueue.main.async {
                            completion(image)
                        }
                        return
                    }
                }
            }
            DispatchQueue.main.async {
                completion(nil)
            }
        }
    }
    
    // Use it.
    firstImage(inURLs: artwork.ImgLink) { image in
        self.artworkImage.image = image
    }
    
    
    func images(forURLs urls: [String], completion: @escaping ([UIImage?]) -> Void) {
        let group = DispatchGroup()
        var images: [UIImage?] = .init(repeating: nil, count: urls.count)
        
        for (index, urlString) in urls.enumerated() {
            group.enter()
            DispatchQueue.global().async {
                var image: UIImage?
                if let url = URL(string: urlString) {
                    if let data = try? Data(contentsOf: url) {
                        image = UIImage(data: data)
                    }
                }
                images[index] = image
                group.leave()
            }
        }
        
        group.notify(queue: .main) {
            completion(images)
        }
    }
    
    // Use it.
    images(forURLs: artwork.ImgLink) { images in
        self.artworkImage.image = images.first(where: { $0 != nil }) ?? nil
    }