Permalink
Browse files

fixed input stream leaks in iOS7

  • Loading branch information...
1 parent b391060 commit 499a3be1f92d7023e2d2092197dbb71c77cdd330 @OpenFibers committed Nov 13, 2013
Showing with 189 additions and 41 deletions.
  1. +4 −0 Classes/ASIHTTPRequest.m
  2. +3 −5 Classes/ASIInputStream.h
  3. +182 −36 Classes/ASIInputStream.m
View
@@ -1173,8 +1173,10 @@ - (void)startRequest
// Are we gzipping the request body?
if ([self compressedPostBodyFilePath] && [fileManager fileExistsAtPath:[self compressedPostBodyFilePath]]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithFileAtPath:[self compressedPostBodyFilePath]]];
} else {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithFileAtPath:[self postBodyFilePath] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithFileAtPath:[self postBodyFilePath]]];
}
[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
} else {
@@ -1183,8 +1185,10 @@ - (void)startRequest
if ([self postBody] && [[self postBody] length] > 0) {
if ([self shouldCompressRequestBody] && [self compressedPostBody]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self compressedPostBody] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithData:[self compressedPostBody]]];
} else if ([self postBody]) {
[self setPostBodyReadStream:[ASIInputStream inputStreamWithData:[self postBody] request:self]];
+// [self setPostBodyReadStream:[NSInputStream inputStreamWithData:[self postBody]]];
}
[self setReadStream:[NSMakeCollectable(CFReadStreamCreateForStreamedHTTPRequest(kCFAllocatorDefault, request,(CFReadStreamRef)[self postBodyReadStream])) autorelease]];
View
@@ -14,13 +14,11 @@
// Subclassing NSInputStream seems to be tricky, and may involve overriding undocumented methods, so we'll cheat instead.
// It is used by ASIHTTPRequest whenever we have a request body, and handles measuring and throttling the bandwidth used for uploading
-@interface ASIInputStream : NSObject {
- NSInputStream *stream;
- ASIHTTPRequest *request;
-}
+@interface ASIInputStream : NSObject<NSStreamDelegate>
+
+ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)request;
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)request;
+- (id)initWithInputStream:(NSInputStream *)stream;
-@property (retain, nonatomic) NSInputStream *stream;
@property (assign, nonatomic) ASIHTTPRequest *request;
@end
View
@@ -8,11 +8,21 @@
#import "ASIInputStream.h"
#import "ASIHTTPRequest.h"
+#import <objc/runtime.h>
// Used to ensure only one request can read data at once
static NSLock *readLock = nil;
@implementation ASIInputStream
+{
+ NSInputStream *stream;
+ id<NSStreamDelegate> delegate;
+
+ CFReadStreamClientCallBack copiedCallback;
+ CFStreamClientContext copiedContext;
+ CFOptionFlags requestedEvents;
+ ASIHTTPRequest *request;
+}
+ (void)initialize
{
@@ -23,53 +33,42 @@ + (void)initialize
+ (id)inputStreamWithFileAtPath:(NSString *)path request:(ASIHTTPRequest *)theRequest
{
- ASIInputStream *theStream = [[[self alloc] init] autorelease];
+ ASIInputStream *theStream = [[[ASIInputStream alloc] initWithInputStream:[NSInputStream inputStreamWithFileAtPath:path]] autorelease];
[theStream setRequest:theRequest];
- [theStream setStream:[NSInputStream inputStreamWithFileAtPath:path]];
return theStream;
}
+ (id)inputStreamWithData:(NSData *)data request:(ASIHTTPRequest *)theRequest
{
- ASIInputStream *theStream = [[[self alloc] init] autorelease];
+ ASIInputStream *theStream = [[[ASIInputStream alloc] initWithInputStream:[NSInputStream inputStreamWithData:data]] autorelease];
[theStream setRequest:theRequest];
- [theStream setStream:[NSInputStream inputStreamWithData:data]];
return theStream;
}
-- (void)dealloc
+#pragma mark - Object lifecycle
+
+- (id)initWithInputStream:(NSInputStream *)aStream
{
- [stream release];
- [super dealloc];
+ self = [super init];
+ if (self) {
+ // Initialization code here.
+ stream = [aStream retain];
+ [stream setDelegate:self];
+
+ [self setDelegate:self];
+ }
+
+ return self;
}
-// Called when CFNetwork wants to read more of our request body
-// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
-- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
+- (void)dealloc
{
- [readLock lock];
- unsigned long toRead = len;
- if ([ASIHTTPRequest isBandwidthThrottled]) {
- toRead = [ASIHTTPRequest maxUploadReadLength];
- if (toRead > len) {
- toRead = len;
- } else if (toRead == 0) {
- toRead = 1;
- }
- [request performThrottling];
- }
- [readLock unlock];
- NSInteger rv = [stream read:buffer maxLength:toRead];
- if (rv > 0)
- [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv];
- return rv;
+ [stream release];
+ [super dealloc];
}
-/*
- * Implement NSInputStream mandatory methods to make sure they are implemented
- * (necessary for MacRuby for example) and avoid the overhead of method
- * forwarding for these common methods.
- */
+#pragma mark - NSStream subclass methods
+
- (void)open
{
[stream open];
@@ -80,14 +79,19 @@ - (void)close
[stream close];
}
-- (id)delegate
+- (id <NSStreamDelegate> )delegate
{
- return [stream delegate];
+ return delegate;
}
-- (void)setDelegate:(id)delegate
+- (void)setDelegate:(id<NSStreamDelegate>)aDelegate
{
- [stream setDelegate:delegate];
+ if (aDelegate == nil) {
+ delegate = self;
+ }
+ else {
+ delegate = aDelegate;
+ }
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode
@@ -120,6 +124,149 @@ - (NSError *)streamError
return [stream streamError];
}
+#pragma mark - NSInputStream subclass methods
+
+// Called when CFNetwork wants to read more of our request body
+// When throttling is on, we ask ASIHTTPRequest for the maximum amount of data we can read
+- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len
+{
+ [readLock lock];
+ unsigned long toRead = len;
+ if ([ASIHTTPRequest isBandwidthThrottled]) {
+ toRead = [ASIHTTPRequest maxUploadReadLength];
+ if (toRead > len) {
+ toRead = len;
+ } else if (toRead == 0) {
+ toRead = 1;
+ }
+ [request performThrottling];
+ }
+ [readLock unlock];
+ NSInteger rv = [stream read:buffer maxLength:toRead];
+ if (rv > 0)
+ [ASIHTTPRequest incrementBandwidthUsedInLastSecond:rv];
+ return rv;
+}
+
+
+- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len
+{
+ // We cannot implement our character-counting in O(1) time,
+ // so we return NO as indicated in the NSInputStream
+ // documentation.
+ return NO;
+}
+
+- (BOOL)hasBytesAvailable
+{
+ return [stream hasBytesAvailable];
+}
+
+#pragma mark - Undocumented CFReadStream bridged methods
+
++ (BOOL)resolveInstanceMethod:(SEL) selector
+{
+ NSString *name = NSStringFromSelector(selector);
+
+ if ([name hasPrefix:@"_"]){
+ name = [name substringFromIndex:1];
+ SEL aSelector = NSSelectorFromString(name);
+ Method method = class_getInstanceMethod(self, aSelector);
+
+ if (method)
+ {
+ class_addMethod(self,
+ selector,
+ method_getImplementation(method),
+ method_getTypeEncoding(method));
+ return YES;
+ }
+ }
+ return [super resolveInstanceMethod:selector];
+}
+
+- (void)scheduleInCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode
+{
+ CFReadStreamScheduleWithRunLoop((CFReadStreamRef)stream, aRunLoop, aMode);
+}
+
+- (BOOL)setCFClientFlags:(CFOptionFlags)inFlags callback:(CFReadStreamClientCallBack)inCallback context:(CFStreamClientContext *)inContext
+{
+ if (inCallback != NULL) {
+ requestedEvents = inFlags;
+ copiedCallback = inCallback;
+ memcpy(&copiedContext, inContext, sizeof(CFStreamClientContext));
+
+ if (copiedContext.info && copiedContext.retain) {
+ copiedContext.retain(copiedContext.info);
+ }
+ }
+ else {
+ requestedEvents = kCFStreamEventNone;
+ copiedCallback = NULL;
+ if (copiedContext.info && copiedContext.release) {
+ copiedContext.release(copiedContext.info);
+ }
+
+ memset(&copiedContext, 0, sizeof(CFStreamClientContext));
+ }
+
+ return YES;
+}
+
+- (void)unscheduleFromCFRunLoop:(CFRunLoopRef)aRunLoop forMode:(CFStringRef)aMode
+{
+ CFReadStreamUnscheduleFromRunLoop((CFReadStreamRef)stream, aRunLoop, aMode);
+}
+
+#pragma mark - NSStreamDelegate methods
+
+- (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode
+{
+ assert(aStream == stream);
+
+ switch (eventCode) {
+ case NSStreamEventOpenCompleted:
+ if (requestedEvents & kCFStreamEventOpenCompleted) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventOpenCompleted,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventHasBytesAvailable:
+ if (requestedEvents & kCFStreamEventHasBytesAvailable) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventHasBytesAvailable,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventErrorOccurred:
+ if (requestedEvents & kCFStreamEventErrorOccurred) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventErrorOccurred,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventEndEncountered:
+ if (requestedEvents & kCFStreamEventEndEncountered) {
+ copiedCallback((CFReadStreamRef)self,
+ kCFStreamEventEndEncountered,
+ copiedContext.info);
+ }
+ break;
+
+ case NSStreamEventHasSpaceAvailable:
+ // This doesn't make sense for a read stream
+ break;
+
+ default:
+ break;
+ }
+}
+
// If we get asked to perform a method we don't have (probably internal ones),
// we'll just forward the message to our stream
@@ -133,6 +280,5 @@ - (void)forwardInvocation:(NSInvocation *)anInvocation
[anInvocation invokeWithTarget:stream];
}
-@synthesize stream;
@synthesize request;
@end

5 comments on commit 499a3be

@zhangdan

good!

@393385724

+1

@lixiasandy

In several broken network, network operation, the interface will be stuck, caused by abnormal maybe methods, [ASIHTTPRequest findProxyCredentials], [ASIHTTPRequest markAsFinished]. For me it is important to do this can be resolved as soon as possible...

@OpenFibers
Owner

Hello @lixiasandy , does the original repo of ASIHTTPRequest works for this situation?

Please sign in to comment.