Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Complaints of errors in Xcode 4.3 with the NSCachedURLResponse category #29

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions SDURLCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@

#import <Foundation/Foundation.h>

// wrapper pattern
@interface SDCachedURLResponse : NSObject <NSCoding, NSCopying>
+ (id)cachedURLResponseWithNSCachedURLResponse:(NSCachedURLResponse*)url_response;

@property (nonatomic, retain) NSCachedURLResponse *cached_response;
@end

@interface SDURLCache : NSURLCache
{
@private
Expand Down Expand Up @@ -50,4 +57,9 @@
*/
- (BOOL)isCached:(NSURL *)url;

/*
* Returns the hash key for the url
*/
+ (NSString *)cacheKeyForURL:(NSURL *)url;

@end
142 changes: 81 additions & 61 deletions SDURLCache.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,43 +14,76 @@
static NSString *const kSDURLCacheInfoDiskUsageKey = @"diskUsage";
static NSString *const kSDURLCacheInfoAccessesKey = @"accesses";
static NSString *const kSDURLCacheInfoSizesKey = @"sizes";

// The removal of the NSCachedURLResponse category means that NSKeyedArchiver
// will throw an EXC_BAD_ACCESS when attempting to load NSCachedURLResponse
// data.
// This means that this change requires a cache refresh, and a new cache key
// namespace that will prevent this from happening.
// Old cache keys will eventually be evicted from the system as new keys are
// populated.
static NSString *const kSDURLCacheVersion = @"V2";

static float const kSDURLCacheLastModFraction = 0.1f; // 10% since Last-Modified suggested by RFC2616 section 13.2.4
static float const kSDURLCacheDefault = 3600; // Default cache expiration delay if none defined (1 hour)

static NSDateFormatter* CreateDateFormatter(NSString *format)
{
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"];

[dateFormatter setLocale:locale];
[dateFormatter setLocale:[[[NSLocale alloc] initWithLocaleIdentifier:@"en_US"] autorelease]];
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]];
[dateFormatter setDateFormat:format];
[locale release];

return [dateFormatter autorelease];
}

@implementation NSCachedURLResponse(NSCoder)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"

@implementation SDCachedURLResponse
@synthesize cached_response;

+ (id)cachedURLResponseWithNSCachedURLResponse:(NSCachedURLResponse *)url_response {
SDCachedURLResponse *response = [[SDCachedURLResponse alloc] init];
response.cached_response = url_response;

return [response autorelease];
}
#pragma mark NSCopying Methods
- (id)copyWithZone:(NSZone *)zone {
SDCachedURLResponse *newResponse = [[[self class] allocWithZone:zone] init];
if (newResponse) {
newResponse.cached_response = [[self.cached_response copyWithZone:zone] autorelease];
}

return newResponse;
}

#pragma mark NSCoding Methods
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeDataObject:self.data];
[coder encodeObject:self.response forKey:@"response"];
[coder encodeObject:self.userInfo forKey:@"userInfo"];
[coder encodeInt:self.storagePolicy forKey:@"storagePolicy"];
// force write the data of underlying cached response
[coder encodeDataObject:self.cached_response.data];
[coder encodeObject:self.cached_response.response forKey:@"response"];
[coder encodeObject:self.cached_response.userInfo forKey:@"userInfo"];
[coder encodeInt:self.cached_response.storagePolicy forKey:@"storagePolicy"];
}

- (id)initWithCoder:(NSCoder *)coder
{
return [self initWithResponse:[coder decodeObjectForKey:@"response"]
data:[coder decodeDataObject]
userInfo:[coder decodeObjectForKey:@"userInfo"]
storagePolicy:[coder decodeIntForKey:@"storagePolicy"]];
self = [super init];

if (self) {
self.cached_response = [[[NSCachedURLResponse alloc] initWithResponse:[coder decodeObjectForKey:@"response"]
data:[coder decodeDataObject]
userInfo:[coder decodeObjectForKey:@"userInfo"]
storagePolicy:[coder decodeIntForKey:@"storagePolicy"]] autorelease];
}

return self;
}

#pragma clang diagnostic pop
- (void)dealloc {
[super dealloc];
[cached_response release], cached_response = nil;
}

@end

Expand Down Expand Up @@ -87,8 +120,8 @@ + (NSString *)cacheKeyForURL:(NSURL *)url
const char *str = [url.absoluteString UTF8String];
unsigned char r[CC_MD5_DIGEST_LENGTH];
CC_MD5(str, strlen(str), r);
return [NSString stringWithFormat:@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]];
return [NSString stringWithFormat:@"%@_%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
kSDURLCacheVersion, r[0], r[1], r[2], r[3], r[4], r[5], r[6], r[7], r[8], r[9], r[10], r[11], r[12], r[13], r[14], r[15]];
}

/*
Expand Down Expand Up @@ -167,12 +200,11 @@ + (NSDate *)expirationDateFromHeaders:(NSDictionary *)headers withStatusCode:(NS
}

NSInteger maxAge;
foundRange = [cacheControl rangeOfString:@"max-age"];
foundRange = [cacheControl rangeOfString:@"max-age="];
if (foundRange.length > 0)
{
NSScanner *cacheControlScanner = [NSScanner scannerWithString:cacheControl];
[cacheControlScanner setScanLocation:foundRange.location + foundRange.length];
[cacheControlScanner scanString:@"=" intoString:nil];
if ([cacheControlScanner scanInteger:&maxAge])
{
if (maxAge > 0)
Expand Down Expand Up @@ -315,7 +347,7 @@ - (void)removeCachedResponseForCachedKeys:(NSArray *)cacheKeys
{
NSMutableDictionary *accesses = [self.diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey];
NSMutableDictionary *sizes = [self.diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey];
NSFileManager *fileManager = [[NSFileManager alloc] init];
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];

while ((cacheKey = [enumerator nextObject]))
{
Expand All @@ -327,7 +359,6 @@ - (void)removeCachedResponseForCachedKeys:(NSArray *)cacheKeys
diskCacheUsage -= cacheItemSize;
[self.diskCacheInfo setObject:[NSNumber numberWithUnsignedInteger:diskCacheUsage] forKey:kSDURLCacheInfoDiskUsageKey];
}
[fileManager release];
}

[pool drain];
Expand Down Expand Up @@ -368,7 +399,10 @@ - (void)balanceDiskUsage
- (void)storeToDisk:(NSDictionary *)context
{
NSURLRequest *request = [context objectForKey:@"request"];
NSCachedURLResponse *cachedResponse = [context objectForKey:@"cachedResponse"];

// use wrapper to ensure we save appropriate fields..
SDCachedURLResponse *cachedResponse = [SDCachedURLResponse
cachedURLResponseWithNSCachedURLResponse:[context objectForKey:@"cachedResponse"]];

NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL];
NSString *cacheFilePath = [diskCachePath stringByAppendingPathComponent:cacheKey];
Expand Down Expand Up @@ -402,25 +436,21 @@ - (void)storeToDisk:(NSDictionary *)context

- (void)periodicMaintenance
{
// If another maintenance operation is already sceduled, cancel it so this new operation will be executed after other
// If another same maintenance operation is already scheduled, cancel it so this new operation will be executed after other
// operations of the queue, so we can group more work together
[periodicMaintenanceOperation cancel];
self.periodicMaintenanceOperation = nil;

// If disk usage exceeds capacity, run the cache eviction operation and if cacheInfo dictionary is dirty, save it in an operation
// If disk usage outrich capacity, run the cache eviction operation and if cacheInfo dictionnary is dirty, save it in an operation
if (diskCacheUsage > self.diskCapacity)
{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(balanceDiskUsage) object:nil];
self.periodicMaintenanceOperation = operation;
self.periodicMaintenanceOperation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(balanceDiskUsage) object:nil] autorelease];
[ioQueue addOperation:periodicMaintenanceOperation];
[operation release];
}
else if (diskCacheInfoDirty)
{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saveCacheInfo) object:nil];
self.periodicMaintenanceOperation = operation;
self.periodicMaintenanceOperation = [[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saveCacheInfo) object:nil] autorelease];
[ioQueue addOperation:periodicMaintenanceOperation];
[operation release];
}
}

Expand All @@ -442,11 +472,9 @@ - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger
self.diskCachePath = path;

// Init the operation queue
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
self.ioQueue = queue;
[queue release];

self.ioQueue = [[[NSOperationQueue alloc] init] autorelease];
ioQueue.maxConcurrentOperationCount = 1; // used to streamline operations in a separate thread

self.ignoreMemoryOnlyStoragePolicy = YES;
}

Expand Down Expand Up @@ -487,14 +515,10 @@ - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NS
}
}

NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:@selector(storeToDisk:)
object:[NSDictionary dictionaryWithObjectsAndKeys:
cachedResponse, @"cachedResponse",
request, @"request",
nil]];
[ioQueue addOperation:operation];
[operation release];
[ioQueue addOperation:[[[NSInvocationOperation alloc] initWithTarget:self selector:@selector(storeToDisk:)
object:[NSDictionary dictionaryWithObjectsAndKeys:
cachedResponse, @"cachedResponse",
request, @"request", nil]] autorelease]];
}
}

Expand All @@ -504,9 +528,7 @@ - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request

NSCachedURLResponse *memoryResponse = [super cachedResponseForRequest:request];
if (memoryResponse)
{
return memoryResponse;
}

NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL];

Expand All @@ -515,9 +537,15 @@ - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
@synchronized(self.diskCacheInfo)
{
NSMutableDictionary *accesses = [self.diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey];
if ([accesses objectForKey:cacheKey]) // OPTI: Check for cache-hit in a in-memory dictionary before hitting the file system
if ([accesses objectForKey:cacheKey]) // OPTI: Check for cache-hit in a in-memory dictionnary before to hit the FS
{
NSCachedURLResponse *diskResponse = [NSKeyedUnarchiver unarchiveObjectWithFile:[diskCachePath stringByAppendingPathComponent:cacheKey]];

// load wrapper
SDCachedURLResponse *diskResponseWrapper = [NSKeyedUnarchiver unarchiveObjectWithFile:
[diskCachePath stringByAppendingPathComponent:cacheKey]];

NSCachedURLResponse *diskResponse = diskResponseWrapper.cached_response;

if (diskResponse)
{
// OPTI: Log the entry last access time for LRU cache eviction algorithm but don't save the dictionary
Expand All @@ -527,7 +555,8 @@ - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request

// OPTI: Store the response to memory cache for potential future requests
[super storeCachedResponse:diskResponse forRequest:request];



// SRK: Work around an interesting retainCount bug in CFNetwork on iOS << 3.2.
if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_2)
{
Expand All @@ -541,7 +570,7 @@ - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
}
}
}

return nil;
}

Expand All @@ -566,11 +595,8 @@ - (void)removeCachedResponseForRequest:(NSURLRequest *)request
- (void)removeAllCachedResponses
{
[super removeAllCachedResponses];

NSFileManager *fileManager = [[NSFileManager alloc] init];
NSFileManager *fileManager = [[[NSFileManager alloc] init] autorelease];
[fileManager removeItemAtPath:diskCachePath error:NULL];
[fileManager release];

@synchronized(self)
{
[diskCacheInfo release], diskCacheInfo = nil;
Expand All @@ -586,15 +612,9 @@ - (BOOL)isCached:(NSURL *)url
{
return YES;
}

NSString *cacheKey = [SDURLCache cacheKeyForURL:url];
NSString *cacheFile = [diskCachePath stringByAppendingPathComponent:cacheKey];
NSFileManager *manager = [[NSFileManager alloc] init];

BOOL exists = [manager fileExistsAtPath:cacheFile];
[manager release];

if (exists)
if ([[[[NSFileManager alloc] init] autorelease] fileExistsAtPath:cacheFile])
{
return YES;
}
Expand Down