2016-05-30 13 views
1

Das Problem ist jetzt behoben. Siehe die letzte Bearbeitung für Details!Lange Verzögerung beim Drücken eines View-Controllers von GCD

Ich habe einen UIViewController mit einer Audiovisualisierung und einer Schaltfläche. Wenn eine Taste gedrückt wird, wird die folgende Funktion ausgelöst:

Hier erstelle ich eine Hintergrundwarteschlange und starte einen sehr langen Signalverarbeitungsvorgang. Sobald die Verarbeitung abgeschlossen ist, führe ich die Navigation zu einem anderen View-Controller aus, der die Hauptwarteschlange verwendet.

Dadurch erscheint ResultDetailViewController auf dem Bildschirm mit allen relevanten Daten und der vollständig geladenen Visualisierung.

Jedoch, für die ersten 2-3 Sekunden nach dem VC geladen wurde, keiner der Knöpfe funktioniert! Wenn ich innerhalb dieser Anfangsperiode auf eine Schaltfläche klicke, wird die Aktion ausgelöst, sobald die Anfangsphase vorbei ist.

Wenn ich diesen Übergang von einem anderen VC durchführen, wird ResultDetailViewController reibungslos geladen, und alles funktioniert.

Was mache ich falsch? Jede Hilfe würde sehr geschätzt werden!

EDIT 1

ich mehr Details über mein Set-up hinzufügen wird: In AnalysisController, ich tue das folgende:

  1. Prozess das Signal FFT und solche
  2. -Update ein die Verwendung von Eigenschaften eines global_result
  3. Trigger eine Methode global_result, die das Ergebnis in CoreData speichert und Alamofire verwendet, um die Daten an meinen Server
  4. POST
  5. Sobald der erste POST erfolgreich ist, aktualisiert der Callback die ID global_result und löst einige weitere POST-Anfragen aus.
  6. Completion-Handler ausgelöst wird, die dann den Übergang

global_result verursacht, ist mein benutzerdefiniertes globales Objekt, das als public var initialisiert wird.

Theoretisch sollte der Beendigungshandler ausgelöst werden, sobald die Verarbeitung abgeschlossen ist, die Ergebnisse in CoreData gespeichert werden und die erste POST-Anforderung ausgelöst wird. unter Verwendung von Daten aus global_result

In ResultDetailViewController ‚s viewDidLoad Funktion, bin ich das Kopieren global_result in eine lokale Variable und die UI-Elemente zu schaffen.

Nun vermutete ich, dass die Verzögerung aufgrund Hintergrund-Thread tritt global_result verwenden, wenn ResultDetailViewController bereits geladen ist, so habe ich versucht, eine neue Instanz der Result Klasse zu erstellen, anstatt die global_result Kopieren, aber das auch nicht helfen.

Und hier ist die Result Klasse:

import Foundation 
import CoreData 
import Alamofire 
import CryptoSwift 

public class Result { 
    var localID: Int 
    var id: Int 
    var filePath: NSURL! 
    var result: Double 
    var origSound: [Double] 


    init(localID: Int, id: Int, filePath: NSURL, result: Double, origSound: [Double]) { 
     // Initialize stored properties. 
     self.localID = localID 
     self.id = id 
     self.filePath = filePath 
     self.result = result 
     self.origSound = origSound 
    } 

    func store() { 
     self.storeLocal() 

     // Serialise data 
     let parameters = [ 
      "localID": self.localID, 
      "id": self.id, 
      "result": self.result 
     ] 

     // Prepare request 
     let request = NSMutableURLRequest(URL: NSURL(string: "my_server/script.php")!) 
     request.HTTPMethod = "POST" 
     request.setValue("application/json", forHTTPHeaderField: "Content-Type") 

     // Encode parameters in JSON and encrypt them 
     request.HTTPBody = dictToEncryptedJSONData(rsaKey, parameters: parameters) 

     // POST the data to server 
     Alamofire.request(request) 
      .responseJSON { response in 
       if let JSON = response.result.value { 
        if let myID = JSON as? Int { 

         self.id = myID 

         // Store the global ID in CoreData 
         updateIDLocal(self.localID, id: self.id) 

         // Serialise and POST array 
         let param = [ 
          "origSound": self.origSound 
         ] 

         // Prepare request 
         var request = NSMutableURLRequest(URL: NSURL(string: "my_server/script2.php?id=\(self.id)")!) 
         request.HTTPMethod = "POST" 
         request.setValue("application/json", forHTTPHeaderField: "Content-Type") 

         // Encode parameters in JSON and encrypt them 
         request.HTTPBody = dictToEncryptedJSONData(rsaKey, parameters: param) 

         // POST the data to server 
         Alamofire.request(request) 

         // Upload the file 
         let upURL = "my_server/script3.php?id=\(self.id)" 

         // Prepare request 
         request = NSMutableURLRequest(URL: NSURL(string: upURL)!) 
         request.HTTPMethod = "POST" 
         request.setValue("application/json", forHTTPHeaderField: "Content-Type") 

         // Encrypt the file 
         request.HTTPBody = fileToEncryptedData(rsaKey, filePath: self.filePath) 

         // POST the data to server 
         Alamofire.request(request) 
        } 
       } 
     } 
    } 

    // Store the object in CoreData 
    func storeLocal() { 
     // Create a local id 
     if let oldID = NSUserDefaults.standardUserDefaults().integerForKey("localID") as Int? { 
      // Increment the ID 
      NSUserDefaults.standardUserDefaults().setInteger(oldID + 1, forKey: "localID") 
      self.localID = oldID + 1 
     } 
     else { 
      // First object in CoreData 
      NSUserDefaults.standardUserDefaults().setInteger(0, forKey: "localID") 
     } 

     // Store data in CoreData 
     var resultDatas = [NSManagedObject]() 
     //1 
     let appDelegate = 
      UIApplication.sharedApplication().delegate as! AppDelegate 

     let managedContext = appDelegate.managedObjectContext 

     //2 
     let entity = NSEntityDescription.entityForName("Result", 
                 inManagedObjectContext:managedContext) 

     let resultData = NSManagedObject(entity: entity!, 
             insertIntoManagedObjectContext: managedContext) 

     // Store data 
     resultData.setValue(localID, forKey: "localID") 
     resultData.setValue(id, forKey: "id") 
     resultData.setValue(filePath.path!, forKey: "url") 
     resultData.setValue(result, forKey: "result") 

     // Store the array 
     var data = NSData(bytes: origSound, length: origSound.count * sizeof(Double)) 
     resultData.setValue(data, forKey: "origSound") 

     //4 
     do { 
      try managedContext.save() 
      //5 
      resultDatas.append(resultData) 
     } catch _ { 
      print("Could not save") 
     } 
    } 
} 

Im AnalysisController, ich rufe global_result.store()

EDIT 2

Was ich dachte, geschah, war dies:

  1. Erstellt einen Hintergrundthread
  2. Fertig schwere Verarbeitung auf diesem Thread
  3. eine POST-Anforderung an diesem
  4. Thread gesendet
  5. HTTP-Antwort verarbeitet wurde, und eine Menge von Daten wurde auf diesem Hintergrund-Thread
  6. an den Haupt-Thread verschlüsselten
  7. den Übergang Ausgeführte Jumped auf dem Haupt-Thread

In Wirklichkeit geschah dies:

  1. einen Hintergrund-Thread
  2. Fertig schwere Verarbeitung auf diesem Thread
  3. Gesendete eine POST-Anforderung auf diesem Thread
  4. gesprungen Hauptthread
  5. ausgeführt, um den Übergang von dem Hauptthread
  6. HTTP-Antworten plötzlich zurück kam Erstellt zu der Hauptthread, und es wurde blockiert, bis Tonnen von Daten die Verschlüsselung abgeschlossen haben.

Dank alexcurylo ‚s Vorschlag, und this SO thread, erkannte ich, dass die Antwort Handling Alamofire auf dem Hauptthread auftritt, so war es notwendig, einen kühlen Alamofire Funktion zu verwenden, um die Antwort drücken auf eine gleichzeitige Fadenhandhabungs, so die Hauptwarteschlange nicht zu blockieren.

Für die Zukunft ist jemand, implementiert ich es so auf:

// Prepare request 
let request = NSMutableURLRequest(URL: NSURL(string: "my_server/script.php")!) 
request.HTTPMethod = "POST" 
request.setValue("application/json", forHTTPHeaderField: "Content-Type") 

// Encode parameters in JSON and encrypt them 
request.HTTPBody = dictToEncryptedJSONData(rsaKey, parameters: parameters) 

// Create a concurrent queue 
let queue = dispatch_queue_create("DT.response-queue", DISPATCH_QUEUE_CONCURRENT) 

// POST the data to server 
Alamofire.request(request) 
.response(
    queue: queue, 
    responseSerializer: Request.JSONResponseSerializer(options: .AllowFragments), 
    completionHandler: { response in 
     // Perform response-handling HERE 
}) 

Vielen Dank allen für Ihre Hilfe! Obwohl es dieses Problem nicht direkt löste, stellen Sandeep Bhandari 's Hintergrund Queue Creation Tip, und Igor B' s Methode für Storyboard-Referenzierung eine bessere Kodierungspraxis, die anstelle meines ursprünglichen Codes übernommen werden sollte.

+0

Sind Sie etwas schwer auf Mainthread in viewDidLoad viewWillAppear oder viewDidAppear von ResultDetailViewController tun ??? –

+0

In der ViewDidLoad-Methode von ResultDetailViewController lade ich einige Daten von CoreData ab und richte die UI-Elemente ein. Dieser VC lädt jedoch völlig in Ordnung, wenn er von irgendeinem anderen Teil meiner Anwendung aufgerufen wird, also vermute ich, dass das Problem an anderer Stelle liegt. –

+0

Warum DISPATCH_QUEUE_CONCURRENT irgendetwas spezifisch ??? –

Antwort

2

Die Aktionen Brennen nach einer Verzögerung helfen Thread erstellt haben könnte, ist ein untrügliches Zeichen, dass einige Verarbeitung Ihrer Haupt-Thread blockiert.

Watchdog wird Ihnen helfen, genau herauszufinden, was diese Verarbeitung ist.

Klasse für die Protokollierung übermäßiger Blockierung des Hauptthreads. Es überwacht den Hauptthread und überprüft, ob es für mehr als einen definierten Schwellenwert blockiert wird.Sie können auch überprüfen, welcher Teil des Codes den Hauptthread blockiert.

Instanziieren Sie Watchdog einfach mit der Anzahl der Sekunden, die übergeben werden müssen, um den blockierten Hauptthread zu berücksichtigen. Zusätzlich können Sie strictMode aktivieren, das die Ausführung stoppt, sobald der Schwellenwert erreicht wird. Auf diese Weise können Sie prüfen, welcher Teil des Codes den Hauptthread blockiert.

Vermutlich wird das Problem von dieser Inspektion offensichtlich sein!

+0

Danke, ich gebe dir einen Versuch und komme zurück zu dir. –

+0

In Ordnung, also habe ich Watchdog benutzt, und es scheint, dass mein Haupt-Thread um mindestens 1 Sekunde innerhalb des 'responseJSON' Callbacks blockiert ist, den Sie in den aktualisierten Details finden. (Zeile 63, um genau zu sein) Ich hatte den Eindruck, dass dieser Callback innerhalb des Hintergrund-Threads ausgeführt werden sollte, da ich die ursprüngliche POST-Anfrage damit ausgelöst habe? –

+1

Als ich es das letzte Mal benutzt habe, rief AlamoFire definitiv zum Hauptthread zurück. Das wäre ein sicheres Design für Leute, die UIKit in ihrem Callback verwenden, also würde ich nicht erwarten, dass sich das ändert. Siehe [diese Frage] (http://stackoverflow.com/questions/29852431/alamofire-asynchronous-completionhandler-for-json-request) für mehr. –

1

versuchen, die Art und Weise ändern, die Sie ein Hintergrund it :)

func use(sender:UIButton!) { 
     // Analyse the audio 
     dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { 
      // Initialise the analysis controller 
      let analysisController = AnalysisController() 
      analysisController.analyseAudio(global_result.filePath, completion: { 
       // When analysis is complete, navigate to another VC 
       dispatch_async(dispatch_get_main_queue(), { 
        let mainStoryboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle()) 
        let vc : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ResultDetailViewController") as UIViewController 
        let navigationController = self.navigationController 
        // Pop the current VC before pushing the new one 
        navigationController?.popViewControllerAnimated(false) 
        navigationController?.pushViewController(vc, animated: false) 
       }) 
      }) 
     }) 
    } 
+0

Danke für den Vorschlag! Leider hat diese Lösung nicht funktioniert. Es gibt vielleicht mehr als ein Problem mit meinem Code, also werde ich versuchen, diese Lösung mit anderen zu kombinieren. –

+0

@ daniil-t: Sicher :) –

1

Mein Punkt Instanziierung UIStoryboard könnte aufwendige Aufgabe sein, so dass ich, so etwas versuchen würde empfehlen:

func use(sender:UIButton!) { 
    // Analyse the audio 
    let analysisQueue = dispatch_queue_create("analysis", DISPATCH_QUEUE_CONCURRENT) 
    dispatch_async(analysisQueue, { 
     // Initialise the analysis controller 
     let analysisController = AnalysisController() 
     analysisController.analyseAudio(global_result.filePath, completion: { 
      // When analysis is complete, navigate to another VC 
      dispatch_async(dispatch_get_main_queue(), { 
       let mainStoryboard = self.navigationController!.storyboard 
       let vc : UIViewController = mainStoryboard.instantiateViewControllerWithIdentifier("ResultDetailViewController") as UIViewController 
       let navigationController = self.navigationController 
       // Pop the current VC before pushing the new one 
       navigationController?.popViewControllerAnimated(false) 
       navigationController?.pushViewController(vc, animated: false) 
      }) 
     }) 
    }) 
} 
+0

Ich habe meine Storyboard-Initialisierung durch Ihren Vorschlag ersetzt, aber das Problem bleibt bestehen. Interessanterweise lädt der VC ohne Verzögerung, aber keine der Aktionen funktioniert für ein paar Sekunden ... –

+0

@ daniil.t Können Sie überprüfen, dass 'viewDidLoad',' viewDidApper', 'viewWillApper' keine wesentliche Arbeit (Kommentar alles außer 'Super' Methodenaufruf)? –

+0

Eine weitere Frage: Interagiert 'AnalysisController' mit dem Hauptthread unter der Haube? –

Verwandte Themen