从GCD推送视图控制器时出现长时间延迟

时间:2016-05-30 13:14:11

标签: ios swift swift2

此问题现已解决。有关详细信息,请参阅上一个编辑!

我有一个带有音频可视化和按钮的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

我将添加有关我的设置的更多详细信息: 在AnalysisController中,我正在执行以下操作:

  1. 使用FFT等处理信号
  2. 更新global_result
  3. 的属性
  4. 触发global_result方法,该方法将结果存储在CoreData中并使用Alamofire将数据发布到我的服务器
  5. 第一次POST成功后,回调会更新global_result的ID,并触发更多POST请求。
  6. 触发完成处理程序,然后导致转换
  7. global_result,是我的自定义全局对象,初始化为public var

    理论上,处理完成后应触发完成处理程序,结果保存在CoreData中,并调度第一个POST请求。

    ResultDetailViewController的{​​{1}}函数中,我将viewDidLoad复制到局部变量中,并使用来自global_result的数据创建UI元素。

    现在,我怀疑由于后台线程在global_result已经加载时使用global_result而导致延迟,所以我尝试创建ResultDetailViewController类的新实例,而不是复制Result,但这也没有帮助。

    这里是global_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

    编辑2

    我的想法是这样的:

    1. 创建了一个后台主题
    2. 在该主题上完成繁重的处理
    3. 在该主题上发送了一个POST请求
    4. 处理了HTTP响应,并在该后台线程上加密了大量数据
    5. 跳转到主线程
    6. 在主线程上执行转换
    7. 实际上,这发生了:

      1. 创建了一个后台主题
      2. 在该主题上完成繁重的处理
      3. 在该主题上发送了一个POST请求
      4. 跳转到主线程
      5. 在主线程上执行转换
      6. HTTP响应突然回到主线程,并且在大量数据加密完成之前就被阻止了。
      7. 感谢alexcurylo的建议和this SO thread,我意识到Alamofire的响应处理发生在主线程上,因此有必要使用一个很酷的Alamofire将响应处理推送到并发线程的功能,以免阻塞主队列。

        对于任何人未来的参考,我都是这样实现的:

        global_result.store()

        感谢大家的帮助!即使它没有直接解决这个问题,Sandeep Bhandari的后台队列创建提示,而Igor B的故事板引用方法代表了更好的编码实践,应该采用而不是我的原始代码。

3 个答案:

答案 0 :(得分:2)

延迟后触发的动作是一个死的赠品,有些处理正在阻止你的主线程。

Watchdog将帮助您确切了解该处理的内容。

  

用于在主线程上记录过多阻塞的类。它监视主线程并检查它是否被阻止超过定义的阈值。您还可以检查代码的哪一部分阻塞主线程。

     

简单地说,只需使用必须通过的秒数来实例化Watchdog以考虑主线程被阻止。此外,您可以启用strictMode,以便在达到阈值时停止执行。这样,您可以检查代码的哪一部分阻塞主线程。

据推测,这个问题显而易见!

答案 1 :(得分: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)
                })
            })
        })
    }

答案 2 :(得分: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)
            })
        })
    })
}