iOS应用CADisplayLink回调调用setContentOffset触发EXC_BAD_ACCESS崩溃
iOS应用CADisplayLink回调调用
setContentOffset:崩溃问题解决 问题现象
iOS应用在CADisplayLink回调方法里调用[UICollectionView setContentOffset:animated:]时,触发EXC_BAD_ACCESS (KERN_INVALID_ADDRESS / SIGSEGV)崩溃。崩溃根源位于UIKit滚动指示器布局内部,流程中调用了Swift Observation框架相关代码。
崩溃堆栈
libswiftCore.dylib _swift_isUniquelyReferenced_nonNull_native() libswiftObservation.dylib Observation.Tracking._AccessList.addAccess(keyPath:context:) libswiftObservation.dylib Observation.Registrar.access(_:keyPath:) UIKitCore _keypath_setTm() UIKitCore _block_destroy_helper.219() UIKitCore -[UIScrollView _effectiveVerticalScrollIndicatorInsets] UIKitCore -[UIScrollView _layoutVerticalScrollIndicatorWithBounds:...] UIKitCore -[UIScrollView _adjustScrollerIndicators:alwaysShowingThem:] UIKitCore -[UIScrollView setContentOffset:] UIKitCore -[UICollectionView setContentOffset:] MyApp -[MyMarqueeView scrollTableViewOneLoop] ← line 387 CoreFoundation -[NSInvocation invokeWithTarget:] MyApp -[KSWeakProxy forwardInvocation:] QuartzCore CA::Display::DisplayLink::dispatch_items(...) UIKitCore __UIUpdateSequenceRunNext()
相关代码实现
- (void)startScrolling { self.displayLink = [CADisplayLink displayLinkWithTarget:self.weakProxy selector:@selector(scrollTableViewOneLoop)]; [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; } - (void)scrollTableViewOneLoop { CGPoint offset = self.collectionView.contentOffset; offset.y += self.scrollSpeed; [self.collectionView setContentOffset:offset animated:NO]; // 崩溃发生处 }
原因分析及解决方案
核心原因
- 滚动流程冲突:CADisplayLink的逐帧回调与UIKit自身的滚动布局流程存在时序冲突,导致滚动指示器布局时访问了无效对象。
- Observation框架引用异常:UIKit内部通过Swift Observation跟踪属性变化时,
setContentOffset:触发的流程中出现对象引用计数异常(从堆栈中_swift_isUniquelyReferenced_nonNull_native()可推断)。 - 弱代理潜在风险:使用
KSWeakProxy作为CADisplayLink的target,可能在回调触发时代理指向的对象状态不稳定,间接引发后续UIKit内部错误。
可行解决方案
- 替换CADisplayLink为UIKit动画API:避免逐帧修改
contentOffset,改用UIKit动画API减少与内部布局流程的冲突:[UIView animateWithDuration:1.0/60.0 animations:^{ // 匹配60帧刷新率 CGPoint offset = self.collectionView.contentOffset; offset.y += self.scrollSpeed; [self.collectionView setContentOffset:offset]; }]; - 严格管理对象生命周期:在
MyMarqueeView的销毁方法中停止并释放CADisplayLink,避免回调触发时对象已释放:- (void)dealloc { [self.displayLink invalidate]; self.displayLink = nil; } - 临时禁用滚动指示器:若滚动指示器非必需,可先禁用绕过崩溃:
self.collectionView.showsVerticalScrollIndicator = NO; - 检查混编兼容性:如果项目是Swift与OC混编,确保Observation框架的使用符合官方规范,避免跨语言引用计数问题。
内容的提问来源于stack exchange,提问作者iforvert
相关产品推荐
相关产品推荐

