Last active
October 13, 2015 22:28
-
-
Save asarazan/4265969 to your computer and use it in GitHub Desktop.
Proposed workaround for NSURLCachedResponse leak as described at https://github.com/steipete/SDURLCache/issues/7
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "NSCachedURLResponse+MemoryLeakFix.h" | |
#import <objc/runtime.h> | |
/** | |
* In iOS 5 onward, there is a significant memory leak in NSCachedURLResponse. | |
* Discussion: https://github.com/steipete/SDURLCache/issues/7#issuecomment-5066300 | |
*/ | |
@implementation NSCachedURLResponse (MemoryLeakFix) | |
/** | |
* We will attempt to reproduce the leak with a single-byte data object. | |
* | |
* The memory leak won't present itself if NSCachedURLResponse segments the data internally. | |
* The safest way to prevent that is to use a single byte object. | |
*/ | |
static BOOL needsWorkaround() | |
{ | |
NSData *data1, *data2; | |
@autoreleasepool { | |
data1 = [[NSData alloc] initWithBytes:"a" length:1]; | |
NSURL *url = [NSURL URLWithString:@"http://www.google.com"]; | |
NSURLResponse *response = | |
[[NSURLResponse alloc] initWithURL:url | |
MIMEType:@"text/html" | |
expectedContentLength:0 | |
textEncodingName:@"utf-8"]; | |
NSCachedURLResponse *cached = | |
[[NSCachedURLResponse alloc] initWithResponse:response data:data1]; | |
[response release]; | |
data2 = cached.data; | |
[cached release]; | |
} | |
if (data1 == data2) { | |
if (data1.retainCount == 2) { | |
NSLog(@"Memory leak detected, activating workaround"); | |
[data1 release]; | |
[data1 release]; | |
return YES; | |
} else { | |
NSLog(@"No memory leak detected, not activating workaround"); | |
[data1 release]; | |
return NO; | |
} | |
} else { | |
NSLog(@"Inconclusive, not activating memory leak workaround"); | |
[data1 release]; | |
return NO; | |
} | |
} | |
/** | |
* We call the original accessor twice. If the returned pointers are equal with increased retain count, we know | |
* we've hit the bad code path, and must manually counterbalance each retained call with a release. | |
* | |
* NOTE: Logically, we should only have to check for pointer equality, | |
* but we did see double-release crashes until we added the manual retainCount test. | |
*/ | |
- (NSData *)cueSwizzledData; | |
{ | |
@synchronized(self) { | |
NSData *retval = [self cueSwizzledData]; | |
@synchronized (retval) { | |
NSInteger count = [retval retainCount]; | |
if (retval == [self cueSwizzledData]) { | |
if ([retval retainCount] > count) { | |
[retval release]; | |
[retval release]; | |
} | |
} | |
} | |
return retval; | |
} | |
} | |
+ (void)cueInstallDataFixIfNeeded; | |
{ | |
if (needsWorkaround()) { | |
Method oldMethod = class_getInstanceMethod([NSCachedURLResponse class], @selector(data)); | |
Method newMethod = class_getInstanceMethod([NSCachedURLResponse class], @selector(cueSwizzledData)); | |
method_exchangeImplementations(oldMethod, newMethod); | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment