diff --git a/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h index 1e7ad3202..c11a1c4d2 100644 --- a/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h +++ b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.h @@ -25,4 +25,6 @@ unmaskBytes:(BOOL)unmaskBytes; - (void)returnConsumer:(SRIOConsumer *)consumer; +- (void)clear; + @end diff --git a/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m index 2c527daed..9d54874b0 100644 --- a/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m +++ b/SocketRocket/Internal/IOConsumer/SRIOConsumerPool.m @@ -61,4 +61,10 @@ - (void)returnConsumer:(SRIOConsumer *)consumer; } } +- (void)clear +{ + _poolSize = 0; + _bufferedConsumers = nil; +} + @end diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 83f3e128f..708a73216 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -295,6 +295,14 @@ + (BOOL)automaticallyNotifiesObserversOfReadyState { return NO; } +-(void)_onTimeout +{ + if (self.readyState == SR_CONNECTING) { + NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorTimedOut, @"Timed out connecting to server."); + [self _failWithError:error]; + } +} + ///-------------------------------------- #pragma mark - Open / Close ///-------------------------------------- @@ -307,13 +315,7 @@ - (void)open _selfRetain = self; if (_urlRequest.timeoutInterval > 0) { - dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_urlRequest.timeoutInterval * NSEC_PER_SEC)); - dispatch_after(popTime, dispatch_get_main_queue(), ^{ - if (self.readyState == SR_CONNECTING) { - NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, NSURLErrorTimedOut, @"Timed out connecting to server."); - [self _failWithError:error]; - } - }); + [self performSelector:@selector(_onTimeout) withObject:nil afterDelay:_urlRequest.timeoutInterval]; } _proxyConnect = [[SRProxyConnect alloc] initWithURL:_url]; @@ -419,14 +421,23 @@ - (void)_readHTTPHeader; _receivedHTTPHeaders = CFHTTPMessageCreateEmpty(NULL, NO); } + // Uses weak self object in the block, otherwise Consumers will retain SRWebSocket instance, + // and SRWebSocket instance also hold consumers, cycle reference will occur. + __weak typeof(self) wself = self; + [self _readUntilHeaderCompleteWithCallback:^(SRWebSocket *socket, NSData *data) { - CFHTTPMessageAppendBytes(_receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); - if (CFHTTPMessageIsHeaderComplete(_receivedHTTPHeaders)) { - SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(_receivedHTTPHeaders))); - [self _HTTPHeadersDidFinish]; + __strong typeof(wself) sself = wself; + if (sself == nil) + return; + + CFHTTPMessageAppendBytes(sself.receivedHTTPHeaders, (const UInt8 *)data.bytes, data.length); + + if (CFHTTPMessageIsHeaderComplete(sself.receivedHTTPHeaders)) { + SRDebugLog(@"Finished reading headers %@", CFBridgingRelease(CFHTTPMessageCopyAllHeaderFields(sself.receivedHTTPHeaders))); + [sself _HTTPHeadersDidFinish]; } else { - [self _readHTTPHeader]; + [sself _readHTTPHeader]; } }]; } @@ -1127,6 +1138,16 @@ - (void)_scheduleCleanup _cleanupScheduled = YES; + // _consumers retain SRWebSocket instance by block copy, if there are consumers here, clear them. + [_consumers removeAllObjects]; + [_consumerPool clear]; + + // Cancel the timer which retains SRWebSocket instance. + // If we don't cancel the timer, the 'dealloc' method will be invoked only after the time (default: 60s) have come, which may cause memory increase. + dispatch_async(dispatch_get_main_queue(), ^(){ + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(_onTimeout) object:nil]; + }); + // Cleanup NSStream delegate's in the same RunLoop used by the streams themselves: // This way we'll prevent race conditions between handleEvent and SRWebsocket's dealloc NSTimer *timer = [NSTimer timerWithTimeInterval:(0.0f) target:self selector:@selector(_cleanupSelfReference:) userInfo:nil repeats:NO];