2016-04-28 3 views
2

Anforderung- Ich habe eine Verpflichtung, in dem ich von dem ein JSON-Wörterbuch erhalte ich eine Reihe von Bildern und Informationen Text am abruft. Dann muss ich alle Bilder mit entsprechenden Inhalten in einer Sammlungsansicht anzeigen.Herunterladen von Bildern in Serie in einer seriellen Warteschlange sehr langsam

Update - Vor allem ich brauche die Zellengröße zu berechnen, basierend auf Bildgröße an die eine konstante Breite skaliert, für die ich fiel, dass (möglicherweise nicht korrekt) Ich brauche alle Bilder vollständig heruntergeladen werden dann neu zu laden Sammlung Ansicht

Problem - Aber das Problem ist, dass wenn ich die Bilder im Hintergrund-Thread zu downloaden und zu bevölkern in separaten arrays.Then das Bild kann nicht in derselben Reihenfolge hinzugefügt werden, wie sie im JSON Dictionary waren, da ich das herunterladen bin sie in einer gleichzeitigen Warteschlange.

Meine Lösung - Also überlegte ich, sie herunterzuladen, indem ich alles in eine serielle Warteschlange stellte, was meine Daten sehr langsam gemacht hat. Was kann eine effiziente Alternative dafür sein?

-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() 
         }) 
        } 
+0

Natürlich es langsam ist, da Sie in jeder Iteration 2 Sekunden Wartezeiten. Es wird dringend empfohlen, 'NSURLSession' und' NSOperationQueue' wie im Beispielcode von Apple statt 'NSData (contentsOfURL:)' – vadian

+0

@vadian zu verwenden. Aber ich brauche alle Bilder, um in der gleichen Reihenfolge in der resultArr zu kommen. NSUrlSession ist standardmäßig asynchron und Prozess sollte in der gleichzeitigen Warteschlange ausgeführt werden, aber wenn ich das gleichzeitig tue, stört die Reihenfolge des Bildes im Array.Also wäre jeder Code oder Empfehlungen sehr hilfreich. –

+0

Setzen Sie die Eigenschaft 'maxConcurrentOperationCount' von' NSOperationQueue' auf '1' und Sie erhalten die gleiche Reihenfolge. Oder wie in Olegs Antwort erwähnt, verwenden Sie ein Datenmodell mit einer benutzerdefinierten Klasse, dann spielt die Download-Reihenfolge keine Rolle. – vadian

Antwort

1

Sie können auf jeden Fall die Reihenfolge halten, wenn Sie eine gleichzeitige Warteschlange verwenden. Ich denke, Ihr Code, wie er steht, verwendet die Warteschlange überhaupt nicht richtig (und warum gibt es eine sleep(2)?) Ihre gleichzeitige Warteschlange sollte innerhalb der Forloop sein, so dass es die verschiedenen Blöcke zur gleichen Zeit auslösen kann, und sie werden verwenden der richtige Index der for-Schleife, die ihnen zugewiesen wurde in der richtigen Matrixlokation

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); 
    }) 
} 
+0

Ich werde versuchen, wie Sie gesagt haben, aber wie Sie wissen, dass gleichzeitige Warteschlange die abgeschlossenen Ergebnisse zuerst trotz ihrer Reihenfolge der Zugabe in die Warteschlange zurück, so wie die Bilder in einer Reihenfolge sein können. Bsp: image1 hat eine größere Größe als image2, dann wird image2 zuerst zurückgegeben und dem Array in 0 index statt image1 hinzugefügt. –

+0

Nehmen wir an, Sie laden image1 image2 image3 zur gleichen Zeit herunter, aber iOS entscheidet, sie wie image2 image1 image3 zu verarbeiten, denn in Ihrem Code ist image1 immer arrayIndex1, image2 immer arrayIndex2 usw. zugewiesen die Reihenfolge der Ausführung ist egal, nur das Endergebnis des Arrays – Fonix

+1

Ich würde vorschlagen, maximale gleichzeitige Downloads auf eine sinnvolle Anzahl mit Semaphor zu begrenzen. – werediver

3

eine effizientere Weg wäre, ein Datenmodell-Objekt zu erstellen, die Sie Bild wird vertreten Link und die optional das resultierende Bild zu platzieren UIImage. Etwas wie folgt aus:

class NetworkImage { 
    let imageURL: String! 
    let image: UIImage? 
} 

Nun, wenn Sie Ihre JSON mit Bild-Links-Array erhalten, können Sie Ihr Datenmodell Array erstellen, die die Reihenfolge respektieren:

let dataModel: [NetworkImage] 

Also, wenn Sie Ihre Bilder abruft asynchron können Sie Ihr dataModel mit Ihrem Bild aktualisieren, so dass keine Bestellung betroffen ist. Die Idee kann nach Ihren Bedürfnissen weiterentwickelt werden. Sie sollten für diese Art von Jobs niemals Synchronisierungsoperationen verwenden.

+0

stimme mit Ihnen überein, würde ich denke, eine Kombination unserer Antworten würde zu einer sehr lesbaren und effizienten Lösung – Fonix

+0

scheint ziemlich interessant, wird implementieren und versuche dich zu aktualisieren. –

1

Sie können mit dieser Probenlösung rumspielen, Versand Gruppen nutzen:

//: 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")] 
+0

Sie brauchen dafür keine Gruppe, nur "dispatch_barrier_async()" reicht aus. – werediver

+0

@werediver Nein, das ist nicht der Fall, da die an die Warteschlange gesendete Aufgabe _asynchronous_ ist. 'dispatch_barrier_async' würde nur dann als Sentinel funktionieren, wenn die übergebenen Blöcke synchron sind. – CouchDeveloper

+1

Dieser Ansatz ist übrigens ein ausgearbeitetes Beispiel für @OlegDanus Vorschlag. – CouchDeveloper