React Native 分析(四)UI對(duì)象創(chuàng)建和管理

我們知道JS主要管理的是界面渲染邏輯和事件處理邏輯瘤泪,那么渲染是怎么同步到Native端的呢灶泵?初始又是怎么創(chuàng)建的呢?
RCTRootView是入口对途,RCTRootView是RN的根視圖赦邻,內(nèi)部持有了一個(gè)RCTBridge,但是這個(gè)RCTBridge并沒有太多的代碼,而是持有了另一個(gè)RCTBatchBridge對(duì)象实檀,大部分的業(yè)務(wù)邏輯都轉(zhuǎn)發(fā)給BatchBridge惶洲,BatchBridge里面寫著的大量的核心代碼
RCTUIManager是管理所有UI的對(duì)象按声,負(fù)責(zé)創(chuàng)建,更新UIView對(duì)象恬吕。這樣就借助了 iOS本身的UIView 渲染機(jī)制進(jìn)行渲染签则。
RCTUIManager又是通過上篇所提到的通信機(jī)制作為一個(gè)JS對(duì)象來(lái)操作的。

在JS里面有一個(gè)所有JSComponent的tag表铐料,在OC里面依然也有這么一個(gè)所有nativeView的Tag表_shadowViewRegistry渐裂,只有通過唯一指定的tag,這樣RCTUIManager钠惩,才能知道到底應(yīng)該操作哪一個(gè)nativeView

一柒凉、創(chuàng)建rootview

在開始正式創(chuàng)建RCTRootView的時(shí)候會(huì)創(chuàng)建一個(gè)subviewRCTContentRootView這個(gè)東西創(chuàng)建的時(shí)候需要一個(gè)reactTag,這個(gè)tag是一個(gè)很關(guān)鍵的東西篓跛,此時(shí)通過allocateRootTag方法創(chuàng)建了root得reactTag膝捞。

- (NSNumber *)reactTag
{
  RCTAssertMainQueue();
  if (!super.reactTag) {
    /**
     * Every root view that is created must have a unique react tag.
     * Numbering of these tags goes from 1, 11, 21, 31, etc
     *
     * NOTE: Since the bridge persists, the RootViews might be reused, so the
     * react tag must be re-assigned every time a new UIManager is created.
     */
    self.reactTag = [_bridge.uiManager allocateRootTag];
  }
  return super.reactTag;
}

從注釋可以看出規(guī)則是從1開始,每次創(chuàng)建一個(gè)RootView實(shí)例都會(huì)累加10愧沟,如1蔬咬,11,21央渣,31计盒,以此類推渴频。創(chuàng)建完RCTContentRootView后還要去UIManager用這個(gè)reactTag注冊(cè)View芽丹,也就是以Tag為Key,登記進(jìn)入_viewRegistry字典表卜朗,同時(shí)創(chuàng)建對(duì)應(yīng)的shadow view拔第。

- (void)registerRootView:(RCTRootContentView *)rootView
{
  NSNumber *reactTag = rootView.reactTag;
  UIView *existingView = _viewRegistry[reactTag];
  CGSize availableSize = rootView.availableSize;

  _viewRegistry[reactTag] = rootView;

  dispatch_async(RCTGetUIManagerQueue(), ^{
    if (!self->_viewRegistry) {
      return;
    }

    RCTRootShadowView *shadowView = [RCTRootShadowView new];
    shadowView.availableSize = availableSize;
    shadowView.reactTag = reactTag;
    shadowView.backgroundColor = rootView.backgroundColor;
    shadowView.viewName = NSStringFromClass([rootView class]);
    self->_shadowViewRegistry[shadowView.reactTag] = shadowView;
    [self->_rootViewTags addObject:reactTag];
  });

  [[NSNotificationCenter defaultCenter] postNotificationName:RCTUIManagerDidRegisterRootViewNotification
                                                      object:self
                                                    userInfo:@{RCTUIManagerRootViewKey: rootView}];
}

關(guān)于 shadow view,后面會(huì)有更詳細(xì)的介紹(如果有后面的話_)场钉,簡(jiǎn)而言之就是優(yōu)化布局用的蚊俺。

/**
 * ShadowView tree mirrors RCT view tree. Every node is highly stateful.
 * 1. A node is in one of three lifecycles: uninitialized, computed, dirtied.
 * 1. RCTBridge may call any of the padding/margin/width/height/top/left setters. A setter would dirty
 *    the node and all of its ancestors.
 * 2. At the end of each Bridge transaction, we call collectUpdatedFrames:widthConstraint:heightConstraint
 *    at the root node to recursively lay out the entire hierarchy.
 * 3. If a node is "computed" and the constraint passed from above is identical to the constraint used to
 *    perform the last computation, we skip laying out the subtree entirely.
 */

然后將RCTContentRootView添加到RCTRootView上面,然后執(zhí)行了一行JS代碼逛万,告訴JS你要開始繪制這個(gè)參數(shù)params的界面

- (void)runApplication:(RCTBridge *)bridge
{
  NSString *moduleName = _moduleName ?: @"";
  NSDictionary *appParameters = @{
    @"rootTag": _contentView.reactTag,
    @"initialProps": _appProperties ?: @{},
  };

  RCTLogInfo(@"Running application %@ (%@)", moduleName, appParameters);
  [bridge enqueueJSCall:@"AppRegistry"
                 method:@"runApplication"
                   args:@[moduleName, appParameters]
             completion:NULL];
}

再往后就是React.JS的工作了泳猬,React.JS會(huì)著手把JS中的頁(yè)面進(jìn)行計(jì)算,排版宇植,生成對(duì)應(yīng)的JS Component得封,準(zhǔn)備組織繪制界面了,包含著無(wú)數(shù)個(gè)JS Component的相互嵌套指郁。最終通過UIManager.js接口忙上,開始call oc去創(chuàng)建界面。

js 中如何處理 tag 的呢闲坎?
會(huì)跳過哪些 native 保留的 tag疫粥,也就是對(duì)10取余為1的那些 tag茬斧。然后自增。

/**
 * Keeps track of allocating and associating native "tags" which are numeric,
 * unique view IDs. All the native tags are negative numbers, to avoid
 * collisions, but in the JS we keep track of them as positive integers to store
 * them effectively in Arrays. So we must refer to them as "inverses" of the
 * native tags (that are * normally negative).
 *
 * It *must* be the case that every `rootNodeID` always maps to the exact same
 * `tag` forever. The easiest way to accomplish this is to never delete
 * anything from this table.
 * Why: Because `dangerouslyReplaceNodeWithMarkupByID` relies on being able to
 * unmount a component with a `rootNodeID`, then mount a new one in its place,
 */
var INITIAL_TAG_COUNT = 1;
var ReactNativeTagHandles = {
  tagsStartAt: INITIAL_TAG_COUNT,
  tagCount: INITIAL_TAG_COUNT,

  allocateTag: function(): number {
    // Skip over root IDs as those are reserved for native
    while (this.reactTagIsNativeTopRootID(ReactNativeTagHandles.tagCount)) {
      ReactNativeTagHandles.tagCount++;
    }
    var tag = ReactNativeTagHandles.tagCount;
    ReactNativeTagHandles.tagCount++;
    return tag;
  },

  assertRootTag: function(tag: number): void {
    invariant(
      this.reactTagIsNativeTopRootID(tag),
      'Expect a native root tag, instead got %s',
      tag,
    );
  },

  reactTagIsNativeTopRootID: function(reactTag: number): boolean {
    // We reserve all tags that are 1 mod 10 for native root views
    return reactTag % 10 === 1;
  },
};

實(shí)際跑一下梗逮,創(chuàng)建的第一個(gè) RootView

self->_shadowViewRegistry:
{
    17 = "<RCTShadowView: 0x121ed46e0; viewName: RCTView; reactTag: 17; frame: {{0, 0}, {0, 0}}>";
    15 = "<RCTShadowText: 0x123940ff0; viewName: RCTText; reactTag: 15; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}; text: Tap me to load the next scene>";
    13 = "<RCTShadowView: 0x123940690; viewName: RCTView; reactTag: 13; frame: {{77.666666666666671, 357.66666666666669}, {258.66666666666669, 37.666666666666664}}>";
    9 = "<RCTShadowText: 0x12393f670; viewName: RCTText; reactTag: 9; frame: {{0, 0}, {132.33333333333334, 17}}; text: Current Scene: haha>";
    7 = "<RCTShadowView: 0x12393f2b0; viewName: RCTView; reactTag: 7; frame: {{0, 0}, {414, 736}}>";
    5 = "<RCTShadowView: 0x12393cf60; viewName: RCTNavigator; reactTag: 5; frame: {{0, 0}, {414, 736}}>";
    3 = "<RCTShadowView: 0x121e17f30; viewName: RCTView; reactTag: 3; frame: {{0, 0}, {414, 736}}>";
    1 = "<RCTRootShadowView: 0x121ec5cf0; viewName: RCTRootContentView; reactTag: 1; frame: {{0, 0}, {414, 736}}>";
    16 = "<RCTShadowRawText: 0x121ed3ee0; viewName: RCTRawText; reactTag: 16; frame: {{0, 0}, {nan, nan}}; text: Tap me to load the next scene>";
    14 = "<RCTShadowView: 0x123940eb0; viewName: RCTView; reactTag: 14; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}>";
    12 = "<RCTShadowRawText: 0x12393fc00; viewName: RCTRawText; reactTag: 12; frame: {{0, 0}, {nan, nan}}; text: haha>";
    10 = "<RCTShadowRawText: 0x12393f8b0; viewName: RCTRawText; reactTag: 10; frame: {{0, 0}, {nan, nan}}; text: Current Scene: >";
    8 = "<RCTShadowView: 0x121ecf2d0; viewName: RCTView; reactTag: 8; frame: {{141, 340.66666666666669}, {132.33333333333334, 17}}>";
    6 = "<RCTShadowView: 0x121ec73b0; viewName: RCTNavItem; reactTag: 6; frame: {{0, 0}, {414, 736}}>";
    4 = "<RCTShadowView: 0x12393ce20; viewName: RCTView; reactTag: 4; frame: {{0, 0}, {414, 736}}>";
    2 = "<RCTShadowView: 0x12393cce0; viewName: RCTView; reactTag: 2; frame: {{0, 0}, {414, 736}}>";
}

創(chuàng)建了多個(gè) RootView 以后

{
    410 = "<RCTShadowRawText: 0x1014d0af0; viewName: RCTRawText; reactTag: 410; frame: {{0, 0}, {nan, nan}}; text: Tap me to load the next scene>";
    399 = "<RCTShadowView: 0x1016812f0; viewName: RCTNavigator; reactTag: 399; frame: {{0, 0}, {414, 736}}>";
    407 = "<RCTShadowView: 0x10169bc10; viewName: RCTView; reactTag: 407; frame: {{77.666666666666671, 357.66666666666669}, {258.66666666666669, 37.666666666666664}}>";
    396 = "<RCTShadowView: 0x101425af0; viewName: RCTView; reactTag: 396; frame: {{0, 0}, {414, 736}}>";
    221 = "<RCTRootShadowView: 0x101487710; viewName: RCTRootContentView; reactTag: 221; frame: {{0, 0}, {414, 736}}>";
    404 = "<RCTShadowText: 0x1016281d0; viewName: RCTText; reactTag: 404; frame: {{0, 0}, {132.33333333333334, 17}}; text: Current Scene: haha>";
    412 = "<RCTShadowView: 0x101405e40; viewName: RCTView; reactTag: 412; frame: {{0, 0}, {0, 0}}>";
    409 = "<RCTShadowText: 0x10148cf10; viewName: RCTText; reactTag: 409; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}; text: Tap me to load the next scene>";
    398 = "<RCTShadowView: 0x1014069b0; viewName: RCTView; reactTag: 398; frame: {{0, 0}, {414, 736}}>";
    406 = "<RCTShadowRawText: 0x10169c840; viewName: RCTRawText; reactTag: 406; frame: {{0, 0}, {nan, nan}}; text: haha>";
    403 = "<RCTShadowView: 0x101441830; viewName: RCTView; reactTag: 403; frame: {{141, 340.66666666666669}, {132.33333333333334, 17}}>";
    400 = "<RCTShadowView: 0x101677f00; viewName: RCTNavItem; reactTag: 400; frame: {{0, 0}, {414, 736}}>";
    408 = "<RCTShadowView: 0x1014c9b60; viewName: RCTView; reactTag: 408; frame: {{0, 0}, {258.66666666666669, 37.666666666666664}}>";
    397 = "<RCTShadowView: 0x10146c180; viewName: RCTView; reactTag: 397; frame: {{0, 0}, {414, 736}}>";
    405 = "<RCTShadowRawText: 0x1016820d0; viewName: RCTRawText; reactTag: 405; frame: {{0, 0}, {nan, nan}}; text: Current Scene: >";
    402 = "<RCTShadowView: 0x10e41c750; viewName: RCTView; reactTag: 402; frame: {{0, 0}, {414, 736}}>";
}

那么RCTUIManager都有哪些API提供給了JS呢项秉,大致如下:

createView
updateView
setChildren
removeRootView
manageChildren
findSubviewIn
measure
dispatchViewManagerCommand

createView的作用是創(chuàng)建一個(gè)個(gè)的UIView,RCTView库糠,各種nativeView伙狐,并且把傳過來(lái)的JS的屬性參數(shù),一一賦值給nativeView
updateView的作用是瞬欧,當(dāng)JSComponent的布局信息贷屎,界面樣子發(fā)生變化,JS來(lái)通知nativeView來(lái)更新對(duì)應(yīng)的屬性變化艘虎,樣子變化
setChildren的作用是唉侄,告訴OC,那個(gè)tag的View是另一個(gè)tag的view的子view野建,需要執(zhí)行addsubview属划,insertsubview等

如果要?jiǎng)?chuàng)建一個(gè) view,會(huì)經(jīng)過以下流程:
js傳來(lái)viewName候生,通過初始化的_componentDataByName表獲取RCTComponentData
dispatch_async(mainqueue)從JS通信線程拋到主線程創(chuàng)建UI
js傳來(lái)了ReactTag同眯,通過RCTComponentData的createViewWithTag方法創(chuàng)建界面
js傳來(lái)了屬性props,通過RCTComponentData的setProps:forView:方法進(jìn)行屬性賦值

  mountComponent: function mountComponent(transaction, hostParent, hostContainerInfo, context) {
    var tag = ReactNativeTagHandles.allocateTag();

    this._rootNodeID = tag;
    this._hostParent = hostParent;
    this._hostContainerInfo = hostContainerInfo;

    var updatePayload = ReactNativeAttributePayload.create(this._currentElement.props, this.viewConfig.validAttributes);

    var nativeTopRootTag = hostContainerInfo._tag;
    UIManager.createView(tag, this.viewConfig.uiViewClassName, nativeTopRootTag, updatePayload);

    ReactNativeComponentTree.precacheNode(this, tag);

    this.initializeChildren(this._currentElement.props.children, tag, transaction, context);
    return tag;
  }

這樣一來(lái)唯鸭,基本上就完成了React.JS創(chuàng)建一個(gè)純native界面的過程:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末须蜗,一起剝皮案震驚了整個(gè)濱河市狂鞋,隨后出現(xiàn)的幾起案子跌帐,更是在濱河造成了極大的恐慌,老刑警劉巖簸州,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缭付,死亡現(xiàn)場(chǎng)離奇詭異柿估,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)陷猫,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門秫舌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人绣檬,你說我怎么就攤上這事足陨。” “怎么了河咽?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵钠右,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我忘蟹,道長(zhǎng)飒房,這世上最難降的妖魔是什么搁凸? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮狠毯,結(jié)果婚禮上护糖,老公的妹妹穿的比我還像新娘。我一直安慰自己嚼松,他們只是感情好嫡良,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著献酗,像睡著了一般寝受。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罕偎,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天很澄,我揣著相機(jī)與錄音,去河邊找鬼颜及。 笑死甩苛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的俏站。 我是一名探鬼主播讯蒲,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼肄扎!你這毒婦竟也來(lái)了墨林?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤反浓,失蹤者是張志新(化名)和其女友劉穎萌丈,沒想到半個(gè)月后赞哗,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雷则,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年肪笋,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了月劈。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡藤乙,死狀恐怖猜揪,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情坛梁,我是刑警寧澤而姐,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布,位于F島的核電站划咐,受9級(jí)特大地震影響拴念,放射性物質(zhì)發(fā)生泄漏钧萍。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一政鼠、第九天 我趴在偏房一處隱蔽的房頂上張望风瘦。 院中可真熱鬧,春花似錦公般、人聲如沸万搔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)瞬雹。三九已至,卻和暖如春刽虹,著一層夾襖步出監(jiān)牢的瞬間挖炬,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工状婶, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留意敛,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓膛虫,卻偏偏與公主長(zhǎng)得像草姻,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稍刀,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容