Skip to content

Instantly share code, notes, and snippets.

@asarazan
Last active October 13, 2015 22:28
Show Gist options
  • Save asarazan/4265969 to your computer and use it in GitHub Desktop.
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
#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