问题
Requirement - I have a requirement in which I am receiving a JSON dictionary from which I am retrieving an array of images and content text. Then I have to display all the images with corresponding contents in a collection view.
Update - Above all I need to calculate the cell size based on image size scaled to the a constant width for which I fell that(may not be correct) I need all images to be downloaded completely then reload collection view
Problem - But the problem is that when I download the images in background thread and populate in separate arrays.Then the image cannot be added in the same order as they were in the JSON Dictionary since I am downloading them in a concurrent queue.
My Solution - So I thought of downloading them by putting everything in a serial queue which has made my retrieving data very slow. What can be an efficient alternative for this?
Code -
let serialQueue = dispatch_queue_create("my serial queue", nil)
dispatch_async(serialQueue, {
print("This is first Method")
for var i=0;i<self.resultArr.count;i++//resultArr is my array of data's in the jsonDic
{
sleep(2)
print(self.resultArr[i].valueForKey("profile_pic")! as! String)
if self.resultArr[i].valueForKey("profile_pic")! as! String != "Null" && self.resultArr[i].valueForKey("profile_pic")! as! String != "null" && self.resultArr[i].valueForKey("profile_pic")! as! String != "NULL" && self.resultArr[i].valueForKey("profile_pic")! as! String != ""
{
let imageUrl = UrlClass.imageUrlWithoutExtension + String(self.resultArr[i].valueForKey("profile_pic")!)
print(imageUrl)
let url = NSURL(string: imageUrl)
let imageData = NSData(contentsOfURL: url!)
self.contentlabelArr.insertObject(String(self.resultArr[i].valueForKey("content")!), atIndex: i)
if imageData != nil && imageData?.length > 0
{
print("this is \(i) image")
print(UIImage(data: imageData!))
self.imageArr.insertObject(UIImage(data: imageData!)!, atIndex: i)
}
else
{
print("\(i) image has nill")
self.imageArr.insertObject(UIImage(named: "logo.png")!, atIndex: i)
}
}
else
{
print("\(i) image has nill")
self.contentlabelArr.insertObject(String(self.resultArr[i].valueForKey("content")!), atIndex: i)
self.imageArr.insertObject(UIImage(named: "logo.png")!, atIndex: i)
}
print("\(i) times 5 is \(i * 5)")
if self.imageArr.count==self.resultArr.count
{
print(self.resultArr.count)
print(self.imageArr.count)
dispatch_async(dispatch_get_main_queue(),
{
print(self.resultArr.count)
print(self.imageArr.count)
print(self.imageArr)
print(self.contentlabelArr)
self.collectionView?.reloadData()
})
}
回答1:
you definitely can keep the order if you use a concurrent queue. i think your code as it stands pretty much doesnt use the queue correctly at all (and why is there a sleep(2)
?) your concurrent queue should be inside the forloop so it can fire off the different blocks at the same time, and they will use the correct index of the for loop that was assigned to them to place the resulting image in the correct array location
let sema = dispatch_semaphore_create(2); //depending how many downloads you want to go at once
for i in 0..<self.resultArr.count {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), {
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
//download images here, order of execution will not be guaranteed, but after they are finished, they will always put the images in the array at 'i' so it doesnt matter
dispatch_semaphore_signal(sema);
})
}
回答2:
A more efficient way would be to create a data model object which will represent you image link and the optional UIImage. Something like this:
class NetworkImage {
let imageURL: String!
let image: UIImage?
}
Now when you receive your JSON with image links array, you can create your data model array, which will respect the order:
let dataModel: [NetworkImage]
So when you will retrieve your images asynchronously, you can update your dataModel with your image, so no order will be affected. The idea can be evolved suiting your needs. You should never use sync operations for this kind of jobs.
回答3:
You may play around with this sample solution, utilising dispatch groups:
//: Playground - noun: a place where people can play
import UIKit
import Dispatch
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true
class Record {
init(text: String, imageURL: String) {
self.text = text
self.imageURL = imageURL
self.image = nil
}
var text: String
var imageURL: String
var image: String?
}
extension Record: CustomStringConvertible {
var description: String {
return "text: \(text), imageURL: \(imageURL), image: \(image)"
}
}
// Fetch text and image url, but no image.
func fetchRecords(completion: ([Record]?, ErrorType?) -> ()) {
let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayInNanoSeconds, dispatch_get_global_queue(0, 0)) {
let result: [Record] = [
Record(text: "Aaa", imageURL: "path/image1"),
Record(text: "Bbb", imageURL: "path/image2"),
Record(text: "Ccc", imageURL: "path/image3")
]
completion(result, nil)
}
}
// fetch an image
func fetchImage(url: String, completion: (String?, ErrorType?) -> () ) {
let delayInNanoSeconds = dispatch_time(DISPATCH_TIME_NOW, Int64(1 * Double(NSEC_PER_SEC)))
dispatch_after(delayInNanoSeconds, dispatch_get_global_queue(0, 0)) {
let image = url
completion(image, nil)
}
}
// Put everything together:
// 1) Fetch an array of records, omitting the image
// 2) When this is finished, in parallel, for each record
// fetch each image.
// 3) When all is finished, call the completion handler containing
// the records including the images
func fetchRecordsWithImages(completion: ([Record]?, ErrorType?) -> () ) {
fetchRecords { (result, error) in
if let records = result {
let grp = dispatch_group_create()
records.forEach { record in
dispatch_group_enter(grp)
fetchImage(record.imageURL) { (image, error) in
if let image = image {
record.image = image
}
dispatch_group_leave(grp)
}
}
dispatch_group_notify(grp, dispatch_get_global_queue(0, 0)) {
completion(records, nil)
}
}
}
}
fetchRecordsWithImages() { (records, error) in
if let records = records {
print("Records: \(records)")
}
}
Console:
Records: [text: Aaa, imageURL: path/image1, image: Optional("path/image1"), text: Bbb, imageURL: path/image2, image: Optional("path/image2"), text: Ccc, imageURL: path/image3, image: Optional("path/image3")]
来源:https://stackoverflow.com/questions/36908183/downloading-images-serially-in-a-serial-queue-very-slow