diff --git a/packages/react-native/Libraries/Blob/RCTBlobCollector.mm b/packages/react-native/Libraries/Blob/RCTBlobCollector.mm index c9afc78057c9..9b2ca8cb3a09 100644 --- a/packages/react-native/Libraries/Blob/RCTBlobCollector.mm +++ b/packages/react-native/Libraries/Blob/RCTBlobCollector.mm @@ -21,7 +21,7 @@ { RCTBlobManager *blobManager = blobManager_; NSString *blobId = [NSString stringWithUTF8String:blobId_.c_str()]; - dispatch_async([blobManager_ methodQueue], ^{ + dispatch_async([blobManager_ executionQueue], ^{ [blobManager remove:blobId]; }); } diff --git a/packages/react-native/Libraries/Blob/RCTBlobManager.h b/packages/react-native/Libraries/Blob/RCTBlobManager.h index 317b202911df..bceb8eb5d81f 100755 --- a/packages/react-native/Libraries/Blob/RCTBlobManager.h +++ b/packages/react-native/Libraries/Blob/RCTBlobManager.h @@ -10,6 +10,8 @@ #import #import +RCT_EXTERN void RCTEnableBlobManagerProcessingQueue(BOOL enabled); + @interface RCTBlobManager : NSObject - (NSString *)store:(NSData *)data; @@ -26,4 +28,6 @@ - (void)createFromParts:(NSArray *> *)parts withId:(NSString *)blobId; +- (dispatch_queue_t)executionQueue; + @end diff --git a/packages/react-native/Libraries/Blob/RCTBlobManager.mm b/packages/react-native/Libraries/Blob/RCTBlobManager.mm index 14496f7e4910..82c089789041 100755 --- a/packages/react-native/Libraries/Blob/RCTBlobManager.mm +++ b/packages/react-native/Libraries/Blob/RCTBlobManager.mm @@ -11,6 +11,7 @@ #import #import +#import #import #import #import @@ -18,6 +19,16 @@ #import "RCTBlobCollector.h" #import "RCTBlobPlugins.h" +RCT_MOCK_DEF(RCTBlobManager, dispatch_async); +#define dispatch_async RCT_MOCK_USE(RCTBlobManager, dispatch_async) + +static BOOL gBlobManagerProcessingQueueEnabled = NO; + +RCT_EXTERN void RCTEnableBlobManagerProcessingQueue(BOOL enabled) +{ + gBlobManagerProcessingQueueEnabled = enabled; +} + static NSString *const kBlobURIScheme = @"blob"; @interface RCTBlobManager () < @@ -35,6 +46,7 @@ @implementation RCTBlobManager { std::mutex _blobsMutex; NSOperationQueue *_queue; + dispatch_queue_t _processingQueue; } RCT_EXPORT_MODULE(BlobModule) @@ -48,6 +60,10 @@ - (void)initialize std::lock_guard lock(_blobsMutex); _blobs = [NSMutableDictionary new]; + if (gBlobManagerProcessingQueueEnabled) { + _processingQueue = dispatch_queue_create("com.facebook.react.blobmanager.processing", DISPATCH_QUEUE_SERIAL); + } + facebook::react::RCTBlobCollector::install(self); } @@ -194,12 +210,22 @@ - (void)remove:(NSString *)blobId [NSException raise:@"Invalid type for blob" format:@"%@ is invalid", type]; } } - [self store:data withId:blobId]; + + dispatch_async([self executionQueue], ^{ + [self store:data withId:blobId]; + }); } RCT_EXPORT_METHOD(release : (NSString *)blobId) { - [self remove:blobId]; + dispatch_async([self executionQueue], ^{ + [self remove:blobId]; + }); +} + +- (dispatch_queue_t)executionQueue +{ + return gBlobManagerProcessingQueueEnabled ? _processingQueue : _methodQueue; } #pragma mark - RCTURLRequestHandler methods diff --git a/packages/react-native/Libraries/Blob/RCTFileReaderModule.mm b/packages/react-native/Libraries/Blob/RCTFileReaderModule.mm index 685d86b48d80..caa554029f73 100644 --- a/packages/react-native/Libraries/Blob/RCTFileReaderModule.mm +++ b/packages/react-native/Libraries/Blob/RCTFileReaderModule.mm @@ -31,7 +31,7 @@ @implementation RCTFileReaderModule : (RCTPromiseRejectBlock)reject) { RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"]; - dispatch_async(blobManager.methodQueue, ^{ + dispatch_async([blobManager executionQueue], ^{ NSData *data = [blobManager resolve:blob]; if (data == nil) { @@ -62,7 +62,7 @@ @implementation RCTFileReaderModule : (RCTPromiseRejectBlock)reject) { RCTBlobManager *blobManager = [_moduleRegistry moduleForName:"BlobModule"]; - dispatch_async(blobManager.methodQueue, ^{ + dispatch_async([blobManager executionQueue], ^{ NSData *data = [blobManager resolve:blob]; if (data == nil) { diff --git a/packages/rn-tester/RNTesterUnitTests/RCTBlobManagerTests.m b/packages/rn-tester/RNTesterUnitTests/RCTBlobManagerTests.m index 2bbf23e657dd..94ca812875e7 100644 --- a/packages/rn-tester/RNTesterUnitTests/RCTBlobManagerTests.m +++ b/packages/rn-tester/RNTesterUnitTests/RCTBlobManagerTests.m @@ -8,6 +8,15 @@ #import #import +#import + +RCT_MOCK_REF(RCTBlobManager, dispatch_async); + +static void _mock_dispatch_async(dispatch_queue_t queue, dispatch_block_t block) +{ + XCTAssertNotNil(queue); + block(); +} @interface RCTBlobManagerTests : XCTestCase @@ -23,8 +32,12 @@ - (void)setUp { [super setUp]; + RCT_MOCK_SET(RCTBlobManager, dispatch_async, _mock_dispatch_async); + _module = [RCTBlobManager new]; + dispatch_queue_t methodQueue = dispatch_queue_create("test.queue", DISPATCH_QUEUE_SERIAL); [_module setValue:nil forKey:@"bridge"]; + [_module setValue:methodQueue forKey:@"methodQueue"]; [_module initialize]; NSInteger size = 120; _data = [NSMutableData dataWithCapacity:size]; @@ -36,6 +49,13 @@ - (void)setUp [_module store:_data withId:_blobId]; } +- (void)tearDown +{ + [super tearDown]; + + RCT_MOCK_RESET(RCTBlobManager, dispatch_async); +} + - (void)testResolve { XCTAssertTrue([_data isEqualToData:[_module resolve:_blobId offset:0 size:_data.length]]); @@ -98,4 +118,37 @@ - (void)testCreateFromParts XCTAssertTrue([expectedData isEqualToData:result]); } +- (void)testCreateFromPartsProcessingQueue +{ + RCTEnableBlobManagerProcessingQueue(YES); + [self setUp]; + + NSDictionary *blobData = @{ + @"blobId" : _blobId, + @"offset" : @0, + @"size" : @(_data.length), + }; + NSDictionary *blob = @{ + @"data" : blobData, + @"type" : @"blob", + }; + NSString *stringData = @"i \u2665 dogs"; + NSDictionary *string = @{ + @"data" : stringData, + @"type" : @"string", + }; + NSString *resultId = [NSUUID UUID].UUIDString; + NSArray *parts = @[ blob, string ]; + + [_module createFromParts:parts withId:resultId]; + + NSMutableData *expectedData = [NSMutableData new]; + [expectedData appendData:_data]; + [expectedData appendData:[stringData dataUsingEncoding:NSUTF8StringEncoding]]; + + NSData *result = [_module resolve:resultId offset:0 size:expectedData.length]; + + XCTAssertTrue([expectedData isEqualToData:result]); +} + @end