React Native

深入浅出RN中的定时器(下)

2019-03-30  本文已影响0人  码农二哥

如果你想知道RN中定时器在Native端是如何驱动实现的,如果你对RN源码感兴趣或者有学习欲望的,如果你想知道setTimeout、setIdleCallback确切调用时机的话,不妨仔细看看本篇文章;如果你仅仅想停留在表面使用上,完全可以忽略本篇。如果不懂OC,可能看起来略微吃力。

RCTDisplayLink

先看看它的基本接口和基本属性:

@interface RCTDisplayLink : NSObject
{
  CADisplayLink *_jsDisplayLink;
  NSMutableSet<RCTModuleData *> *_frameUpdateObservers;
  NSRunLoop *_runLoop;
}

- (instancetype)init;
- (void)invalidate;
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
                       withModuleData:(RCTModuleData *)moduleData;
- (void)addToRunLoop:(NSRunLoop *)runLoop;
@end
// 这个代码是在js线程执行的,所以这里获取到的runloop就是js线程的runloop
_jsMessageThread = std::make_shared<RCTMessageThread>([NSRunLoop currentRunLoop], ^(NSError *error) {
    
}

RCTDisplayLink的初始化

- (instancetype)initWithParentBridge:(RCTBridge *)bridge
{
  if ((self = [super initWithDelegate:bridge.delegate
                            bundleURL:bridge.bundleURL
                       moduleProvider:bridge.moduleProvider
                        launchOptions:bridge.launchOptions])) {
    _parentBridge = bridge;
    _performanceLogger = [bridge performanceLogger];
    registerPerformanceLoggerHooks(_performanceLogger);

    /**
     * Set Initial State
     */
    _valid = YES;
    _loading = YES;
    _pendingCalls = [NSMutableArray new];
    _displayLink = [RCTDisplayLink new];

    [RCTBridge setCurrentBridge:self];
  }
  return self;
}

DisplayLink被添加到runloop

// executeSourceCode是在RN-Native框架初始化完成时调用
- (void)executeSourceCode:(NSData *)sourceCode sync:(BOOL)sync
{
    
  // This will get called from whatever thread was actually executing JS.
  dispatch_block_t completion = ^{
    // 框架js已经执行完毕
#ifdef YRN_OPT
      // 解决com.facebook.react.JavaScript 线程无法延时释放的问题
      @autoreleasepool {
          if ([self.delegate respondsToSelector:@selector(bundleJSDidExecuted:)]) {
              [self.delegate bundleJSDidExecuted:self];
          }
      }
#endif
    
    // Flush pending calls immediately so we preserve ordering
    [self _flushPendingCalls];

    // Perform the state update and notification on the main thread, so we can't run into
    // timing issues with RCTRootView
    dispatch_async(dispatch_get_main_queue(), ^{
      [[NSNotificationCenter defaultCenter]
       postNotificationName:RCTJavaScriptDidLoadNotification
       object:self->_parentBridge userInfo:@{@"bridge": self}];

      // Starting the display link is not critical to startup, so do it last
      [self ensureOnJavaScriptThread:^{
        // Register the display link to start sending js calls after everything is setup
        [self->_displayLink addToRunLoop:[NSRunLoop currentRunLoop]];
      }];
    });
  };
    
  if (sync) {
    [self executeApplicationScriptSync:sourceCode url:self.bundleURL];
    completion();
  } else {
    [self enqueueApplicationScript:sourceCode url:self.bundleURL onComplete:completion];
  }
}

RCTDisplayLink的销毁

- (void)invalidate
{
    [self ensureOnJavaScriptThread:^{
        [self->_displayLink invalidate];
        self->_displayLink = nil;
    }];
}

RCTDisplayLink用在了什么地方?

- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
                       withModuleData:(RCTModuleData *)moduleData
{
  [_displayLink registerModuleForFrameUpdates:module withModuleData:moduleData];
}

RCTModuleData

- (void)finishSetupForInstance
{
  if (!_setupComplete && _instance) {
    RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"[RCTModuleData finishSetupForInstance]", nil);
    _setupComplete = YES;
    [_bridge registerModuleForFrameUpdates:_instance withModuleData:self];
    
#ifdef YRN_OPT
      // 解决com.facebook.react.JavaScript 线程无法延时释放的问题
      @autoreleasepool {
          [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
                                                              object:_bridge
                                                            userInfo:@{@"module": _instance, @"bridge": RCTNullIfNil(_bridge.parentBridge)}];
      }
#else
      [[NSNotificationCenter defaultCenter] postNotificationName:RCTDidInitializeModuleNotification
                                                          object:_bridge
                                                        userInfo:@{@"module": _instance, @"bridge": RCTNullIfNil(_bridge.parentBridge)}];
#endif
      
    RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");
  }
}

我们上面已经提到了RCTTiming遵循了RCTFrameUpdateObserver协议,满足了作为RCTDisplayLink观察者的条件,下面我们就来具体说说RCTTIming这个native module;

RCTTiming

- (void)didUpdateFrame:(RCTFrameUpdate *)update
{
  NSDate *nextScheduledTarget = [NSDate distantFuture];
  NSMutableArray<_RCTTimer *> *timersToCall = [NSMutableArray new];
  NSDate *now = [NSDate date]; // compare all the timers to the same base time
  // 遍历需要触发回调的timers,并找出下一个最快要触发的timer的触发时间点;
  // 每一个_RCTTimer都保存了js方传递过来的关于一个timer所需要的信息;
  for (_RCTTimer *timer in _timers.allValues) {
    if ([timer shouldFire:now]) {
      [timersToCall addObject:timer];
    } else {
      nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target];
    }
  }

  // Call timers that need to be called
  // 回调前先sort一下
  if (timersToCall.count > 0) {
    NSArray<NSNumber *> *sortedTimers = [[timersToCall sortedArrayUsingComparator:^(_RCTTimer *a, _RCTTimer *b) {
      return [a.target compare:b.target];
    }] valueForKey:@"callbackID"];
    [_bridge enqueueJSCall:@"JSTimers"
                    method:@"callTimers"
                      args:@[sortedTimers]
                completion:NULL];
  }

  // 如果有需要重复触发的timer,reschedule一下,并更新一下[下一个最快要触发的timer的触发时间点];
  // 如果不需要重复触发,则直接移除调(因为上面刚刚已经回调了);
  for (_RCTTimer *timer in timersToCall) {
    if (timer.repeats) {
      [timer reschedule];
      nextScheduledTarget = [nextScheduledTarget earlierDate:timer.target];
    } else {
      [_timers removeObjectForKey:timer.callbackID];
    }
  }

  // 如果需要idle回调,则判断当前是否idle
  if (_sendIdleEvents) {
    NSTimeInterval frameElapsed = (CACurrentMediaTime() - update.timestamp);
    // 如果这一帧16ms还没用完(16ms-已逝时间>0.001)则发送idle回调;
    // 如果这一帧剩余时间小于0.001,只能说明当前不idle;
    if (kFrameDuration - frameElapsed >= kIdleCallbackFrameDeadline) {
      NSTimeInterval currentTimestamp = [[NSDate date] timeIntervalSince1970];
      NSNumber *absoluteFrameStartMS = @((currentTimestamp - frameElapsed) * 1000);
      [_bridge enqueueJSCall:@"JSTimers"
                      method:@"callIdleCallbacks"
                        args:@[absoluteFrameStartMS]
                  completion:NULL];
    }
  }

  // Switch to a paused state only if we didn't call any timer this frame, so if
  // in response to this timer another timer is scheduled, we don't pause and unpause
  // the displaylink frivolously.
  // 如果没有timer了,那么就切换到pause状态;如果很久之后才有timer触发(超过1s),直接换成NSTimer来提高性能;
  if (!_sendIdleEvents && timersToCall.count == 0) {
    // No need to call the pauseCallback as RCTDisplayLink will ask us about our paused
    // status immediately after completing this call
    if (_timers.count == 0) {
      _paused = YES;
    }
    // If the next timer is more than 1 second out, pause and schedule an NSTimer;
    // 如果最近一个timer触发需要1s以上,则pause并切换到NSTimer可能会提高性能;
    else if ([nextScheduledTarget timeIntervalSinceNow] > kMinimumSleepInterval) {
      [self scheduleSleepTimer:nextScheduledTarget];
      _paused = YES;
    }
  }
}
// RCTDisplayLink.m
- (void)registerModuleForFrameUpdates:(id<RCTBridgeModule>)module
                       withModuleData:(RCTModuleData *)moduleData
{
  // 如果natigve module没有遵循RCTFrameUpdateObserver协议,则直接return
  if (![moduleData.moduleClass conformsToProtocol:@protocol(RCTFrameUpdateObserver)] ||
      [_frameUpdateObservers containsObject:moduleData]) {
    return;
  }

  // 保存观察者
  [_frameUpdateObservers addObject:moduleData];

  // Don't access the module instance via moduleData, as this will cause deadlock
  id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)module;
  __weak typeof(self) weakSelf = self;
  observer.pauseCallback = ^{
    typeof(self) strongSelf = weakSelf;
    if (!strongSelf) {
      return;
    }

    CFRunLoopRef cfRunLoop = [strongSelf->_runLoop getCFRunLoop];
    if (!cfRunLoop) {
      return;
    }

    if ([NSRunLoop currentRunLoop] == strongSelf->_runLoop) {
      [weakSelf updateJSDisplayLinkState];
    } else {
      CFRunLoopPerformBlock(cfRunLoop, kCFRunLoopDefaultMode, ^{
        [weakSelf updateJSDisplayLinkState];
      });
      CFRunLoopWakeUp(cfRunLoop);
    }
  };

  // Assuming we're paused right now, we only need to update the display link's state
  // when the new observer is not paused. If it not paused, the observer will immediately
  // start receiving updates anyway.
  if (![observer isPaused] && _runLoop) {
    CFRunLoopPerformBlock([_runLoop getCFRunLoop], kCFRunLoopDefaultMode, ^{
      [self updateJSDisplayLinkState];
    });
  }
}
// RCTDisplayLink.m
- (void)updateJSDisplayLinkState
{
  RCTAssertRunLoop();

  BOOL pauseDisplayLink = YES;
  for (RCTModuleData *moduleData in _frameUpdateObservers) {
    id<RCTFrameUpdateObserver> observer = (id<RCTFrameUpdateObserver>)moduleData.instance;
    if (!observer.paused) {
      pauseDisplayLink = NO;
      break;
    }
  }

  _jsDisplayLink.paused = pauseDisplayLink;
}

总结

上一篇下一篇

猜你喜欢

热点阅读