• 软件:1159
  • 资讯:38676|
  • 收录网站:95940|

IT精英团

深入浅出 ReactNative 事件响应系统(上)

深入浅出 ReactNative 事件响应系统(上)

浏览次数:
评论次数:
编辑: mob604756fdc4e1
信息来源: 51CTO博客
更新日期: 2021-05-13 16:04:09
摘要

深入浅出 ReactNative 事件响应系统(上),mob604756fdc4e1的博客原创的Java文章。

  • 资讯详情

概述

我们知道 ReactNative 中很重要的一个 view 是 RCTRootView,它几乎是我们在 RN 中接触到的第一个 View。本篇将会从 RCTRootView 入手,详细梳理一下 RN 中事件响应之 Native 部分。

  • 本篇主要分析 ReactNative 框架中Natvie部分的事件处理流程。如果对 iOS 事件分发、处理系统了解比较清楚的话,特别容易理解,另外限于篇幅关于 JavaScript 部分的事件分发及处理我们后面再做分析。

RN 事件响应之 Native 部分

  • RCTRootView 创建后,在其 bundleFinishedLoading 方法中会创建一个 RCTRootContentView,RCTRootContentView 在创建时它会调用 _bridge.uiManager 的 registerRootView 方法将自己注册为 rootView,所以站在 RN 的角度看,其实这才是名副其实的 rootView(因为它的 reactTag%10=1);

  • RCTRootContentView 中有一个 RCTTouchHandler,在 initWithFrame 的时候初始化并 attachToView 到 RCTRootContentView 上。

  • RCTTouchHandler 是什么呢?它其实是 UIGestureRecognizer 的一个子类,如果对 iOS 的事件传递了解的比较清楚的话,我们可以猜到,ReactNative 想要通过它拦截 View 的所有事件并转发到 ReactNative,进而在 RN 进行分发、识别,最终实现了一套事件响应系统。

    • RCTTouchHandler 为什么可以拦截所有事件?因为手势的优先级比普通视图更高,其实 UIGestureRecognizer 也有 touchesBegan、touchesMoved 等一系列方法,我们只需要在子类中重写这几个方法就可以拦截所有事件。(好好思考一下 iOS 的事件传递机制和自定义手势)

    • 以下是 RCTTouchHandler 中几个 touches* 方法的实现代码:

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{  [super touchesBegan:touches withEvent:event]; // "start" has to record new touches *before* extracting the event. // "end"/"cancel" needs to remove the touch *after* extracting the event. [self _recordNewTouches:touches];  [self _updateAndDispatchTouches:touches eventName:@"touchStart"]; if (self.state == UIGestureRecognizerStatePossible) { self.state = UIGestureRecognizerStateBegan;  } else if (self.state == UIGestureRecognizerStateBegan) { self.state = UIGestureRecognizerStateChanged;  }}- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{  [super touchesMoved:touches withEvent:event];  [self _updateAndDispatchTouches:touches eventName:@"touchMove"]; self.state = UIGestureRecognizerStateChanged;}- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{  [super touchesEnded:touches withEvent:event];  [self _updateAndDispatchTouches:touches eventName:@"touchEnd"]; if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { self.state = UIGestureRecognizerStateEnded;  } else if (RCTAnyTouchesChanged(event.allTouches)) { self.state = UIGestureRecognizerStateChanged;  }  [self _recordRemovedTouches:touches];}- (void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{  [super touchesCancelled:touches withEvent:event];  [self _updateAndDispatchTouches:touches eventName:@"touchCancel"]; if (RCTAllTouchesAreCancelledOrEnded(event.allTouches)) { self.state = UIGestureRecognizerStateCancelled;  } else if (RCTAnyTouchesChanged(event.allTouches)) { self.state = UIGestureRecognizerStateChanged;  }  [self _recordRemovedTouches:touches];}


  • 我们以 touchesBegan 为例深入分析。

    • [self _recordNewTouches:touches] 比较重要,但我们暂时不管。

    • touchesBegan 中有这么行代码:

[self _updateAndDispatchTouches:touches eventName:@"touchStart"];

我们来看看 _updateAndDispatchTouches:eventName: 的实现:

/** * Constructs information about touch events to send across the serialized * boundary. This data should be compliant with W3C `Touch` objects. This data * alone isn't sufficient to construct W3C `Event` objects. To construct that, * there must be a simple receiver on the other side of the bridge that * organizes the touch objects into `Event`s. * * We send the data as an array of `Touch`es, the type of action * (start/end/move/cancel) and the indices that represent "changed" `Touch`es * from that array. */ - (void)_updateAndDispatchTouches:(NSSet<UITouch *> *)touches                        eventName:(NSString *)eventName{ // Update touches NSMutableArray<NSNumber *> *changedIndexes = [NSMutableArray new]; for (UITouch *touch in touches) { NSInteger index = [_nativeTouches indexOfObject:touch]; if (index == NSNotFound) { continue; }    [self _updateReactTouchAtIndex:index];    [changedIndexes addObject:@(index)];  } if (changedIndexes.count == 0) { return;  } // Deep copy the touches because they will be accessed from another thread // TODO: would it be safer to do this in the bridge or executor, rather than trusting caller? NSMutableArray<NSDictionary *> *reactTouches = [[NSMutableArray alloc] initWithCapacity:_reactTouches.count]; for (NSDictionary *touch in _reactTouches) {    [reactTouches addObject:[touch copy]];  } BOOL canBeCoalesced = [eventName isEqualToString:@"touchMove"]; // We increment `_coalescingKey` twice here just for sure that // this `_coalescingKey` will not be reused by ahother (preceding or following) event // (yes, even if coalescing only happens (and makes sense) on events of the same type). if (!canBeCoalesced) { _coalescingKey++;  }  RCTTouchEvent *event = [[RCTTouchEvent alloc] initWithEventName:eventName                                                         reactTag:self.view.reactTag reactTouches:reactTouches                                                   changedIndexes:changedIndexes                                                    coalescingKey:_coalescingKey]; if (!canBeCoalesced) {    _coalescingKey++;  }  [_eventDispatcher sendEvent:event];}

注释写得很清楚:

  • 这段代码主要在构造 touch events 信息,并且这构造的最好还得与 W3C Touch objects 兼容。

  • 但这里现在的信息不足以构造 W3C Event objects,还需要做一些其它处理。

  • 无论是否理解上面的注释,这个方法最终根据 eventName、reactTag 以及自己创建的 reactTouches 创建出来一个 RCTTouchEvent 对象。

  • RCTTouchEvent 是个很好的东西,它遵循了 RCTEvent 协议,我们后面慢慢分析。现在你需要知道的是:RCTEventDispatcher在sentEvent 的时候,唯一的一个参数就是这个东西。

  • OK,我们现在来看看 RCTEventDispatcher 的sentEvent: 方法的实现:

- (void)sendEvent:(id<RCTEvent>)event {  [_observersLock lock]; for (id<RCTEventDispatcherObserver> observer in _observers) {    [observer eventDispatcherWillDispatchEvent:event];  }  [_observersLock unlock];  [_eventQueueLock lock];  NSNumber *eventID = RCTGetEventID(event);  id<RCTEvent> previousEvent = _events[eventID]; if (previousEvent) {    RCTAssert([event canCoalesce], @"Got event %@ which cannot be coalesced, but has the same eventID %@ as the previous event %@", event, eventID, previousEvent); event = [previousEvent coalesceWithEvent:event];  } else {    [_eventQueue addObject:eventID];  }  _events[eventID] = event;  BOOL scheduleEventsDispatch = NO; if (!_eventsDispatchScheduled) {    _eventsDispatchScheduled = YES;    scheduleEventsDispatch = YES;  } // We have to release the lock before dispatching block with events, // since dispatchBlock: can be executed synchronously on the same queue. // (This is happening when chrome debugging is turned on.) [_eventQueueLock unlock]; if (scheduleEventsDispatch) { [_bridge dispatchBlock:^{      [self flushEventsQueue];    } queue:RCTJSThread];  }}
  • 首先遍历所有的观察者(遵循RCTEventDispatcherObserver 协议),并通知 eventDispatcherWillDispatchEvent 。

  • 操作 _eventQueue。

  • 在 JS 线程中,调用 [self flushEventsQueue]。即遍历 eventQueue,分别调用 dispatchEvent (别混了啊,dispatchEvent 是 RCTEventDispatcher类的方法)。

- (void)flushEventsQueue{  [_eventQueueLock lock]; NSDictionary *events = _events;  _events = [NSMutableDictionary new]; NSMutableArray *eventQueue = _eventQueue;  _eventQueue = [NSMutableArray new];  _eventsDispatchScheduled = NO;  [_eventQueueLock unlock]; for (NSNumber *eventId in eventQueue) {    [self dispatchEvent:events[eventId]];  }}
  • flushEventsQueue 遍历事件队列,一个一个的分发事件,其实分发事件的本质就是去执行相应的JS代码。

  • 下面是 dispatchEvent 的源代码:

- (void)dispatchEvent:(id<RCTEvent>)event{ NSLog(@"xiaowei.li:%@", event.eventName);  [_bridge enqueueJSCall:[[event class] moduleDotMethod] args:[event arguments]];}
  • 不同 RN 框架的版本,实现稍微不同,到目前为止,共有三个 bridge:RCTBridge、RCTBatchedBridge、RCTCxxBridge。

  • 还记得我们刚才提到的 event 的类型么?RCTTouchEvent,我们再列出它两个比较重要的方法:

+ (NSString *)moduleDotMethod{ return @"RCTEventEmitter.receiveTouches";}- (NSArray *)arguments{ return @[RCTNormalizeInputEventName(_eventName), _reactTouches, _changedIndexes];}
  • 我们继续上面的代码往下跟,dispatchEvent 调用的是 bridge 的 enqueueJSCall:args: 方法。

- (void)enqueueJSCall:(NSString *)moduleDotMethod args:(NSArray *)args{ NSArray<NSString *> *ids = [moduleDotMethod componentsSeparatedByString:@"."]; NSString *module = ids[0]; NSString *method = ids[1];  [self enqueueJSCall:module method:method args:args completion:NULL];}
  • 注意:这里已经进入了 RCTBridge 类。我们再继续跟一下:

/** * Public. Can be invoked from any thread. */ - (void)enqueueJSCall:(NSString *)module method:(NSString *)method args:(NSArray *)args completion:(dispatch_block_t)completion{ if (!self.valid) { return;  } /**   * AnyThread */ RCT_PROFILE_BEGIN_EVENT(RCTProfileTagAlways, @"-[RCTCxxBridge enqueueJSCall:]", nil); RCTProfileBeginFlowEvent();  [self _runAfterLoad:^{ RCTProfileEndFlowEvent(); if (self->_reactInstance) { self->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],                                           convertIdToFollyDynamic(args ?: @[])); // ensureOnJavaScriptThread may execute immediately, so use jsMessageThread, to make sure // the block is invoked after callJSFunction if (completion) { if (self->_jsMessageThread) { self->_jsMessageThread->runOnQueue(completion);        } else { RCTLogWarn(@"Can't invoke completion without messageThread");        }      }    }  }]; RCT_PROFILE_END_EVENT(RCTProfileTagAlways, @"");}
  • 到这里为止,很显然已经到了最后一步,通过执行 self->_reactInstance->callJSFunction([module UTF8String], [method UTF8String],convertIdToFollyDynamic(args ?: @[])); 这么一行代码,将锅甩给了 JavaScript。

题外小结

  • 除了 RCTTouchEvent 之外,还有 RCTScrollEvent(去哪儿的 QRCTScrollEvent 基本一样):

+ (NSString *)moduleDotMethod{ return @"RCTEventEmitter.receiveEvent";}- (NSArray *)arguments{ return @[self.viewTag, RCTNormalizeInputEventName(self.eventName), [self body]];}
  • 本篇文章涉及的到类和协议有以下几个,慢慢体会,后面我们慢慢分析:

    • RCTRootView

    • RCTRootContentView

    • RCTUIManager

    • RCTTouchHandler

    • RCTTouchEvent

    • RCTEventDispatcher

    • RCTEvent协议

    • RCTEventDispatcherObserver

    • RCTBridge

    • RCTBatchedBridge

    • RCTCxxBridge


标签: Java
深入浅出 setNativeProps:从实践窥视原理
« 上一篇
返回列表
下一篇 »
发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表
你会是第一个来这里评论的人吗?
最近发布资讯
更多