Search code examples
iosswiftswift2nsurlsessionnsoperationqueue

Append images in array in sequence


I want to add images in array in sequence after downloading. I am appending images in array after downloading one by one but they are not in sequence. Can any one please tell me what is best way to do this.

var queue: NSOperationQueue = {
    let _queue = NSOperationQueue()
    _queue.maxConcurrentOperationCount = 4
    return _queue
}()

var imageArrayNsData : [NSData] = []

let session = NSURLSession.sharedSession()

@IBAction func didClickOnStart(sender: AnyObject) {

    queue.cancelAllOperations()

    let completionOperation = NSBlockOperation() {
        print("all done")
    }

    for (index, imageURL) in imageURLs.enumerate() {
        let operation = ImageNetworkOperation(session: session, urlString: imageURL) { image, response, error in

            let dtA : NSData = NSData(data: UIImageJPEGRepresentation(image!, 0.75)!)
            self.imageArrayNsData.append(dtA)
            print("JPEG download\(index)")
        }

        completionOperation.addDependency(operation)
        queue.addOperation(operation)
    }

    NSOperationQueue.mainQueue().addOperation(completionOperation)        
}

The resulting output:

JPEG download0
JPEG download2
JPEG download1
JPEG download3
all done


Solution

  • You should change your model such that it doesn't matter what order the images are downloaded. For example, you have your array of image URL strings:

    var imageURLs: [String]
    

    So, your NSData should be stored in a dictionary (or NSCache) keyed by that URL string:

    var imageData = [String: NSData]()
    

    Then when you download the data, you can update this dictionary:

    self.imageData[imageURL] = dtA
    

    Then, when you need to retrieve this data later, you can use the imageURL, e.g.:

    let data = imageData[imageURLs[index]]
    

    Or you could define it as a [Int: NSData] and use the number index as the key. But the idea is that you can use a dictionary, and then the order you receive the responses doesn't matter, but you still enjoy the performance benefit of doing concurrent requests.


    What I'd suggest would be something like:

    var imageData = [String: NSData]()
    
    @IBAction func didClickOnStart(sender: AnyObject) {
    
        queue.cancelAllOperations()
    
        let completionOperation = NSBlockOperation() {
            print("all done")
        }
    
        for (index, imageURL) in imageURLs.enumerate() {
            let operation = DataOperation(session: session, urlString: imageURL) { data, response, error in
                guard let data = data where error == nil else { return }
                guard let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200 else { return }
    
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.imageData[imageURL] = data
                }
                print("JPEG download\(index)")
            }
    
            completionOperation.addDependency(operation)
            queue.addOperation(operation)
        }
    
        NSOperationQueue.mainQueue().addOperation(completionOperation)        
    }
    

    And then access it like so:

    if let data = imageData[imageURLs[index]], let image = UIImage(data: data) {
        // use `image` here
    }
    

    Or

    var imageData = [Int: NSData]()
    
    @IBAction func didClickOnStart(sender: AnyObject) {
    
        queue.cancelAllOperations()
    
        let completionOperation = NSBlockOperation() {
            print("all done")
        }
    
        for (index, imageURL) in imageURLs.enumerate() {
            let operation = DataOperation(session: session, urlString: imageURL) { data, response, error in
                guard let data = data where error == nil else { return }
                guard let httpResponse = response as? NSHTTPURLResponse where httpResponse.statusCode == 200 else { return }
    
                NSOperationQueue.mainQueue().addOperationWithBlock {
                    self.imageData[index] = data
                }
                print("JPEG download\(index)")
            }
    
            completionOperation.addDependency(operation)
            queue.addOperation(operation)
        }
    
        NSOperationQueue.mainQueue().addOperation(completionOperation)        
    }
    

    And access it like so:

    if let data = imageData[index], let image = UIImage(data: data) {
        // use `image` here
    }
    

    Note, ImageNetworkOperation is just calling DataOperation to get the NSData, and then converting it to a UIImage. If you really want the original NSData, I'd suggest bypassing ImageNetworkOperation and just calling DataOperation directly, like shown above.