iOS9 此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃
Posted
技术标签:
【中文标题】iOS9 此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃【英文标题】:iOS9 This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes 【发布时间】:2015-09-16 22:21:04 【问题描述】:我刚刚下载了最新的 XCode(7.1 测试版)并开始使用 ios9。
我有一个应用程序在 iOS8 中完美运行,没有错误,但现在我在 UITableViewCell 类中重写 drawRect 方法得到以下错误:
“此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃。这将在未来的版本中导致异常。”
这是回溯:
Stack:(
0 CoreFoundation 0x000000010a749f65 __exceptionPreprocess + 165
1 libobjc.A.dylib 0x0000000109dcfdeb objc_exception_throw + 48
2 CoreFoundation 0x000000010a749e9d +[NSException raise:format:] + 205
3 Foundation 0x0000000109b442e5 _AssertAutolayoutOnMainThreadOnly + 79
4 Foundation 0x00000001099a4ece -[NSISEngine withBehaviors:performModifications:] + 31
5 UIKit 0x000000010b9d425b -[UIView(AdditionalLayoutSupport) _withAutomaticEngineOptimizationDisabledIfEngineExists:] + 58
6 UIKit 0x000000010b9d4d9e -[UIView(AdditionalLayoutSupport) updateConstraintsIfNeeded] + 254
7 UIKit 0x000000010b702760 -[UITableViewCellContentView updateConstraintsIfNeeded] + 185
8 UIKit 0x000000010b9d5ab3 -[UIView(AdditionalLayoutSupport) _updateConstraintsAtEngineLevelIfNeeded] + 272
9 UIKit 0x000000010b1e6274 -[UIView(Hierarchy) _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 159
10 UIKit 0x000000010b1f5d84 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 710
11 QuartzCore 0x000000010ae1059a -[CALayer layoutSublayers] + 146
12 QuartzCore 0x000000010ae04e70 _ZN2CA5Layer16layout_if_neededEPNS_11TransactionE + 366
13 QuartzCore 0x000000010ae04cee _ZN2CA5Layer28layout_and_display_if_neededEPNS_11TransactionE + 24
14 QuartzCore 0x000000010adf9475 _ZN2CA7Context18commit_transactionEPNS_11TransactionE + 277
15 QuartzCore 0x000000010ae26c0a _ZN2CA11Transaction6commitEv + 486
16 QuartzCore 0x000000010ae2737c _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv + 92
17 CoreFoundation 0x000000010a675967 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
18 CoreFoundation 0x000000010a6758d7 __CFRunLoopDoObservers + 391
19 CoreFoundation 0x000000010a66ae4c CFRunLoopRunSpecific + 524
20 CoreFoundation 0x000000010a71e011 CFRunLoopRun + 97
21 SDWebImage 0x000000010971773c -[SDWebImageDownloaderOperation start] + 1868
22 Foundation 0x0000000109961e47 __NSOQSchedule_f + 194
23 libdispatch.dylib 0x000000010d93849b _dispatch_client_callout + 8
24 libdispatch.dylib 0x000000010d91e8ec _dispatch_queue_drain + 2215
25 libdispatch.dylib 0x000000010d91de0d _dispatch_queue_invoke + 601
26 libdispatch.dylib 0x000000010d920a56 _dispatch_root_queue_drain + 1420
27 libdispatch.dylib 0x000000010d9204c5 _dispatch_worker_thread3 + 111
28 libsystem_pthread.dylib 0x000000010dc80a9d _pthread_wqthread + 729
29 libsystem_pthread.dylib 0x000000010dc7e3dd start_wqthread + 13
)
这里是drawRect方法:
override func drawRect(rect: CGRect)
super.drawRect(rect)
let cellRect = rect
// Values
let buttonBoxX = CELL_MARGIN + CELL_MARGIN/2
let buttonBoxY = cellRect.height - buttonBoxHeight
let buttonBoxWidth = rect.width - CELL_MARGIN * 3
// Set Button Box
let buttonBoxRect = CGRectMake(buttonBoxX, buttonBoxY, buttonBoxWidth, buttonBoxHeight )
let buttonBox = UIBezierPath(roundedRect: buttonBoxRect, byRoundingCorners: [.BottomRight, .BottomLeft], cornerRadii: CGSize(width: CORNER_RADIUS, height: CORNER_RADIUS)) // Create the path
UIColor.whiteColor().setFill() // Set the Fill to be white
buttonBox.fill()
我的理解(参见this question)是复杂的计算等应该在后台线程上完成,而在主线程上更新 UI,因为 UI 不是线程安全的。
但是,如果我使用以下内容:
override func drawRect(rect: CGRect)
super.drawRect(rect)
let cellRect = rect
// Values
let buttonBoxX = CELL_MARGIN + CELL_MARGIN/2
let buttonBoxY = cellRect.height - buttonBoxHeight
let buttonBoxWidth = rect.width - CELL_MARGIN * 3
// Set Button Box
let buttonBoxRect = CGRectMake(buttonBoxX, buttonBoxY, buttonBoxWidth, buttonBoxHeight )
let buttonBox = UIBezierPath(roundedRect: buttonBoxRect, byRoundingCorners: [.BottomRight, .BottomLeft], cornerRadii: CGSize(width: CORNER_RADIUS, height: CORNER_RADIUS)) // Create the path
UIColor.whiteColor().setFill() // Set the Fill to be white
dispatch_async(dispatch_get_main_queue(),
buttonBox.fill()
)
我现在收到一个 CGContext 错误……
<Error>: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextSetFlatness: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextAddPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextDrawPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextRestoreGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextSaveGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextSetFlatness: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextAddPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextDrawPath: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
<Error>: CGContextRestoreGState: invalid context 0x0. If you want to see the backtrace, please set CG_CONTEXT_SHOW_BACKTRACE environmental variable.
有什么建议吗?
解决方案: 好的。这是交易。我正在将图像异步加载到 UIImageView 中,但不是在主线程的 UI 中而是在后台线程上“绘制”它。此外;在将图像添加到 UI 后,我在 UITableViewCell 上调用了 setNeedsDisplay,因此再次调用了 drawRect 方法……但这次是在后台线程上。
【问题讨论】:
从您的问题中不清楚,您在后台线程上执行了哪些代码?只是drawRect
还是别的什么?您究竟是如何将其更改为使用主队列的?您通常不会自己调度 drawRect,而是会根据 UIKit 的要求调用它。
我编辑了我的问题来回答你的问题。确实 drawRect 是由 UIKit 而不是我调用的。但是,错误是由 drawRect 中的 UI 发生的事情引起的。
调度到drawRect
内的主队列为时已晚——您正在从后台线程更新 UI 的其他地方——也许您正在从网络任务中检索单元数据?这是您需要找到并分派到主队列的更新
好的。谢谢(你的)信息。我不确定。我正在从网络任务中检索数据……但是,奇怪的是,如果我注释 drawRect 方法,我的错误就会消失……
【参考方案1】:
您注意到的第一个错误看起来与您的drawRect:
代码无关。你的drawRect:
看起来不错。它没有做任何特别复杂的事情。如果您的路径更复杂,您可能想要缓存它,但它可能按原样很好。您更有可能尝试在后台线程的其他地方修改 UI。它可能只会在您覆盖 drawRect:
时出现,因为这会改变 UI 更新的发生方式(如果没有自定义 drawRect:
,系统可能能够应用简单的转换)。您需要查找在后台线程上进行 UIKit 调用的位置。
如果有疑问,如果它以UI
开头,您可能无法在后台线程上使用它。这并不完全正确(您可以在后台线程上创建 UIBezierPath
,甚至可以将其绘制到非屏幕上下文中),但作为第一个近似值,寻找它是一件好事。
这段代码完全不正确:
dispatch_async(dispatch_get_main_queue(),
buttonBox.fill()
)
对drawRect:
的调用最好已经在主队列中(因此分派到主队列没有帮助)。如果不是,请参见上文。当这个块执行时,绘制周期已经结束,所以没有上下文可以绘制了。
【讨论】:
你完全正确!该错误不是由我的 drawRect 方法触发的,而是由异步请求触发的,以获取然后我忘记在主队列上分派的图像……新规则:在凌晨 1 点之后永远不要编码。 最好的编码发生在凌晨 1 点之后!!【参考方案2】:基本上This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release
声明您不应该更新 UI,尤其是非 UI 线程中的自动布局相关部分。修复使用dispatch_async(dispatch_get_main_queue()) // your code
,或
斯威夫特 3:
DispatchQueue.main.async
buttonBox.fill()
【讨论】:
【参考方案3】:应该尝试符号断点来检测问题:-
然后把你的 UI 更新代码放到主线程中
DispatchQueue.main.async
【讨论】:
以上是关于iOS9 此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃的主要内容,如果未能解决你的问题,请参考以下文章
iOS 9 键盘:此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃
Xcode7:此应用程序正在从后台线程修改自动布局引擎,这可能导致引擎损坏和奇怪的崩溃