HistoryPreloader.m 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139
  1. //
  2. // HistoryPreloader.m
  3. // AIIM
  4. //
  5. // Created by qitewei on 2025/5/20.
  6. //
  7. #import "HistoryPreloader.h"
  8. #import <objc/runtime.h>
  9. @interface HistoryPreloader() {
  10. CADisplayLink *_displayLink;
  11. CGFloat _initialOffset;
  12. BOOL _isUserInitiatedScroll;
  13. NSTimeInterval _lastTriggerTime;
  14. CGFloat _lastScrollVelocity;
  15. }
  16. @end
  17. @implementation HistoryPreloader
  18. - (instancetype)init {
  19. self = [super init];
  20. if (self) {
  21. _threshold = 300.0; // 默认阈值
  22. _initialOffset = CGFLOAT_MAX;
  23. }
  24. return self;
  25. }
  26. - (void)dealloc {
  27. [self stopMonitoring];
  28. }
  29. #pragma mark - 公共方法
  30. - (void)startMonitoring {
  31. if (_displayLink) return;
  32. // 记录初始偏移量(考虑contentInset)
  33. _initialOffset = -self.tableView.contentInset.top;
  34. _isUserInitiatedScroll = NO;
  35. // 设置DisplayLink监控滚动
  36. _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(handleScrollCheck)];
  37. [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
  38. }
  39. - (void)stopMonitoring {
  40. [_displayLink invalidate];
  41. _displayLink = nil;
  42. }
  43. - (void)resetScrollState {
  44. _isUserInitiatedScroll = NO;
  45. _initialOffset = -self.tableView.contentInset.top;
  46. }
  47. #pragma mark - 私有方法
  48. - (void)handleScrollCheck {
  49. CGFloat currentOffset = self.tableView.contentOffset.y;
  50. // 1. 检测是否是用户发起的滑动
  51. if (!_isUserInitiatedScroll) {
  52. // 使用5pt的阈值避免误判
  53. if (fabs(currentOffset - _initialOffset) > 5.0) {
  54. _isUserInitiatedScroll = YES;
  55. } else {
  56. return; // 未滑动,直接返回
  57. }
  58. }
  59. // 2. 计算滚动速度(用于动态调整阈值)
  60. CFTimeInterval timestamp = _displayLink.timestamp;
  61. static CFTimeInterval lastTimestamp = 0;
  62. if (lastTimestamp != 0) {
  63. _lastScrollVelocity = (currentOffset - _lastScrollVelocity) / (timestamp - lastTimestamp);
  64. }
  65. lastTimestamp = timestamp;
  66. _lastScrollVelocity = currentOffset;
  67. // 3. 动态调整阈值(可选)
  68. CGFloat dynamicThreshold = [self dynamicThresholdBasedOnVelocity:_lastScrollVelocity];
  69. // 4. 检查是否需要触发预加载
  70. if (currentOffset < dynamicThreshold) {
  71. [self tryTriggerPreload];
  72. }
  73. }
  74. - (CGFloat)dynamicThresholdBasedOnVelocity:(CGFloat)velocity {
  75. // 基础阈值
  76. CGFloat baseThreshold = self.threshold;
  77. // 根据滚动速度调整(速度越快,阈值越大,提前触发)
  78. if (fabs(velocity) > 100) { // 快速滚动
  79. return baseThreshold * 1.5;
  80. } else if (fabs(velocity) < 20) { // 慢速滚动
  81. return baseThreshold * 0.8;
  82. }
  83. return baseThreshold;
  84. }
  85. - (void)tryTriggerPreload {
  86. // 冷却时间检查(至少间隔1秒)
  87. NSTimeInterval now = [NSDate timeIntervalSinceReferenceDate];
  88. if (now - _lastTriggerTime < 1.0){
  89. // 触发过快加载加载回调
  90. if (self.toofast) {
  91. self.toofast();
  92. }
  93. return;
  94. }
  95. _lastTriggerTime = now;
  96. // 触发加载回调
  97. if (self.loadBlock) {
  98. self.loadBlock();
  99. }
  100. // 短暂停止监控防止连续触发
  101. [self stopMonitoring];
  102. __weak typeof(self) weakSelf = self;
  103. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  104. [weakSelf startMonitoring];
  105. });
  106. }
  107. #pragma mark - 只读属性访问
  108. - (BOOL)isUserInitiatedScroll {
  109. return _isUserInitiatedScroll;
  110. }
  111. @end