【发布时间】: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,我正在执行以下操作:
- 使用 FFT 等处理信号
- 更新
global_result的属性 - 触发
global_result的方法,将结果存储在CoreData并使用Alamofire将数据POST到我的服务器 - 第一次 POST 成功后,回调会更新
global_result的 ID,并触发更多 POST 请求。 - 触发完成处理程序,然后导致转换
global_result,是我的自定义全局对象,初始化为public var。
理论上,完成处理程序应该在处理完成后触发,结果保存在 CoreData 中,并分派第一个 POST 请求。
在ResultDetailViewController 的viewDidLoad 函数中,我将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
我以为发生了什么,是这样的:
- 创建了后台线程
- 对该线程进行繁重的处理
- 在该线程上发送 POST 请求
- HTTP 响应已处理,大量数据已在该后台线程上加密
- 跳转到主线程
- 在主线程上执行了转换
实际上,发生了这样的事情:
- 创建了后台线程
- 对该线程进行繁重的处理
- 在该线程上发送 POST 请求
- 跳转到主线程
- 在主线程上执行了转换
- 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 : 我不能把代码放在这里,所以写答案试试这个试试,让我知道它是否有效,我总是使用它并且工作得很好