Browse Source

Make copying optional when sending data (#427)

* Reduce memory usage

Reduce memory usage by discarding, not resetting, the frame data buffer.
Let delegate control copying.

* Delegate methods expect `self`

* Add `-sendWithoutCopyingData:error:`

* Add `-webSocket:shouldCopyReceivedData:`

* Update test script name in Makefile

* use a Swiftier name

* Fix error messages

* Remove `-webSocket:shouldCopyReceivedData:`

* Remove assertion

* Fix typo
Erik Price 9 years ago
parent
commit
fcd482898a
3 changed files with 28 additions and 8 deletions
  1. 3 3
      Makefile
  2. 12 0
      SocketRocket/SRWebSocket.h
  3. 13 5
      SocketRocket/SRWebSocket.m

+ 3 - 3
Makefile

@@ -10,17 +10,17 @@ clean:
 test:
 
 	mkdir -p pages/results
-	bash ./TestSupport/run_test.sh $(TEST_SCENARIOS) $(TEST_URL) Debug || open pages/results/index.html && false
+	bash ./TestSupport/run_test_server.sh $(TEST_SCENARIOS) $(TEST_URL) Debug || open pages/results/index.html && false
 	open pages/results/index.html
 
 test_all:
 
 	mkdir -p pages/results
-	bash ./TestSupport/run_test.sh '*' $(TEST_URL) Debug || open pages/results/index.html && false
+	bash ./TestSupport/run_test_server.sh '*' $(TEST_URL) Debug || open pages/results/index.html && false
 	open pages/results/index.html
 
 test_perf:
 
 	mkdir -p pages/results
-	bash ./TestSupport/run_test.sh '9.*' $(TEST_URL) Release || open pages/results/index.html && false
+	bash ./TestSupport/run_test_server.sh '9.*' $(TEST_URL) Release || open pages/results/index.html && false
 	open pages/results/index.html

+ 12 - 0
SocketRocket/SRWebSocket.h

@@ -265,6 +265,18 @@ NS_DESIGNATED_INITIALIZER;
  */
 - (BOOL)sendData:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(data:));
 
+/**
+ Send binary data to the server, without making a defensive copy of it first.
+
+ @param data  Data to send.
+ @param error On input, a pointer to variable for an `NSError` object.
+ If an error occurs, this pointer is set to an `NSError` object containing information about the error.
+ You may specify `nil` to ignore the error information.
+
+ @return `YES` if the string was scheduled to send, otherwise - `NO`.
+ */
+- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error NS_SWIFT_NAME(send(dataNoCopy:));
+
 /**
  Send Ping message to the server with optional data.
 

+ 13 - 5
SocketRocket/SRWebSocket.m

@@ -643,9 +643,15 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 }
 
 - (BOOL)sendData:(nullable NSData *)data error:(NSError **)error
+{
+    data = [data copy];
+    return [self sendDataNoCopy:data error:error];
+}
+
+- (BOOL)sendDataNoCopy:(nullable NSData *)data error:(NSError **)error
 {
     if (self.readyState != SR_OPEN) {
-        NSString *message = @"Invalid State: Cannot call `sendData:error:` until connection is open.";
+        NSString *message = @"Invalid State: Cannot call `sendDataNoCopy:error:` until connection is open.";
         if (error) {
             *error = SRErrorWithCodeDescription(2134, message);
         }
@@ -653,7 +659,6 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
         return NO;
     }
 
-    data = [data copy];
     dispatch_async(_workQueue, ^{
         if (data) {
             [self _sendFrameWithOpcode:SROpCodeBinaryFrame data:data];
@@ -786,7 +791,10 @@ static inline BOOL closeCodeIsValid(int closeCode) {
 
 - (void)_handleFrameWithData:(NSData *)frameData opCode:(NSInteger)opcode;
 {
+    //frameData will be copied before passing to handlers
+    //otherwise there can be misbehaviours when value at the pointer is changed
     frameData = [frameData copy];
+
     // Check that the current data is valid UTF8
 
     BOOL isControlFrame = (opcode == SROpCodePing || opcode == SROpCodePong || opcode == SROpCodeConnectionClose);
@@ -798,8 +806,6 @@ static inline BOOL closeCodeIsValid(int closeCode) {
         });
     }
 
-    //frameData will be copied before passing to handlers
-    //otherwise there can be misbehaviours when value at the pointer is changed
     switch (opcode) {
         case SROpCodeTextFrame: {
             NSString *string = [[NSString alloc] initWithData:frameData encoding:NSUTF8StringEncoding];
@@ -1031,7 +1037,9 @@ static const uint8_t SRPayloadLenMask   = 0x7F;
 - (void)_readFrameNew;
 {
     dispatch_async(_workQueue, ^{
-        [_currentFrameData setLength:0];
+        // Don't reset the length, since Apple doesn't guarantee that this will free the memory (and in tests on
+        // some platforms, it doesn't seem to, effectively causing a leak the size of the biggest frame so far).
+        _currentFrameData = [[NSMutableData alloc] init];
 
         _currentFrameOpcode = 0;
         _currentFrameCount = 0;