【问题标题】:Long delay when pushing a view controller from GCD从 GCD 推送视图控制器时的长时间延迟
【发布时间】:2016-09-28 08:09:09
【问题描述】:

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

我有一个带有音频可视化和按钮的 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. 触发global_result的方法,将结果存储在CoreData并使用Alamofire将数据POST到我的服务器
  4. 第一次 POST 成功后,回调会更新 global_result 的 ID,并触发更多 POST 请求。
  5. 触发完成处理程序,然后导致转换

global_result,是我的自定义全局对象,初始化为public var

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

ResultDetailViewControllerviewDidLoad 函数中,我将global_result 复制到一个局部变量中,并使用来自global_result 的数据创建UI 元素。

现在,我怀疑当ResultDetailViewController 已经加载时,由于后台线程使用global_result 而发生延迟,所以我尝试创建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内,我打电话给global_result.store()

编辑 2

我以为发生了什么,是这样的:

  1. 创建了后台线程
  2. 对该线程进行繁重的处理
  3. 在该线程上发送 POST 请求
  4. HTTP 响应已处理,大量数据已在该后台线程上加密
  5. 跳转到主线程
  6. 在主线程上执行了转换

实际上,发生了这样的事情:

  1. 创建了后台线程
  2. 对该线程进行繁重的处理
  3. 在该线程上发送 POST 请求
  4. 跳转到主线程
  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引用方法代表了一种更好的编码实践,应该采用它来代替我的原始代码。

【问题讨论】:

  • 您是否在 ResultDetailViewController 的 viewDidLoad viewWillAppear 或 viewDidAppear 的主线程上做任何繁重的工作???
  • 在 ResultDetailViewController 的 viewDidLoad 方法中,我从 CoreData 获取一些数据,并设置 UI 元素。但是,当从我的应用程序的任何其他部分调用时,该 VC 加载得非常好,所以我怀疑问题出在其他地方。
  • 为什么 DISPATCH_QUEUE_CONCURRENT 任何具体的东西???
  • 没什么特别的,它曾经是 nil ,但在阅读了一个教程后,我决定将其更改为 DISPACH_QUEUE_CONCURRENT,看看是否有帮助。它没有。教程:raywenderlich.com/79149/…
  • @daniil-t : 我不能把代码放在这里,所以写答案试试这个试试,让我知道它是否有效,我总是使用它并且工作得很好

标签: ios swift swift2


【解决方案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)
            })
        })
    })
}

【讨论】:

  • 我已经用您的建议替换了我的 Storyboard 初始化,但问题仍然存在。有趣的是,VC 加载没有任何延迟,但没有任何动作在几秒钟内起作用......
  • @daniil.t 你能检查一下viewDidLoadviewDidApperviewWillApper 没有做重要的工作(注释除super 方法调用之外的所有内容)?
  • 还有一个问题:AnalysisController 是否与后台的主线程交互?
  • 我在我的问题中添加了更多细节,所以他们应该稍微澄清一下情况。不,AnalysisController 不会对主线程进行任何调用。
【解决方案2】:

延迟后触发的操作是一些处理阻塞主线程的死机。

Watchdog 将帮助您准确了解该处理是什么。

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

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

从那次检查中,问题可能会很明显!

【讨论】:

  • 谢谢,我会试一试,然后回复你。
  • 好吧,所以我使用了 Watchdog,在responseJSON 回调中,我的主线程似乎被阻塞了至少 1 秒,您可以在更新的详细信息中找到它。 (确切地说,第 63 行)我的印象是这个回调应该在后台线程中执行,因为这是我用来触发初始 POST 请求的?
  • 上次我用它AlamoFire肯定回调到主线程。对于在回调中使用 UIKit 的人来说,这将是一个安全的设计,所以我不希望它会改变。请参阅this question 了解更多信息。
【解决方案3】:

尝试更改您创建后台线程的方式,这可能会有所帮助:)

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

【讨论】:

  • 感谢您的建议!不幸的是,这个解决方案没有奏效。不过,我的代码可能不止一个问题,所以我会尝试将此解决方案与其他解决方案结合起来。
  • @daniil-t : 当然:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-14
  • 1970-01-01
  • 2016-10-22
  • 1970-01-01
相关资源
最近更新 更多