You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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]; // 崩溃发生处
}

原因分析及解决方案

核心原因

  1. 滚动流程冲突:CADisplayLink的逐帧回调与UIKit自身的滚动布局流程存在时序冲突,导致滚动指示器布局时访问了无效对象。
  2. Observation框架引用异常:UIKit内部通过Swift Observation跟踪属性变化时,setContentOffset:触发的流程中出现对象引用计数异常(从堆栈中_swift_isUniquelyReferenced_nonNull_native()可推断)。
  3. 弱代理潜在风险:使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.06 08:04:51