mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
8131b7bb7b
Summary: This is my proposal for fixing `use_frameworks!` compatibility without breaking all `<React/*>` imports I outlined in https://github.com/facebook/react-native/pull/25393#issuecomment-508457700. If accepted, it will fix https://github.com/facebook/react-native/issues/25349. It builds on the changes I made in https://github.com/facebook/react-native/pull/25496 by ensuring each podspec has a unique value for `header_dir` so that framework imports do not conflict. Every podspec which should be included in the `<React/*>` namespace now includes it's headers from `React-Core.podspec`. The following pods can still be imported with `<React/*>` and so should not have breaking changes: `React-ART`,`React-DevSupport`, `React-CoreModules`, `React-RCTActionSheet`, `React-RCTAnimation`, `React-RCTBlob`, `React-RCTImage`, `React-RCTLinking`, `React-RCTNetwork`, `React-RCTPushNotification`, `React-RCTSettings`, `React-RCTText`, `React-RCTSettings`, `React-RCTVibration`, `React-RCTWebSocket` . There are still a few breaking changes which I hope will be acceptable: - `React-Core.podspec` has been moved to the root of the project. Any `Podfile` that references it will need to update the path. - ~~`React-turbomodule-core`'s headers now live under `<turbomodule/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823. - ~~`React-turbomodulesamples`'s headers now live under `<turbomodulesamples/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823. - ~~`React-TypeSaferty`'s headers now live under `<TypeSafety/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511040967. - ~~`React-jscallinvoker`'s headers now live under `<jscallinvoker/*>`~~ Replaced by https://github.com/facebook/react-native/pull/25619#issuecomment-511091823. - Each podspec now uses `s.static_framework = true`. This means that a minimum of CocoaPods 1.5 ([released in April 2018](http://blog.cocoapods.org/CocoaPods-1.5.0/)) is now required. This is needed so that the ` __has_include` conditions can still work when frameworks are enabled. Still to do: - ~~Including `React-turbomodule-core` with `use_frameworks!` enabled causes the C++ import failures we saw in https://github.com/facebook/react-native/issues/25349. I'm sure it will be possible to fix this but I need to dig deeper (perhaps a custom modulemap would be needed).~~ Addressed by https://github.com/facebook/react-native/pull/25619/commits/33573511f02f3502a28bad48e085e9a4b8608302. - I haven't got Fabric working yet. I wonder if it would be acceptable to move Fabric out of the `<React/*>` namespace since it is new? � ## Changelog [iOS] [Fixed] - Fixed compatibility with CocoaPods frameworks. Pull Request resolved: https://github.com/facebook/react-native/pull/25619 Test Plan: ### FB ``` buck build catalyst ``` ### Sample Project Everything should work exactly as before, where `use_frameworks!` is not in `Podfile`s. I have a branch on my [sample project](https://github.com/jtreanor/react-native-cocoapods-frameworks) here which has `use_frameworks!` in its `Podfile` to demonstrate this is fixed. You can see that it works with these steps: 1. `git clone git@github.com:jtreanor/react-native-cocoapods-frameworks.git` 2. `git checkout fix-frameworks-subspecs` 3. `cd ios && pod install` 4. `cd .. && react-native run-ios` The sample app will build and run successfully. To see that it still works without frameworks, remove `use_frameworks!` from the `Podfile` and do steps 3 and 4 again. ### RNTesterPods `RNTesterPodsPods` can now work with or without `use_frameworks!`. 1. Go to the `RNTester` directory and run `pod install`. 2. Run the tests in `RNTesterPods.xcworkspace` to see that everything still works fine. 3. Uncomment the `use_frameworks!` line at the top of `RNTester/Podfile` and run `pod install` again. 4. Run the tests again and see that it still works with frameworks enabled. Reviewed By: PeteTheHeat Differential Revision: D16465247 Pulled By: PeteTheHeat fbshipit-source-id: cad837e9cced06d30cc5b372af1c65c7780b9e7a
286 lines
9.3 KiB
Plaintext
286 lines
9.3 KiB
Plaintext
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTPackagerConnection.h>
|
|
|
|
#import <algorithm>
|
|
#import <objc/runtime.h>
|
|
#import <vector>
|
|
|
|
#import <React/RCTAssert.h>
|
|
#import <React/RCTBridge.h>
|
|
#import <React/RCTBundleURLProvider.h>
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTDefines.h>
|
|
#import <React/RCTLog.h>
|
|
#import <React/RCTPackagerClient.h>
|
|
#import <React/RCTReconnectingWebSocket.h>
|
|
#import <React/RCTUtils.h>
|
|
|
|
#if RCT_DEV && !TARGET_OS_UIKITFORMAC
|
|
|
|
#import <React/RCTSRWebSocket.h>
|
|
|
|
@interface RCTPackagerConnection () <RCTReconnectingWebSocketDelegate>
|
|
@end
|
|
|
|
template <typename Handler>
|
|
struct Registration {
|
|
NSString *method;
|
|
Handler handler;
|
|
dispatch_queue_t queue;
|
|
uint32_t token;
|
|
};
|
|
|
|
@implementation RCTPackagerConnection {
|
|
std::mutex _mutex; // protects all ivars
|
|
RCTReconnectingWebSocket *_socket;
|
|
BOOL _socketConnected;
|
|
NSString *_jsLocationForSocket;
|
|
id _bundleURLChangeObserver;
|
|
uint32_t _nextToken;
|
|
std::vector<Registration<RCTNotificationHandler>> _notificationRegistrations;
|
|
std::vector<Registration<RCTRequestHandler>> _requestRegistrations;
|
|
std::vector<Registration<RCTConnectedHandler>> _connectedRegistrations;
|
|
}
|
|
|
|
+ (instancetype)sharedPackagerConnection
|
|
{
|
|
static RCTPackagerConnection *connection;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
connection = [RCTPackagerConnection new];
|
|
});
|
|
return connection;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
_nextToken = 1; // Prevent randomly erasing a handler if you pass a bogus 0 token
|
|
_jsLocationForSocket = [RCTBundleURLProvider sharedSettings].jsLocation;
|
|
_socket = socketForLocation(_jsLocationForSocket);
|
|
_socket.delegate = self;
|
|
[_socket start];
|
|
|
|
RCTPackagerConnection *const __weak weakSelf = self;
|
|
_bundleURLChangeObserver =
|
|
[[NSNotificationCenter defaultCenter]
|
|
addObserverForName:RCTBundleURLProviderUpdatedNotification
|
|
object:nil
|
|
queue:[NSOperationQueue mainQueue]
|
|
usingBlock:^(NSNotification *_Nonnull __unused note) {
|
|
[weakSelf bundleURLSettingsChanged];
|
|
}];
|
|
}
|
|
return self;
|
|
}
|
|
|
|
static RCTReconnectingWebSocket *socketForLocation(NSString *const jsLocation)
|
|
{
|
|
NSURLComponents *const components = [NSURLComponents new];
|
|
components.host = jsLocation ?: @"localhost";
|
|
components.scheme = @"http";
|
|
components.port = @(kRCTBundleURLProviderDefaultPort);
|
|
components.path = @"/message";
|
|
components.queryItems = @[[NSURLQueryItem queryItemWithName:@"role" value:@"ios"]];
|
|
static dispatch_queue_t queue;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
queue = dispatch_queue_create("com.facebook.RCTPackagerConnectionQueue", DISPATCH_QUEUE_SERIAL);
|
|
});
|
|
return [[RCTReconnectingWebSocket alloc] initWithURL:components.URL queue:queue];
|
|
}
|
|
|
|
- (void)stop
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
if (_socket == nil) {
|
|
// Already stopped
|
|
return;
|
|
}
|
|
[[NSNotificationCenter defaultCenter] removeObserver:_bundleURLChangeObserver];
|
|
_bundleURLChangeObserver = nil;
|
|
_socketConnected = NO;
|
|
[_socket stop];
|
|
_socket = nil;
|
|
_notificationRegistrations.clear();
|
|
_requestRegistrations.clear();
|
|
}
|
|
|
|
- (void)bundleURLSettingsChanged
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
if (_socket == nil) {
|
|
return; // already stopped
|
|
}
|
|
|
|
NSString *const jsLocation = [RCTBundleURLProvider sharedSettings].jsLocation;
|
|
if ([jsLocation isEqual:_jsLocationForSocket]) {
|
|
return; // unchanged
|
|
}
|
|
|
|
_socket.delegate = nil;
|
|
[_socket stop];
|
|
_jsLocationForSocket = jsLocation;
|
|
_socket = socketForLocation(jsLocation);
|
|
_socket.delegate = self;
|
|
[_socket start];
|
|
}
|
|
|
|
- (RCTHandlerToken)addNotificationHandler:(RCTNotificationHandler)handler queue:(dispatch_queue_t)queue forMethod:(NSString *)method
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
const auto token = _nextToken++;
|
|
_notificationRegistrations.push_back({method, handler, queue, token});
|
|
return token;
|
|
}
|
|
|
|
- (RCTHandlerToken)addRequestHandler:(RCTRequestHandler)handler queue:(dispatch_queue_t)queue forMethod:(NSString *)method
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
const auto token = _nextToken++;
|
|
_requestRegistrations.push_back({method, handler, queue, token});
|
|
return token;
|
|
}
|
|
|
|
- (RCTHandlerToken)addConnectedHandler:(RCTConnectedHandler)handler queue:(dispatch_queue_t)queue
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
if (_socketConnected) {
|
|
dispatch_async(queue, ^{
|
|
handler();
|
|
});
|
|
return 0; // _nextToken starts at 1, so 0 is a no-op token
|
|
} else {
|
|
const auto token = _nextToken++;
|
|
_connectedRegistrations.push_back({nil, handler, queue, token});
|
|
return token;
|
|
}
|
|
}
|
|
|
|
- (void)removeHandler:(RCTHandlerToken)token
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
eraseRegistrationsWithToken(_notificationRegistrations, token);
|
|
eraseRegistrationsWithToken(_requestRegistrations, token);
|
|
eraseRegistrationsWithToken(_connectedRegistrations, token);
|
|
}
|
|
|
|
template <typename Handler>
|
|
static void eraseRegistrationsWithToken(std::vector<Registration<Handler>> ®istrations, RCTHandlerToken token)
|
|
{
|
|
registrations.erase(std::remove_if(registrations.begin(), registrations.end(),
|
|
[&token](const auto ®) { return reg.token == token; }),
|
|
registrations.end());
|
|
}
|
|
|
|
- (void)addHandler:(id<RCTPackagerClientMethod>)handler forMethod:(NSString *)method
|
|
{
|
|
dispatch_queue_t queue = [handler respondsToSelector:@selector(methodQueue)]
|
|
? [handler methodQueue] : dispatch_get_main_queue();
|
|
|
|
[self addNotificationHandler:^(NSDictionary<NSString *, id> *notification) {
|
|
[handler handleNotification:notification];
|
|
} queue:queue forMethod:method];
|
|
[self addRequestHandler:^(NSDictionary<NSString *, id> *request, RCTPackagerClientResponder *responder) {
|
|
[handler handleRequest:request withResponder:responder];
|
|
} queue:queue forMethod:method];
|
|
}
|
|
|
|
static BOOL isSupportedVersion(NSNumber *version)
|
|
{
|
|
NSArray<NSNumber *> *const kSupportedVersions = @[ @(RCT_PACKAGER_CLIENT_PROTOCOL_VERSION) ];
|
|
return [kSupportedVersions containsObject:version];
|
|
}
|
|
|
|
#pragma mark - RCTReconnectingWebSocketDelegate
|
|
|
|
- (void)reconnectingWebSocketDidOpen:(__unused RCTReconnectingWebSocket *)webSocket
|
|
{
|
|
std::vector<Registration<RCTConnectedHandler>> registrations;
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
_socketConnected = YES;
|
|
registrations = _connectedRegistrations;
|
|
_connectedRegistrations.clear();
|
|
}
|
|
for (const auto ®istration : registrations) {
|
|
// Beware: don't capture the reference to handler in a dispatched block!
|
|
RCTConnectedHandler handler = registration.handler;
|
|
dispatch_async(registration.queue, ^{ handler(); });
|
|
}
|
|
}
|
|
|
|
- (void)reconnectingWebSocket:(RCTReconnectingWebSocket *)webSocket didReceiveMessage:(id)message
|
|
{
|
|
NSError *error = nil;
|
|
NSDictionary<NSString *, id> *msg = RCTJSONParse(message, &error);
|
|
|
|
if (error) {
|
|
RCTLogError(@"%@ failed to parse message with error %@\n<message>\n%@\n</message>", [self class], error, msg);
|
|
return;
|
|
}
|
|
|
|
if (!isSupportedVersion(msg[@"version"])) {
|
|
RCTLogError(@"%@ received message with not supported version %@", [self class], msg[@"version"]);
|
|
return;
|
|
}
|
|
|
|
NSString *const method = msg[@"method"];
|
|
NSDictionary<NSString *, id> *const params = msg[@"params"];
|
|
id messageId = msg[@"id"];
|
|
|
|
if (messageId) { // Request
|
|
const std::vector<Registration<RCTRequestHandler>> registrations(registrationsWithMethod(_mutex, _requestRegistrations, method));
|
|
if (registrations.empty()) {
|
|
RCTLogError(@"No handler found for packager method %@", msg[@"method"]);
|
|
[[[RCTPackagerClientResponder alloc] initWithId:messageId
|
|
socket:webSocket]
|
|
respondWithError:
|
|
[NSString stringWithFormat:@"No handler found for packager method %@", msg[@"method"]]];
|
|
} else {
|
|
// If there are multiple matching request registrations, only one can win;
|
|
// otherwise the packager would get multiple responses. Choose the last one.
|
|
RCTRequestHandler handler = registrations.back().handler;
|
|
dispatch_async(registrations.back().queue, ^{
|
|
handler(params, [[RCTPackagerClientResponder alloc] initWithId:messageId socket:webSocket]);
|
|
});
|
|
}
|
|
} else { // Notification
|
|
const std::vector<Registration<RCTNotificationHandler>> registrations(registrationsWithMethod(_mutex, _notificationRegistrations, method));
|
|
for (const auto ®istration : registrations) {
|
|
// Beware: don't capture the reference to handler in a dispatched block!
|
|
RCTNotificationHandler handler = registration.handler;
|
|
dispatch_async(registration.queue, ^{ handler(params); });
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)reconnectingWebSocketDidClose:(__unused RCTReconnectingWebSocket *)webSocket
|
|
{
|
|
std::lock_guard<std::mutex> l(_mutex);
|
|
_socketConnected = NO;
|
|
}
|
|
|
|
template <typename Handler>
|
|
static std::vector<Registration<Handler>> registrationsWithMethod(std::mutex &mutex, const std::vector<Registration<Handler>> ®istrations, NSString *method)
|
|
{
|
|
std::lock_guard<std::mutex> l(mutex); // Scope lock acquisition to prevent deadlock when calling out
|
|
std::vector<Registration<Handler>> matches;
|
|
for (const auto ® : registrations) {
|
|
if ([reg.method isEqual:method]) {
|
|
matches.push_back(reg);
|
|
}
|
|
}
|
|
return matches;
|
|
}
|
|
|
|
@end
|
|
|
|
#endif
|