1

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

5
  • Please clarify what you mean by "in sequence." Commented Jul 21, 2016 at 5:29
  • You can opt for GCD, it is very easy to use and has features like serial queue, concurrent queue. here is a tutorial for the same - raywenderlich.com/60749/grand-central-dispatch-in-depth-part-1. Commented Jul 21, 2016 at 5:35
  • I mean in order as urls are in array. For example 10 url of images are in array. Image need save in same index number as its url save at specific index. But due to asynchronous images are downloads in random. Not in sequence. I need to save all images in sequence because i need to use these with its titles. I am downloading these images. http://www.wcvb.com/9849860?format=rss_2.0&view=feed Commented Jul 21, 2016 at 5:38
  • 1
    The easiest way to do without knowing much of the GDC stuff is that after UIImage is downloaded, create it in an object with the index that you have with the imageURL, then just append randomly, after that use sort with the index, then you got the same order :) Commented Jul 21, 2016 at 5:42
  • Thanks Tj3n. I've try to do this. Commented Jul 21, 2016 at 6:00

3 Answers 3

4

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.

Sign up to request clarification or add additional context in comments.

5 Comments

Thanks Rob for again helping me. It works. Now i've try to sort imageData = [String: NSData]() , because later i need to use these images with their titles in tableViewCell
@ZAFAR007 - The beauty of the dictionary is that you don't have to sort it. You can use if let data = imageData[imageURLs[indexPath.row]], let image = UIImage(data: data) { ... }.
Rob- Sorry for this. I know about AlamofireImage library but it takes more storage due to its library files, that reason i did't use it. Also i dont want to show images directly in tableViewCell, because i am saving these images first in NSUserDefualts then i show in tableViewCell after retrieving images from NSUserDefaults. Because i want to make my app also for offline read. I've also try to use dispatch_get_global_queue with synchronous downloading, that downloads images in sequence, but due to dipatch is not cancelable i use your solution of NSOperationQueue.
Hi Rob. Can you please check my new question if when you available. :-). I did't get any help yet. I think little bit mistake are in my code. Thanks. stackoverflow.com/questions/38561293/…
0

I'm not sure but i think, You can not control the Download order.. means all the request are pipelined to server (no matter what the order you create the URL objects in). What you need to do is, you must maintain the mutable array or dictionary that contains the url to actual data mapping, then wait until all the urls have been completely downloaded. Then iterate in a known order.

5 Comments

can you give an simple example?
You should wait for downloading all url's(store that in one mutable array,once it completed , then sort it)
or if you want to download one by one then see @Andrey's answer
Thanks Suraj Sukale. I've try to sort it after download.
yes bro.. i think thats the better solution.. ( use @Rob solution) if answer is helpful then you can upvote my answer
0

try:

var previousOperation : NSOperation! = nil

    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)

        if (previousOperation != nil)
        {
            operation.addDependency(previousOperation)
        }

        previousOperation = operation
        queue.addOperation(operation)
    }

    NSOperationQueue.mainQueue().addOperation(completionOperation)

this is a very quick and rough fix, there well may be a better solution of course. The problem appears because operations in an operation queue are performed concurrently and are not guaranteed to finish in order they started. By adding dependency to a previous operation in the loop you make sure they are performed sequentially

6 Comments

Maybe this would be better than download all at the same time and try to sort them again
I've try this but same result JPEG download0 JPEG download2 JPEG download1 JPEG download3 all done
This is my code can you please try to fix it in as you give me solution. Thanks. https://drive.google.com/open?id=0B29ka7gbDAYxaXdsX05KTUFzYzA
@ZAFAR007 see my updated code. It works. But this solution is far from perfect as Rob said. I suggest taking his approach
Thanks Andrey for helping me. It works but there is an problem. If i cancel NSOperationQueue during downloading it gives me error at let dtA line fatal error: unexpectedly found nil while unwrapping an Optional value. I think i need to use Rob method. And after downloading images i'll need to sort Dictionary Because from this method i cannot sort Array, because they are apending without Keys(urls).
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.