diff --git a/React/Base/RCTBridge.h b/React/Base/RCTBridge.h index 36678d39629..87c1a616bd1 100644 --- a/React/Base/RCTBridge.h +++ b/React/Base/RCTBridge.h @@ -160,6 +160,10 @@ RCT_EXTERN void RCTEnableTurboModuleEagerInit(BOOL enabled); RCT_EXTERN BOOL RCTTurboModuleSharedMutexInitEnabled(void); RCT_EXTERN void RCTEnableTurboModuleSharedMutexInit(BOOL enabled); +// Turn on TurboModule shared mutex initialization +RCT_EXTERN BOOL RCTTurboModuleBlockGuardEnabled(void); +RCT_EXTERN void RCTEnableTurboModuleBlockGuard(BOOL enabled); + /** * Async batched bridge used to communicate with the JavaScript application. */ diff --git a/React/Base/RCTBridge.m b/React/Base/RCTBridge.m index cee883f875d..ee31c54c379 100644 --- a/React/Base/RCTBridge.m +++ b/React/Base/RCTBridge.m @@ -135,6 +135,17 @@ void RCTEnableTurboModuleSharedMutexInit(BOOL enabled) turboModuleSharedMutexInitEnabled = enabled; } +static BOOL turboModuleBlockGuardEnabled = NO; +BOOL RCTTurboModuleBlockGuardEnabled(void) +{ + return turboModuleBlockGuardEnabled; +} + +void RCTEnableTurboModuleBlockGuard(BOOL enabled) +{ + turboModuleBlockGuardEnabled = enabled; +} + @interface RCTBridge () @end diff --git a/ReactCommon/react/nativemodule/core/platform/ios/RCTBlockGuard.h b/ReactCommon/react/nativemodule/core/platform/ios/RCTBlockGuard.h new file mode 100644 index 00000000000..38dbd2bee4b --- /dev/null +++ b/ReactCommon/react/nativemodule/core/platform/ios/RCTBlockGuard.h @@ -0,0 +1,24 @@ +/* + * 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 + +NS_ASSUME_NONNULL_BEGIN + +/** + * RCTBlockGuard is designed to be used with obj-c blocks to assist with manual deallocation of C++ resources + * tied to lifetime of a block. If C++ resources needs to be manually released at the end of block or when the block + * is deallocated, place the clean up code inside constructor and make sure the instace of the class is references in + * the block. + */ +@interface RCTBlockGuard : NSObject + +- (instancetype)initWithCleanup:(void (^)(void))cleanup; + +@end + +NS_ASSUME_NONNULL_END diff --git a/ReactCommon/react/nativemodule/core/platform/ios/RCTBlockGuard.mm b/ReactCommon/react/nativemodule/core/platform/ios/RCTBlockGuard.mm new file mode 100644 index 00000000000..54a856a25a7 --- /dev/null +++ b/ReactCommon/react/nativemodule/core/platform/ios/RCTBlockGuard.mm @@ -0,0 +1,28 @@ +/* + * 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 "RCTBlockGuard.h" + +@implementation RCTBlockGuard { + void (^_cleanup)(void); +} + +- (instancetype)initWithCleanup:(void (^)(void))cleanup +{ + if (self = [super init]) { + _cleanup = cleanup; + } + + return self; +} + +- (void)dealloc +{ + _cleanup(); +} + +@end diff --git a/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModule.mm b/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModule.mm index de1bed23f8e..b730492b903 100644 --- a/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModule.mm +++ b/ReactCommon/react/nativemodule/core/platform/ios/RCTTurboModule.mm @@ -6,6 +6,7 @@ */ #import "RCTTurboModule.h" +#import "RCTBlockGuard.h" #import #import @@ -170,6 +171,16 @@ static RCTResponseSenderBlock convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::shared_ptr jsInvoker) { auto weakWrapper = CallbackWrapper::createWeak(value.getFunction(runtime), runtime, jsInvoker); + RCTBlockGuard *blockGuard; + if (RCTTurboModuleBlockGuardEnabled()) { + blockGuard = [[RCTBlockGuard alloc] initWithCleanup:^() { + auto strongWrapper = weakWrapper.lock(); + if (strongWrapper) { + strongWrapper->destroy(); + } + }]; + } + BOOL __block wrapperWasCalled = NO; RCTResponseSenderBlock callback = ^(NSArray *responses) { if (wrapperWasCalled) { @@ -181,7 +192,7 @@ convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, return; } - strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses]() { + strongWrapper->jsInvoker().invokeAsync([weakWrapper, responses, blockGuard]() { auto strongWrapper2 = weakWrapper.lock(); if (!strongWrapper2) { return; @@ -190,6 +201,9 @@ convertJSIFunctionToCallback(jsi::Runtime &runtime, const jsi::Function &value, std::vector args = convertNSArrayToStdVector(strongWrapper2->runtime(), responses); strongWrapper2->callback().call(strongWrapper2->runtime(), (const jsi::Value *)args.data(), args.size()); strongWrapper2->destroy(); + + // Delete the CallbackWrapper when the block gets dealloced without being invoked. + (void)blockGuard; }); wrapperWasCalled = YES;