主视图控制器外的多线程进度条并发问题
Posted
技术标签:
【中文标题】主视图控制器外的多线程进度条并发问题【英文标题】:Multi-threaded progress bar concurrency issue outside the main view controller 【发布时间】:2015-10-29 10:02:20 【问题描述】:我在同一个线程和视图控制器中找到了很多进度条更新解决方案,但它们似乎与我的情况不同。
在我的应用程序中,主视图控制器调用loadIntoCoreData()
(在类MyLoadingService
中实现),它通过另一个线程将数据异步加载到核心数据中。这个函数必须不断更新加载百分比(写在NSUserDefaults.standardUserDefaults()
)到主线程,以便它可以显示在主视图控制器的进度条上。我曾经在MainViewController
中使用过while 循环来不断获取当前百分比值,如下所示:
class MainViewController
override func viewDidLoad()
MyLoadingService.loadIntoCoreData() result in
NSUserDefaults.standardUserDefaults().setBool(false, forKey: "isLoading")
// do something to update the view
self.performSelectorInBackground("updateLoadingProgress", withObject: nil)
func updatingLoadingProgress()
let prefs = NSUserDefaults.standardUserDefaults()
prefs.setBool(true, forKey: "isLoading")
// here I use a while loop to listen to the progress value
while(prefs.boolForKey("isLoading"))
// update progress bar on main thread
self.performSelectorOnMainThread("showLoadingProcess", withObject: nil, waitUntilDone: true)
prefs.setValue(Float(0), forKey: "loadingProcess")
func showLoadingProcess()
let prefs = NSUserDefaults.standardUserDefaults()
if let percentage = prefs.valueForKey("loadingProcess")
self.progressView.setProgress(percentage.floatValue, animated: true)
并且在函数loadIntoCoreData
的类中:
class MyLoadingService
let context = (UIApplication.sharedApplication()delegate as! AppDelegate).managedObjectContext!
func loadIntoCoreData(source: [MyModel])
var counter = 0
for s in source
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock(
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
)
counter++
上述代码可以成功运行进度条,但是由于核心数据上下文冲突(认为managedObjectContext
没有被主线程触及)。因此,我考虑使用NSOperationQueue.performSelectorOnMainThread
在每次进入后确认主线程,而不是在主线程上使用while 循环。因此我将我的视图控制器作为参数sender
放入loadCoreData
并调用performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true)
但失败并出现错误“无法识别的选择器发送到类'XXXXXXXX'”。所以我想问是否可以在线程之间更新 UI 对象?或者,如何修改我之前的解决方案,以便解决核心数据上下文冲突?任何解决方案都值得赞赏。
class MyLoadingService
func loadIntoCoreData(sender: MainViewController, source: [MyModel])
var counter = 0
for s in source
//load into core data using the class context
NSOperationQueue.mainQueue.addOperationWithBlock(
// updating the value of "loadingProcess" in NSUserDefaults.standardUserDefaults()
// and synchronize it on main queue
)
NSOperationQueue.performSelectorOnMainThread("updateProgressBar", withObject: sender, waitUntilDone: true)
counter++
func updateProgressBar(sender: MainViewController)
sender.progressView.setProgress(percentage, animated: true)
class MainViewController
override func viewDidLoad()
MyLoadingService.loadIntoCoreData(self) result in
// do something to update the view
【问题讨论】:
【参考方案1】:首先,您以可怕的方式滥用NSUserDefaults
。文档将其描述为...
NSUserDefaults 类为 与默认系统交互。默认系统允许 应用程序自定义其行为以匹配用户的偏好。 例如,您可以允许用户确定哪些单位 测量您的应用程序显示或文档的频率 自动保存。应用程序通过分配记录这些偏好 用户默认数据库中的一组参数的值。这 参数被称为默认值,因为它们通常用于 确定应用程序在启动时的默认状态或其行为方式 默认情况下。
您正在使用它来存储全局变量。
此外,您在循环中完全滥用了用户的 CPU,您不断检查用户默认值中的值,并将选择器剪掉到主线程。 “滥用 CPU”甚至无法描述这段代码在做什么。
您应该使用NSProgress
报告进度。有一个WWDC 2015 presentation 专用于使用NSProgress
。
关于您的核心数据使用情况。
不幸的是,由于您有意编辑了所有核心数据代码,因此无法说出问题所在。
但是,根据我所看到的,您可能正试图从后台线程使用您的应用程序委托(可能仍使用已弃用的限制策略创建)中的托管对象上下文,这是最高的罪过就核心数据而言的顺序。
如果要将数据导入为长时间运行的操作,请使用私有上下文,并在后台执行操作。使用NSProgress
将进度传达给任何想听的人。
编辑
感谢您对我的核心数据上下文使用的建议。我挖掘了所有 我的代码中的上下文并重新组织了里面的上下文, 冲突问题不再发生。至于 NSProgress ,它是一个 遗憾的是 WWDC 演示文稿侧重于 ios 9 上的功能(而 我的应用程序必须在 iOS 8 设备上压缩)。但是,即使我使用 NSProgress,我还是应该告诉主线程核心有多少数据 数据(在另一个线程上)已经有了,对吧?线程如何 NSProgress 知道我的核心数据线程的加载进度吗? – 惠特尼13625
您仍然可以在 iOS8 上使用 NSProgress,唯一的区别是您不能显式添加子项,但隐式方式仍然有效,并且该视频也解释了它。
您确实应该观看整个视频而忘记 iOS9 部分,只是要知道您必须隐式添加子代而不是显式添加。
另外,this pre-iOS9 blog 的帖子应该可以解决您对此的任何疑问。
【讨论】:
感谢您对我的核心数据上下文使用的建议。我挖掘了代码中的所有上下文并重新组织了里面的上下文,冲突问题不再发生。至于NSProgress
,很遗憾,WWDC 演示集中在 iOS 9 上的功能上(而我的应用程序必须在 iOS 8 设备上压缩)。然而,即使我使用NSProgress
,我仍然应该告诉主线程核心数据(在另一个线程上)已经有多少数据,对吧? NSProgress
上的线程怎么知道我的核心数据线程的加载进度?以上是关于主视图控制器外的多线程进度条并发问题的主要内容,如果未能解决你的问题,请参考以下文章