[Nearby] Optimize NWFramework socket reads and writes for performance and memory. III

PiperOrigin-RevId: 894930585
This commit is contained in:
Edwin Wu
2026-04-05 07:51:18 -07:00
committed by Copybara-Service
parent 1bc6346601
commit b382cdb022
4 changed files with 80 additions and 1 deletions
@@ -26,4 +26,5 @@ typedef NS_ERROR_ENUM(GNCNWFrameworkErrorDomain, GNCNWFrameworkError){
GNCNWFrameworkErrorUnknown,
GNCNWFrameworkErrorTimedOut,
GNCNWFrameworkErrorDuplicateDiscovererForServiceType,
GNCNWFrameworkErrorNotConnected,
};
@@ -75,6 +75,17 @@
*/
- (BOOL)write:(NSData *)data error:(NSError **_Nullable)error;
/**
* Writes raw bytes to the connection.
*
* @param bytes The buffer to write.
* @param length The number of bytes to write.
* @param error Error that will be populated on failure.
*/
- (BOOL)writeBytes:(const void *)bytes
length:(NSUInteger)length
error:(NSError **_Nullable)error;
/**
* Gracefully closes the connection to remote endpoint.
*
@@ -92,7 +92,7 @@ static const NSTimeInterval kConnectionWriteTimeout = 5.0; // 5 seconds timeout
if (!self.connection) {
if (error) {
*error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain
code:GNCNWFrameworkErrorUnknown
code:GNCNWFrameworkErrorNotConnected
userInfo:nil];
}
return std::nullopt;
@@ -185,6 +185,50 @@ static const NSTimeInterval kConnectionWriteTimeout = 5.0; // 5 seconds timeout
return signaled && blockSuccess;
}
- (BOOL)writeBytes:(const void *)bytes length:(NSUInteger)length error:(NSError **)error {
if (!self.connection) {
if (error) {
*error = [NSError errorWithDomain:GNCNWFrameworkErrorDomain
code:GNCNWFrameworkErrorNotConnected
userInfo:nil];
}
return NO;
}
dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
__block NSError *blockError = nil;
// OPTIMIZATION: Use DISPATCH_DATA_DESTRUCTOR_DEFAULT to perform a
// single copy into a GCD-managed buffer. No NSData required.
// TODO: edwinwu - Investigate to see if it is worth to make it zero-copy by replacing
// DISPATCH_DATA_DESTRUCTOR_DEFAULT with a custom empty destructor:
// dispatch_data_t dispatchData = dispatch_data_create(bytes, length, nil, ^{
// // Zero-copy: ownership remains with the caller.
// });
dispatch_data_t dispatchData =
dispatch_data_create(bytes, length, nil, DISPATCH_DATA_DESTRUCTOR_DEFAULT);
[self.connection sendData:dispatchData
context:NW_CONNECTION_DEFAULT_MESSAGE_CONTEXT
isComplete:NO
completionHandler:^(nw_error_t _Nullable sendError) {
if (sendError) {
blockError = (__bridge_transfer NSError *)nw_error_copy_cf_error(sendError);
}
dispatch_semaphore_signal(semaphore);
}];
// Wait until signaled or the 5-second timeout passes
intptr_t waitResult = dispatch_semaphore_wait(
semaphore,
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kConnectionWriteTimeout * NSEC_PER_SEC)));
if (error != nil) {
*error = blockError;
}
return (waitResult == 0) && (blockError == nil);
}
- (void)close {
[_connection cancel];
_connection = nil;
@@ -132,6 +132,28 @@ NS_ASSUME_NONNULL_BEGIN
XCTAssertFalse(result);
}
- (void)testWriteBytes_Success {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [_socket writeBytes:testData.bytes length:testData.length error:&error];
XCTAssertTrue(result);
XCTAssertNil(error);
}
- (void)testWriteBytes_Error {
NSError *error = nil;
NSString *testString = @"testData";
NSData *testData = [testString dataUsingEncoding:NSUTF8StringEncoding];
_fakeConnection.simulateSendFailure = YES;
BOOL result = [_socket writeBytes:testData.bytes length:testData.length error:&error];
XCTAssertFalse(result);
}
- (void)testClose {
XCTAssertFalse(_fakeConnection.cancelCalled);
[_socket close];
@@ -140,6 +162,7 @@ NS_ASSUME_NONNULL_BEGIN
NSError *error = nil;
XCTAssertNil([_socket readMaxLength:10 error:&error]);
XCTAssertFalse([_socket write:[NSData data] error:&error]);
XCTAssertFalse([_socket writeBytes:"test" length:4 error:&error]);
}
@end