iOS动画块调用layoutIfNeeded()触发EXC_BAD_ACCESS崩溃排查
iOS动画触发layoutIfNeeded崩溃诊断
问题背景
崩溃发生在动画块内调用layoutIfNeeded()方法的位置,已提供完整相关函数实现与崩溃报告,初步判断崩溃与内存问题相关,暂未定位具体根因。
问题代码
private func showWarningView() { DispatchQueue.main.async { [weak self] in guard let strongSelf = self else { return } if let safeAreaHeight = strongSelf.superview?.safeAreaLayoutGuide.layoutFrame.size.height { let visibleBannerHeight: CGFloat = strongSelf.isBannerVisible ? strongSelf.bannerHeight : 0 let newConstraintConstant = (safeAreaHeight - strongSelf.externalBottomOffset - visibleBannerHeight) - strongSelf.warningBannerHeight strongSelf.addInternalBottomOffset(with: strongSelf.warningBannerHeight) strongSelf.warningViewTopConstraint.constant = newConstraintConstant } strongSelf.isWarningViewVisible = true let showWarningView = UIViewPropertyAnimator(duration: 0.25, curve: .easeIn, animations: { self?.layoutIfNeeded() // <---- 崩溃发生在此处 }) showWarningView.startAnimation() } }
崩溃基础信息
Exception Type: EXC_BAD_ACCESS (SIGSEGV) Exception Subtype: KERN_INVALID_ADDRESS at 0x0000000000000010 Exception Codes: 0x0000000000000001, 0x0000000000000010 VM Region Info: 0x10 is not in any region. Bytes before following region: 4337303536 REGION TYPE START - END [ VSIZE] PRT/MAX SHRMOD REGION DETAIL UNUSED SPACE AT START ---> __TEXT 102860000-102864000 [ 16K] r-x/r-x SM=COW ... Exception Note: EXC_CORPSE_NOTIFY Termination Reason: SIGNAL 11 Segmentation fault: 11 Terminating Process: exc handler [265]
- 崩溃触发线程:Thread 0(主线程)
- 内核诊断信息:VM - Compressor failed a blocking pager_get
崩溃调用栈(Thread 0)
0 libobjc.A.dylib 0x00000001d5d22f40 objc_msgSend + 32 1 UIKitCore 0x00000001bf72d010 -[UIView(AdditionalLayoutSupport) _nsis_center:bounds:inEngine:forLayoutGuide:] + 112 (NSLayoutConstraint_UIKitAdditions.m:3513) 2 UIKitCore 0x00000001bf7455b0 -[UILayoutGuide _updateLayoutFrameInOwningView:fromEngine:] + 80 (UILayoutGuide.m:109) 3 UIKitCore 0x00000001bf6c8350 -[UIView(AdditionalLayoutSupport) _is_layout] + 320 (NSLayoutConstraint_UIKitAdditions.m:3173) 4 UIKitCore 0x00000001bf5b1a7c -[UIView _updateConstraintsAsNecessaryAndApplyLayoutFromEngine] + 1028 (UIView.m:12933) 5 UIKitCore 0x00000001bf5a9fb4 -[UIView(CALayerDelegate) layoutSublayersOfLayer:] + 2592 (UIView.m:18426) 6 QuartzCore 0x00000001c0d20cd0 CA::Layer::layout_if_needed(CA::Transaction*) + 532 (CALayer.mm:10116) 7 UIKitCore 0x00000001bf60dd90 -[UIView(Hierarchy) layoutBelowIfNeeded] + 584 (UIView.m:12868) 8 ****** 0x0000000102eb59d4 closure #1 in closure #1 in BottomDrawView.showWarningView() + 52 (BottomDrawView.swift:410) 9 ****** 0x0000000102eb59d4 partial apply for closure #1 in closure #1 in BottomDrawView.showWarningView() + 68 (:0) 10 ****** 0x0000000102f33378 thunk for @escaping @callee_guaranteed () -> () + 20 (:0) 11 UIKitCore 0x00000001bf8ee77c -[UIViewPropertyAnimator _runAnimations] + 212 (UIViewPropertyAnimator.m:1912) 12 UIKitCore 0x00000001bf78c6a4 __49-[UIViewPropertyAnimator startAnimationAsPaused:]_block_invoke_2 + 108 (UIViewPropertyAnimator.m:3279) 13 UIKitCore 0x00000001bf637fec __49-[UIViewPropertyAnimator startAnimationAsPaused:]_block_invoke + 128 (UIViewPropertyAnimator.m:3267) 14 UIKitCore 0x00000001bfb6eba8 __49-[UIViewPropertyAnimator startAnimationAsPaused:]_block_invoke.1142 + 284 (UIViewPropertyAnimator.m:3276) 15 UIKitCore 0x00000001bf95fa20 -[UIViewPropertyAnimator _setupAnimationTracking:] + 132 (UIViewPropertyAnimator.m:3230) 16 UIKitCore 0x00000001bf4c146c -[UIViewPropertyAnimator startAnimationAsPaused:] + 1048 (UIViewPropertyAnimator.m:3334) 17 ****** 0x0000000102eb4868 closure #1 in BottomDrawView.showWarningView() + 460 (BottomDrawView.swift:412) 18 ****** 0x0000000102f33378 thunk for @escaping @callee_guaranteed () -> () + 20 (:0) 19 libdispatch.dylib 0x00000001bcca2e6c _dispatch_call_block_and_release + 32 (init.c:1517) 20 libdispatch.dylib 0x00000001bcca4a30 _dispatch_client_callout + 20 (object.m:560) 21 libdispatch.dylib 0x00000001bccb2f48 _dispatch_main_queue_drain + 928 (inline_internal.h:2622) 22 libdispatch.dylib 0x00000001bccb2b98 _dispatch_main_queue_callback_4CF + 44 (queue.c:7770) 23 CoreFoundation 0x00000001bcff5800 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 16 (CFRunLoop.c:1795) 24 CoreFoundation 0x00000001bcfaf704 __CFRunLoopRun + 2532 (CFRunLoop.c:3144) 25 CoreFoundation 0x00000001bcfc2bc8 CFRunLoopRunSpecific + 600 (CFRunLoop.c:3268) 26 GraphicsServices 0x00000001d90f6374 GSEventRunModal + 164 (GSEvent.c:2200) 27 UIKitCore 0x00000001bf932648 -[UIApplication _run] + 1100 (UIApplication.m:3511) 28 UIKitCore 0x00000001bf6b3d90 UIApplicationMain + 364 (UIApplication.m:5064) 29 ****** 0x0000000102864a08 main + 68 (AppDelegate.swift:22) 30 dyld 0x0000000104505ce4 start + 520 (dyldMain.cpp:879)
根因定位
这是野指针访问触发的EXC_BAD_ACCESS崩溃,崩溃地址0x0000000000000010是典型的空指针偏移16位访问特征,对应objc_msgSend向已释放对象发送消息的场景,结合调用栈布局阶段访问UILayoutGuide的逻辑,核心问题是闭包捕获的引用失效:
- 外层GCD主队列闭包虽然用了
[weak self],也通过strongSelf做了入栈时的空值校验,但创建UIViewPropertyAnimator时,动画闭包没有显式声明弱引用捕获,直接捕获了self?。UIViewPropertyAnimator会被UIKit动画核心强持有直到动画执行完成,如果动画运行过程中BottomDrawView被从视图层级移除、完成释放,动画闭包里捕获的self不会被自动置空,会变成悬垂野指针。 - 从调用栈看,崩溃发生在Auto Layout遍历
UILayoutGuide更新布局帧的阶段:BottomDrawView释放时,它绑定的布局向导(包括安全区布局向导、自定义布局约束关联对象)已经被回收,此时执行layoutIfNeeded触发布局遍历,就会访问到已释放的内存块,触发段错误。
修复方案
修改动画闭包的捕获列表,对self做弱引用捕获,动画执行前重新做一次空值校验:
let showWarningView = UIViewPropertyAnimator(duration: 0.25, curve: .easeIn, animations: { [weak self] in guard let self = self else { return } self.layoutIfNeeded() })
额外优化点:
- 执行
layoutIfNeeded前可增加校验,判断self.window != nil时再触发布局,避免视图不在可见层级时执行无效动画 - 在
BottomDrawView的deinit方法中终止正在运行的该动画器,避免动画对象持有无效引用:showWarningView.stopAnimation(true)
内容的提问来源于stack exchange,提问作者DaVinci
相关产品推荐
相关产品推荐

