Unify sampling profiler and reload logic by leveraging RCTPackagerConnection

Differential Revision: D5951952

fbshipit-source-id: ceea04ebbddb02944717a266a14523be052ab213
This commit is contained in:
Alex Dvornikov 2017-10-04 19:06:22 -07:00 committed by Facebook Github Bot
parent 9e01d72103
commit afec987e10
10 changed files with 149 additions and 50 deletions

View File

@ -56,6 +56,14 @@
#endif
#endif
#ifndef ENABLE_PACKAGER_CONNECTION
#if RCT_DEV && __has_include(<React/RCTPackagerConnection.h>)
#define ENABLE_PACKAGER_CONNECTION 1
#else
#define ENABLE_PACKAGER_CONNECTION 0
#endif
#endif
#if RCT_DEV
#define RCT_IF_DEV(...) __VA_ARGS__
#else

View File

@ -16,11 +16,16 @@
extern const int RCT_PACKAGER_CLIENT_PROTOCOL_VERSION;
@protocol RCTPackagerClientMethod
@protocol RCTPackagerClientMethod <NSObject>
- (void)handleRequest:(id)params withResponder:(RCTPackagerClientResponder *)responder;
- (void)handleNotification:(id)params;
@optional
/** By default object will receive its methods on the main queue, unless this method is overriden. */
- (dispatch_queue_t)methodQueue;
@end
@interface RCTPackagerClientResponder : NSObject

View File

@ -13,19 +13,31 @@
#if RCT_DEV
NS_ASSUME_NONNULL_BEGIN
@class RCTBridge;
@protocol RCTPackagerClientMethod;
@protocol RCTPackagerConnectionConfig;
/**
* Encapsulates connection to React Native packager
* Encapsulates connection to React Native packager.
* Dispatches messages from websocket to message handlers that must implement
* <RCTPackagerClientMethod> protocol.
* Message dispatch is performed on the main queue, unless message handler
* provides its own queue by overriding "methodQueue" method.
*/
@interface RCTPackagerConnection : NSObject
+ (void)checkDefaultConnectionWithCallback:(void (^)(BOOL isRunning))callback
queue:(dispatch_queue_t)queue;
+ (instancetype)connectionForBridge:(RCTBridge *)bridge;
- (instancetype)initWithConfig:(id<RCTPackagerConnectionConfig>)config;
- (void)addHandler:(id<RCTPackagerClientMethod>)handler forMethod:(NSString *)name;
- (void)stop;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -13,6 +13,7 @@
#import <React/RCTAssert.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
#import <React/RCTLog.h>
@ -26,6 +27,16 @@
#if RCT_DEV
static dispatch_queue_t RCTPackagerConnectionQueue()
{
static dispatch_queue_t queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = dispatch_queue_create("com.facebook.RCTPackagerConnectionQueue", DISPATCH_QUEUE_SERIAL);
});
return queue;
};
@interface RCTPackagerConnection () <RCTWebSocketProtocolDelegate>
@end
@ -35,6 +46,29 @@
NSMutableDictionary<NSString *, id<RCTPackagerClientMethod>> *_handlers;
}
+ (void)checkDefaultConnectionWithCallback:(void (^)(BOOL isRunning))callback
queue:(dispatch_queue_t)queue
{
RCTBundleURLProvider *const settings = [RCTBundleURLProvider sharedSettings];
NSURLComponents *components = [NSURLComponents new];
components.scheme = @"http";
components.host = settings.jsLocation ?: @"localhost";
components.port = @(kRCTBundleURLProviderDefaultPort);
components.path = @"/status";
[NSURLConnection sendAsynchronousRequest:[NSURLRequest requestWithURL:components.URL]
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
NSString *const status = data != nil
? [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]
: nil;
BOOL isRunning = [status isEqualToString:@"packager-status:running"];
dispatch_async(queue, ^{
callback(isRunning);
});
}];
}
+ (instancetype)connectionForBridge:(RCTBridge *)bridge
{
RCTPackagerConnectionBridgeConfig *config = [[RCTPackagerConnectionBridgeConfig alloc] initWithBridge:bridge];
@ -46,7 +80,6 @@
if (self = [super init]) {
_packagerURL = [config packagerURL];
_handlers = [[config defaultPackagerMethods] mutableCopy];
[self connect];
}
return self;
@ -70,19 +103,35 @@
}
NSString *key = [url absoluteString];
RCTReconnectingWebSocket *webSocket = socketConnections[key];
if (!webSocket) {
webSocket = [[RCTReconnectingWebSocket alloc] initWithURL:url];
[webSocket start];
socketConnections[key] = webSocket;
_socket = socketConnections[key];
if (!_socket) {
_socket = [[RCTReconnectingWebSocket alloc] initWithURL:url];
_socket.delegateDispatchQueue = RCTPackagerConnectionQueue();
[_socket start];
socketConnections[key] = _socket;
}
webSocket.delegate = self;
_socket.delegate = self;
}
- (void)stop
{
[_socket stop];
}
- (void)addHandler:(id<RCTPackagerClientMethod>)handler forMethod:(NSString *)name
{
_handlers[name] = handler;
@synchronized(self) {
_handlers[name] = handler;
}
}
- (id<RCTPackagerClientMethod>)handlerForMethod:(NSString *)name
{
@synchronized(self) {
return _handlers[name];
}
}
static BOOL isSupportedVersion(NSNumber *version)
@ -95,10 +144,6 @@ static BOOL isSupportedVersion(NSNumber *version)
- (void)webSocket:(RCTSRWebSocket *)webSocket didReceiveMessage:(id)message
{
if (!_handlers) {
return;
}
NSError *error = nil;
NSDictionary<NSString *, id> *msg = RCTJSONParse(message, &error);
@ -112,7 +157,7 @@ static BOOL isSupportedVersion(NSNumber *version)
return;
}
id<RCTPackagerClientMethod> methodHandler = _handlers[msg[@"method"]];
id<RCTPackagerClientMethod> methodHandler = [self handlerForMethod:msg[@"method"]];
if (!methodHandler) {
if (msg[@"id"]) {
NSString *errorMsg = [NSString stringWithFormat:@"%@ no handler found for method %@", [self class], msg[@"method"]];
@ -123,13 +168,19 @@ static BOOL isSupportedVersion(NSNumber *version)
return; // If it was a broadcast then we ignore it gracefully
}
if (msg[@"id"]) {
[methodHandler handleRequest:msg[@"params"]
withResponder:[[RCTPackagerClientResponder alloc] initWithId:msg[@"id"]
socket:webSocket]];
} else {
[methodHandler handleNotification:msg[@"params"]];
}
dispatch_queue_t methodQueue = [methodHandler respondsToSelector:@selector(methodQueue)]
? [methodHandler methodQueue]
: dispatch_get_main_queue();
dispatch_async(methodQueue, ^{
if (msg[@"id"]) {
[methodHandler handleRequest:msg[@"params"]
withResponder:[[RCTPackagerClientResponder alloc] initWithId:msg[@"id"]
socket:webSocket]];
} else {
[methodHandler handleNotification:msg[@"params"]];
}
});
}
- (void)webSocketDidOpen:(RCTSRWebSocket *)webSocket

View File

@ -11,6 +11,8 @@
#if RCT_DEV // Only supported in dev mode
NS_ASSUME_NONNULL_BEGIN
@class RCTBridge;
@interface RCTPackagerConnectionBridgeConfig : NSObject <RCTPackagerConnectionConfig>
@ -19,4 +21,6 @@
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -9,7 +9,10 @@
#import "RCTPackagerConnectionBridgeConfig.h"
#import <objc/runtime.h>
#import <React/RCTBridge.h>
#import <React/RCTBundleURLProvider.h>
#import "RCTJSEnvironment.h"
#import "RCTReloadPackagerMethod.h"
@ -18,38 +21,44 @@
#if RCT_DEV // Only supported in dev mode
@implementation RCTPackagerConnectionBridgeConfig {
RCTBridge *_bridge;
id<RCTJSEnvironment> _jsEnvironment;
RCTReloadPackagerMethodBlock _reloadCommand;
NSURL *_sourceURL;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
{
if (self = [super init]) {
_bridge = bridge;
_jsEnvironment = bridge;
_sourceURL = [bridge.bundleURL copy];
__weak RCTBridge *weakBridge = bridge;
_reloadCommand = ^(id params) {
if (params != (id)kCFNull && [params[@"debug"] boolValue]) {
weakBridge.executorClass = objc_lookUpClass("RCTWebSocketExecutor");
}
[weakBridge reload];
};
}
return self;
}
- (NSURL *)packagerURL
{
NSString *host = [_bridge.bundleURL host];
NSString *scheme = [_bridge.bundleURL scheme];
if (!host) {
host = @"localhost";
scheme = @"http";
}
NSNumber *port = [_bridge.bundleURL port];
if (!port) {
port = @8081; // Packager default port
}
return [NSURL URLWithString:[NSString stringWithFormat:@"%@://%@:%@/message?role=ios-rn-rctdevmenu", scheme, host, port]];
NSURLComponents *components = [NSURLComponents new];
NSString *host = [_sourceURL host];
components.host = host ?: @"localhost";
components.scheme = host ? [_sourceURL scheme] : @"http";
components.port = [_sourceURL port] ?: @(kRCTBundleURLProviderDefaultPort);
components.path = @"/message";
components.queryItems = @[[NSURLQueryItem queryItemWithName:@"role" value:@"ios-rn-rctdevmenu"]];
return components.URL;
}
- (NSDictionary<NSString *, id<RCTPackagerClientMethod>> *)defaultPackagerMethods
{
return @{
@"reload": [[RCTReloadPackagerMethod alloc] initWithBridge:_bridge],
@"pokeSamplingProfiler": [[RCTSamplingProfilerPackagerMethod alloc] initWithJSEnvironment:_bridge]
@"reload": [[RCTReloadPackagerMethod alloc] initWithReloadCommand:_reloadCommand callbackQueue:dispatch_get_main_queue()],
@"pokeSamplingProfiler": [[RCTSamplingProfilerPackagerMethod alloc] initWithJSEnvironment:_jsEnvironment]
};
}

View File

@ -11,6 +11,8 @@
#if RCT_DEV // Only supported in dev mode
NS_ASSUME_NONNULL_BEGIN
@protocol RCTPackagerClientMethod;
@protocol RCTPackagerConnectionConfig
@ -20,4 +22,6 @@
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -13,10 +13,16 @@
#if RCT_DEV // Only supported in dev mode
NS_ASSUME_NONNULL_BEGIN
typedef void (^RCTReloadPackagerMethodBlock)(id);
@interface RCTReloadPackagerMethod : NSObject <RCTPackagerClientMethod>
- (instancetype)initWithBridge:(RCTBridge *)bridge;
- (instancetype)initWithReloadCommand:(RCTReloadPackagerMethodBlock)block callbackQueue:(dispatch_queue_t)callbackQueue;
@end
NS_ASSUME_NONNULL_END
#endif

View File

@ -9,20 +9,20 @@
#import "RCTReloadPackagerMethod.h"
#import <objc/runtime.h>
#import "RCTBridge.h"
#if RCT_DEV // Only supported in dev mode
@implementation RCTReloadPackagerMethod {
__weak RCTBridge *_bridge;
RCTReloadPackagerMethodBlock _block;
dispatch_queue_t _callbackQueue;
}
- (instancetype)initWithBridge:(RCTBridge *)bridge
- (instancetype)initWithReloadCommand:(RCTReloadPackagerMethodBlock)block callbackQueue:(dispatch_queue_t)callbackQueue
{
if (self = [super init]) {
_bridge = bridge;
_block = [block copy];
_callbackQueue = callbackQueue;
}
return self;
}
@ -34,10 +34,12 @@
- (void)handleNotification:(id)params
{
if (![params isEqual:[NSNull null]] && [params[@"debug"] boolValue]) {
_bridge.executorClass = objc_lookUpClass("RCTWebSocketExecutor");
}
[_bridge reload];
_block(params);
}
- (dispatch_queue_t)methodQueue
{
return _callbackQueue;
}
@end

View File

@ -36,8 +36,6 @@ static NSString *const kRCTDevSettingStartSamplingProfilerOnLaunch = @"startSamp
static NSString *const kRCTDevSettingsUserDefaultsKey = @"RCTDevMenu";
#define ENABLE_PACKAGER_CONNECTION RCT_DEV && __has_include("RCTPackagerConnection.h")
#if ENABLE_PACKAGER_CONNECTION
#import "RCTPackagerConnection.h"
#endif