views:

292

answers:

3

UPDATE: After submitting a radar to Apple it appears that this is a known issue (Radar #7640470).

CSURLCache is designed to cache resources for offline browsing, as NSURLCache only stores data in-memory.

If cachedResponse is autoreleased before returning the application crashes, if not, the objects are simply leaked.

Any light that could be shed onto this would be much appreciated.

Please note stringByEncodingURLEntities is a category method on NSString.

@interface CSURLCache : NSURLCache {} @end

@implementation CSURLCache

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
    NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:[[[request URL] absoluteString] stringByEncodingURLEntities]];

    if ([[NSFileManager defaultManager] fileExistsAtPath:path])
    {
        NSData *data = [[NSData alloc] initWithContentsOfFile:path];
        NSURLResponse *response = [[NSURLResponse alloc] initWithURL:[request URL]
                                                            MIMEType:nil
                                               expectedContentLength:[data length]
                                                    textEncodingName:nil];

        NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response
                                                                                       data:data];
        [response release];
        [data release];

        return cachedResponse;
    }

    return nil;
}

@end
A: 
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request

Well, this isn't an alloc, new, or copy method…

… and CSURLCache doesn't hold on to the object anywhere, so it's not owning it.

So, you need to autorelease it.

Of course, that means the object is doomed unless something retains it. Your app crashed because it tried to use the object after the object died.

Run your app under Instruments with the Zombies template. Look at where the app crashes and what it was doing when cachedResponseForRequest: was called. The caller needs to own the object until the time when the application would crash otherwise, and then release it.

Peter Hosey
The app crashes because a retain message is sent to a deallocated object, though, with subsequent investigation, NOT the cachedResponse. I cannot however figure out which object is being sent the message. If I enable NSZombieEnabled it prints "[Not A Type retain]".
Oliver White
Don't believe this to be the solution, as the NSCachedResponse isn't the object that is causing the crash when it is released (it's crashing as part of it's dealloc, meaning something that NSCachedResponse is retaining is overreleased)
BadPirate
outtru.mp: Implementing memory management correctly isn't optional (not doing so would mean the questioner would have two bugs), and the Zombies instrument is how you would determine what object is over-released and what the extraneous release was. So, the former aspect is part of the solution (leaking may be preferable to a crash, but once the crash is eliminated, the leak should be, too), and the latter is part of the investigation of the real problem.
Peter Hosey
A: 

I have the exact same issue.

Autoreleasing the response crashes the client while not autoreleasing it apparently leaks (according to naming convention convention and clang).

My caller is UIWebView so I can't really know what's going on inside there.

Anyone has an idea how to fix this?

jpmartineau
I think there is an underlying bug in the URL framework as it appears that the data is not being leaked, simply the cached response.
Oliver White
A: 

I am having the same trouble...

The issue seems to be if you replace sharedCache with a subclass of NSURLCache then the cached response is over released somewhere in foundation (or under retained). You can repeat this yourself:

NSURLCache *replaceCache;
NSString *diskPath = @"/tmp/cacheCrashCache";
const int memoryCapacity = 10000;
const int diskCapacity = 100000;

if(USE_SUBCLASS) // USE_SUBCLASS = YES, then it crashes, USE_SUBCLASS = NO then no crash
{
    replaceCache = [[NSURLCache_Subclass alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:diskPath];
}
else {
    replaceCache = [[NSURLCache alloc] initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:diskPath];
}

[NSURLCache setSharedURLCache:replaceCache];
[replaceCache release];

[[webView mainFrame] loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"http://www.apple.com"]]];

Webview should be a simple webview (like the demo browser), and NSURLCache_subclass can be a blank subclass of NSURLCache (which should function exactly the same way). With Zombies turned on the error shown is:

2010-08-30 12:57:48.987 CacheCrash[6445:5e03] *** -[Not A Type release]: message sent to deallocated instance 0x1001b3d00

I was able to track down that this is occurring when the NSCacheResponse item is deallocated. So, likely there is something that is being released from NSCacheResponse that isn't properly being retained.

I can't seem to find a work around that doesn't leak. I've even tried making my subclass simply a buffer to pass messages to a legitimate copy of NSURLCache, but this also failed. The only solution seems to leak:

- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
    NSCachedURLResponse *response = [super cachedResponseForRequest:request];

    if(HACK_FIX)
    {
        [response retain];
        return response; // Over-retain
    }
    else
        return response;
}

Anyone have a better fix for this?

BadPirate
That's using NSZombieEnabled, right? What if you use the Zombies instrument?
Peter Hosey
Zombies instrument goes a better deeper (Like that tool now!):Seems it's a CFCachedURLResponse that is being over released in CFNetwork's URLConnectionLoader::reallyCleanUpProtocol(unsigned char)...I understand this issue has been fixed for iOS. But it still seems to exist in Snow Leopard.
BadPirate