编辑:我已经简化了代码并添加了对NSThread.isMainThread()的调用,以查看是否存在此问题。请参阅下面的更多详细信息
我正在开发一个相当简单的应用程序来帮助一位教授在夏天进行研究。该应用程序旨在根据iPad中的加速度计确定句子中的单词难度。
基本上,用户会倾斜iPad,从而产生非零加速度,位于scrollView
内ScrollingLabel Class
的文本将相应地滚动。
这种方法在99%的时间内表现出色。在我们几乎所有的测试中,它都运行得很好,它可以毫无问题地遍历整个文本,并且没有任何不好的事情发生。然而,很少,它只是打破,抛出 EXC_BAD_ACCESS 的错误。我想强调的是,在极少数情况下它会破裂,没有明显的模式,有时会发生在滚动,接近结束或开始时。
显然我希望这个应用程序没有错误,这是一个相当重要的应用程序,我无法弄清楚,所以你能给予的任何帮助都将不胜感激。
以下是我startScrolling class
的总代码(该错误始终发生在import Foundation
import UIKit
import QuartzCore
import CoreMotion
public class ScrollingLabel {
//Instantiation of scroll view and label
var baseTextLabel:UILabel!
var baseScrollView:UIScrollView!
var frame:CGRect!
//Instantiation of accelerometer materials
var motionManager=CMMotionManager()
var queue=NSOperationQueue()
//To rectify the issue, I have changed this to:
//var queue=NSOperationQueue.mainQueue(), SEE EDIT BELOW
init(frame:CGRect) {
/*Initializes the object by calling 3 private setup functions,
each dealing one with a specific feature of the final label, and
finally calling the scroll function to activate the accelerometer
control*/
setupFrame(frame)
setupLabel()
setupScroll()
startScrolling()
}
private func startScrolling() {
//The main accelerometer control of the label
println(NSThread.isMainQueue) //THIS RETURNS TRUE
//Allows the start orientation to become default
var firstOrientation:Bool
var timeElapsed:Double=0
if letUserCreateDefaultOrientation {firstOrientation=true}
else {firstOrientation=false}
var standardAccel:Double=0
//Begins taking updates from the accelerometer
if motionManager.accelerometerAvailable{
motionManager.accelerometerUpdateInterval=updateTimeInterval
motionManager.startAccelerometerUpdatesToQueue(self.queue, withHandler: { (accelerometerData, error:NSError!) -> Void in
println(NSThread.isMainQueue) //THIS RETURNS FALSE
//Changes the input of acceleration depending on constant control variables
var accel:Double
if self.timerStarted {
timeElapsed+=Double(self.updateTimeInterval)
}
if !self.upDownTilt {
if self.invertTextMotion {accel = -accelerometerData.acceleration.y}
else {accel = accelerometerData.acceleration.y}
}
else {
if self.invertTextMotion {accel = -accelerometerData.acceleration.x}
else {accel = accelerometerData.acceleration.x}
}
//Changes default acceleration if allowed
if firstOrientation {
standardAccel=accel
firstOrientation=false
}
accel=accel-standardAccel
//Sets the bounds of the label to prevent nil unwrapping
var minXOffset:CGFloat=0
var maxXOffset=self.baseScrollView.contentSize.width-self.baseScrollView.frame.size.width
//If accel is greater than minimum, and label is not paused begin updates
if !self.pauseScrolling && fabs(accel)>=self.minTiltRequired {
//If the timer has not started, and accel is positive, begin the timer
if !self.timerStarted&&accel<0{
self.stopwatch.start()
self.timerStarted=true
}
//Stores the data, and moves the scrollview depending on acceleration and constant speed
if self.collectData {self.storeIndexAccelValues(accel,timeElapsed: timeElapsed)}
var targetX:CGFloat=self.baseScrollView.contentOffset.x-(CGFloat(accel) * self.speed)
if targetX>maxXOffset {targetX=maxXOffset;self.stopwatch.stop();self.doneWithText=true}
else if targetX<minXOffset {targetX=minXOffset}
self.baseScrollView.setContentOffset(CGPointMake(targetX,0),animated:true)
if self.baseScrollView.contentOffset.x>minXOffset&&self.baseScrollView.contentOffset.x<maxXOffset {
if self.PRIVATEDEBUG {
println(self.baseScrollView.contentOffset)
}
}
}
})
}
}
的末尾。)
startScrolling
当它崩溃时,它发生在startScrolling
的末尾,当我将内容Offset设置为目标X时。如果您需要更多信息,我很乐意提供它,但因为bug很少发生我没有什么可说的,特别是当它发生时或类似的东西......它似乎是随机的。
编辑:我已将代码简化为相关部分,并添加了两个我称之为NSThread.isMainQueue()的位置。当在motionManager.startAccelerometerUpdatesToQueue
的第一行调用时,。isMainQueue()返回 TRUE ,但是当在motionManager.startAccelerometerUpdatesToQueue
内调用时,它返回 FALSE 。
为了解决这个问题,我已经将self.queue从一个NSOperationQueue()更改为NSOperationQueue.mainQueue(),并且在进行此切换之后,第二个.isMainThread()调用({{1}}内部的那个)现在我们希望返回 TRUE 。
答案 0 :(得分:0)
这是很多代码。我没有在行中看到根据targetX设置内容偏移的任何内容。
有几种可能性
baseScrollView正在被取消分配并且是一个僵尸(不太可能,因为它看起来像是将它定义为常规(强)实例变量。)
您正在从后台线程调用startScrolling。如果您从后台线程更新UI对象,则所有投注均已关闭。您可以使用NSThread.isMainThread()
进行检查。将该代码放在startScrolling方法中,如果返回false,那就是你的问题。
根据您在回答我的回答时的评论, 从后台主题调用startScrolling
。
这确实很可能是问题所在。编辑帖子以显示从后台线程调用的代码,包括上下文。 (您的#34;加速度计更新周期&#34;代码)。
您无法从后台线程操纵UIKit对象,因此您可能需要在加速度计更新周期中包装UIKit更改&#34;调用在主线程上运行的dispatch_async的代码。
您终于发布了足够的信息,以便我们为您提供帮助。您的原始代码让您在后台队列上接收到acellerometer更新。您正在使用该代码进行UIKit调用。这是禁忌,并且这样做的结果是不确定的。它们的范围可以从永久更新到完全不发生,再到崩溃。
更改代码以使用NSOperationQueue.mainQueue()
作为处理更新的队列应解决问题。
如果您需要在处理程序中进行耗时处理以进行加速度计更新,那么您可以继续使用之前使用的后台队列,但是将您的UIKit调用包装在dispatch_async中:
dispatch_async(dispatch_get_main_queue())
{
//UIKit code here
}
这样,耗时的加速度计更新代码不会使主线程陷入困境,但仍会在主线程上完成UI更新。