關於RN的熱更新


寫點關於RN的熱更新和RN版本升級后的強制更新。以及優化白屏問題

在APPDelegate中加載RN,一般的加載方式是:
RCTRootView *rootView= [[RCTRootView alloc] initWithBundleURL:jsCodeLocation moduleName:@"authen_native" initialProperties:nil launchOptions:nil];

1
2
3
4
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions;

但在調試中發現兩個現象:
1.重復進入react-native頁面、退出react-native頁面的操作,RCTBridge對象會被重復創建、銷毀。有時候RCTBridge對象未能及時創建還會crash
2.在原生頁面和react-native頁面相互跳轉是RCTBridge也會被重復創建,造成很大的內存開銷

閱讀RCTRootView.h發現一些細節:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

* - Convenience initializer -
* A bridge will be created internally.
* This initializer is intended to be used when the app has a single RCTRootView,
* otherwise create an `RCTBridge` and pass it in via `initWithBridge:moduleName:`
* to all the instances.
*/
- (instancetype)initWithBundleURL:(NSURL *)bundleURL
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties
launchOptions:(NSDictionary *)launchOptions;

* - Designated initializer -
*/
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;

initWithBundleURL與initWithBridge的區別

對於項目中只有一個RCTRootView的時候建議initWithBundleURL的方法,這個方法內部創建了一個RCTBridge.

而有多個RCTRootView的情況,建議initWithBridge的方法.開發者直接創建RCTBridge,多個RCTRootView可共用一個RCTBridge。

項目中使用多個RCTRootView,推薦使用以下方法initWithBridge初始化:

1
2
3
4
5
6
 _bridge = [[RCTBridge alloc] initWithBundleURL:[SDRrectFileOption SetFileWithOption:self.luanchOption]
moduleProvider:nil
launchOptions:self.luanchOption];
RCTRootView = [[RCTRootView alloc] initWithBridge:_bridge
moduleName:@"authen_native"
initialProperties:nil];

在SDRrectFileOption中返回的是jsbundle的地址。在這個文件中可以使用NSFileManager來把jsbundle緩存到本地。但是如果是新版本的RN比如0.57要替換老版本的比如0.54的APP覆蓋更新的話,記得要對比版本號,然后把緩存里面的jsbundle清除掉再返回新的jsbundle地址。不然會導致crash。

RN的熱更新

在APPdelegate的didFinishLaunchingWithOptions方法中來判斷是否需要Update。在Update方法中如果需要強制更新的話就就把RCTBridge調用reload方法進行熱更新—和初始化使用的是同一個bridge。

1
2
3
4
5
6
7
8
- (void)checkUpdate {
patchClass *patch = [patchClass sharedInstance];
[patch checkUpdate];
patch.IS_COERCIVE = ^(NSURL *newPath) {
//是強制更新的話,就把RCTBridge調用reload方法進行熱更新
[_bridge reload];
};
}

在patchClass中使用的是單例,在這個里面通過接口判斷是否需要熱更還是強制更新,是只更新jsbundle還是整包更新,下載文件,把下載的壓縮文件解壓縮,如果緩存里面有文件先刪除舊的jsbundle再保存,

大專欄  關於RN的熱更新ink" title="解決白屏問題">解決白屏問題

使用單例初始化一個bridge對象解決上述問題:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//.h
@interface : RCTBridge

+ (BridgeManager*)shareInstance;
@end

@interface BridgeHandle : NSObject<RCTBridgeDelegate>

@end

//.m
implementation MallBridgeHandle

- (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
{
return [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"jsbundle"];
}
@end

@implementation
static BridgeManager * manager = nil;
static dispatch_once_t onceToken;
+ (BridgeManager*)shareInstance
{
dispatch_once(&onceToken,^{
manager = [BridgeManager alloc] initWithDelegate:[[BridgeHandle alloc] init] launchOptions:nil];
});
return manager ;
}
@end

單例在程序啟動時初始化。
測試驗證可以發現:內存得到優化,白屏問題得到解決。

橋接原生模塊

首先我們需要創建一個類,然后導入頭文件 #import <RCTBridgeModule.h> ,這個類需要實現 RCTBridgeModule 協議。

1
2
3
4
5
6
#import <Foundation/Foundation.h>
#import <RCTBridgeModule.h>

@interface RNTestManager : NSObject <RCTBridgeModule>

@end

模塊名字

在類的實現部分,需要包含 RCT_EXPORT_MODULE() 宏,這個宏也可以添加一個參數用來指定在 JS 中訪問這個模塊的名字。如果你不指定,默認就會使用這個 OC 類的名字。

導出方法

RCT_EXPORT_METHOD(),導出到 JS 的方法名是 OC 的方法名的第一個部分,橋接到 JS 的方法返回值類型必須是 void。RN 的橋接操作是異步的,所以如果要返回結果給 JS,你必須通過回調或者觸發事件來進行。傳入的參數類型有以下幾種:

string (NSString)
number (NSInteger, float, double, CGFloat, NSNumber)
boolean (BOOL, NSNumber)
array (NSArray) 包含本列表中任意類型
object (NSDictionary) 包含string類型的鍵和本列表中任意類型的值
function (RCTResponseSenderBlock)

回調函數
RCT_EXPORT_METHOD(RNInvokeOCPromise:(NSDictionary *)dictionary resolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject){
}
橋接原生方法的最后兩個參數是RCTPromiseResolveBlock 和RCTPromiseRejectBlock的話,則對應的JS方法就會返回一個Promise對象。

設置原生模塊執行操作的線程
如果你在原生模塊中需要更改 UI 或者必須在主線程的話,可以實現

- (dispatch_queue_t)methodQueue 方法

- (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); }

RN使用原生的View

import入UIView+React.h文件,,原生視圖都需要被一個RCTViewManager的子類來創建和管理。這些管理器在功能上有些類似“視圖控制器”,但它們本質上都是單例 —— React Native只會為每個管理器創建一個實例。步驟:

  1. 創建一個子類
  2. 添加 RCT_EXPORT_MODULE()標記宏
  3. 實現 -(UIView *)view 方法

創建一個子類

該組件有回調需要處理,這里即必須用到 RCTDirectEventBlock 或者 RCTBubblingEventBlock,而且命名的時候要特別注意,需要已 on 開頭,熟悉 JS 的朋友應該會反應過來,這很像 JS 的事件命名規范。

@property (nonatomic, copy) RCTBubblingEventBlock onValueChange;
@property (nonatomic, copy) RCTBubblingEventBlock onSlidingComplete;

在自定義ViewManager中

  1. 初始化子View
  2. 添加 RCT_EXPORT_MODULE()標記宏
    添加TYRCBarChartViewManager 來管理TYRCBarChartView。這個TYRCBarChartViewManager : 繼承自RCTViewManager。 RCTViewManager 實現 RCTBridgeModule 協議。
  3. 自定義屬性RCT_CUSTOM_VIEW_PROPERTY
  4. 自定義方法RCT_EXPORT_METHOD(refresh){
    [_barView refreshData];
    }
    RCT_CUSTOM_VIEW_PROPERTY(name, type, viewClass),完整的屬性定義為

給JS發送事件使用 eventDispatcher

[self.rootView.bridge.eventDispatcher sendAppEventWithName:@”deviceLocalStateChange”
body:@{@”state”:state}];

參考資料

ios2.1大禮包被拒經驗分享https://zhuanlan.zhihu.com/p/54042709


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM