2016-05-30 60 views
1

現在已解決問題。詳情請看最後的編輯!從GCD推動視圖控制器時的延遲很長

我有一個音頻可視化和按鈕的UIViewController。當按下一個按鈕,下面的功能被激發:

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 = 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) 
      }) 
     }) 
    }) 
} 

在這裏,我創建了一個背景隊列,並啓動一個非常漫長的信號處理操作。處理完成後,我使用主隊列執行導航到另一個視圖控制器。

這會導致ResultDetailViewController出現在屏幕上,並且所有相關數據和可視化都完全加載。

但是,在VC加載後的前2-3秒,沒有任何按鈕可以工作!如果我在這個初始階段點擊任何按鈕,那麼一旦初始階段結束,就會觸發該動作。

當我從任何其他VC執行此轉換時,ResultDetailViewController平滑加載,並且一切正常。

我在做什麼錯?任何幫助將非常感激!

編輯1

我會增加約我設置了更多的細節:使用FFT的和這種

  • 更新的

    1. 過程中的信號: 在AnalysisController,我做了以下屬性global_result
    2. 觸發方法global_result,它將結果存儲在CoreData中並使用Alamofire將數據發佈到我的服務器
    3. 第一次POST成功後,回調會更新global_result的ID,並且會觸發幾個POST請求。
    4. 完成處理程序被觸發,然後使過渡

    global_result,是被初始化爲public var我的自定義全局對象。

    理論上,完成處理程序應該在處理完成後觸發,結果保存在CoreData中,並且分派第一個POST請求。

    ResultDetailViewControllerviewDidLoad函數,我將global_result複製到本地變量中,並使用global_result中的數據創建UI元素。現在

    ,我懷疑是滯後的發生是由於使用global_result的時候就已經被加載ResultDetailViewController後臺線程,所以我想,而不是複製global_result創建Result類的新實例,但也沒有幫助。

    而這裏的Result類:

    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") 
         } 
        } 
    } 
    

    AnalysisController,我打電話global_result.store()

    EDIT 2

    我的想法是怎麼回事,是這樣的:

    1. 創建了一個後臺線程
    2. 完成在該線程的繁重處理
    3. 發送該線程
    4. HTTP響應被處理POST請求,和大量的數據在該後臺線程
    5. 躍升至主線程
    6. 執行的轉變被加密主線程

    在現實上,發生這種情況:

    1. 創建一個後臺線程
    2. 完成對螺紋
    3. 發送該線程
    4. POST請求重處理跳樓主線程
    5. 執行主線程的過渡
    6. HTTP響應突然回來了主線程,它被阻塞,直到大量數據完成加密。

    由於alexcurylo的建議,並this SO thread,我意識到,在主線程中發生Alamofire的響應處理,因此,有必要使用一個很酷的Alamofire功能推響應處理到併發線程,因此不要阻塞主隊列。

    對於任何未來的參考,我實現了它,像這樣:

    // 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 
    }) 
    

    謝謝大家對你的幫助!儘管它沒有直接解決這個問題,但Sandeep Bhandari的背景隊列創建提示和Igor B的Storyboard引用方法代表了更好的編碼實踐,應該採用這種實踐,而不是我的原始代碼。

  • +0

    你在viewDidLoad中viewWillAppear中或ResultDetailViewController的viewDidAppear做任何重物mainthread ??? –

    +0

    在ResultDetailViewController的viewDidLoad方法中,我從CoreData獲取一些數據,並設置UI元素。但是,從我的應用程序的任何其他部分調用時,VC加載完全正常,所以我懷疑問題在於其他地方。 –

    +0

    爲什麼DISPATCH_QUEUE_CONCURRENT什麼具體??? –

    回答

    2

    延遲後的發射行動的方式是,一些處理阻止你的主線程死贈品。

    Watchdog將幫助您完全確定處理的內容。

    用於在主線程上記錄過多阻塞的類。它觀察主線程並檢查它是否沒有超過定義的閾值而被阻塞。您還可以檢查代碼的哪個部分阻止主線程。

    簡單地說,只需要用必須通過的秒數實例化看門狗,以考慮主線程被阻塞。此外,您可以啓用strictMode,在達到閾值時停止執行。這樣,您可以檢查代碼的哪個部分阻止主線程。

    大概這個問題在這個檢查中會很明顯!

    +0

    謝謝,我會試一試並回復你。 –

    +0

    好吧,所以我使用了看門狗,看起來我的主線程在'responseJSON'回調中至少被阻塞了1秒,你可以在更新的細節中找到它。 (準確的說,第63行)我在印象中認爲這個回調應該在後臺線程中執行,因爲這是我用來觸發初始POST請求的東西嗎? –

    +1

    我最後一次使用它AlamoFire肯定回調主線程。這對於在回調中使用UIKit的人來說是一個安全的設計,所以我不希望它改變。看到[這個問題](http://stackoverflow.com/questions/29852431/alamofire-asynchronous-completionhandler-for-json-request)瞭解更多。 –

    1

    嘗試改變你已經創建了一個後臺線程可能會有所幫助:)

    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

    感謝您的建議!不幸的是,這個解決方案不起作用。儘管我的代碼可能有多個問題,所以我會嘗試將此解決方案與其他人結合使用。 –

    +0

    @ daniil-t:當然:) –

    1

    我的觀點UIStoryboard的實例可能是耗時的任務,這樣我會建議嘗試類似的東西:

    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

    我用你的建議取代了我的Storyboard初始化,但問題仍然存在。有趣的是,VC沒有任何延遲加載,但沒有一個行動工作幾秒鐘... –

    +0

    @ daniil.t你可以檢查'viewDidLoad','viewDidApper','viewWillApper'沒有做重要的工作(評論除了超級方法調用之外的所有內容)? –

    +0

    還有一個問題:'AnalysisController'是否與引擎下的主線程交互? –