Permalink
Browse files

Fix SDURLCache with iOS 5+ (fix #30, #29)

- Disable SDURLCache at runtime if iOS version is >5 as this version
  now includes disk caching support
- Exchange category with a wrapper to add NSCoder protocol support
  to NSCachedURLResponse
  • Loading branch information...
1 parent 9fdb79a commit 4c3acd9c578bc104b0d0dc7bb52484bf78cfa650 @rs committed May 11, 2012
Showing with 142 additions and 32 deletions.
  1. +17 −0 SDCachedURLResponse.h
  2. +65 −0 SDCachedURLResponse.m
  3. +1 −1 SDURLCache.h
  4. +51 −31 SDURLCache.m
  5. +8 −0 SDURLCache.xcodeproj/project.pbxproj
View
17 SDCachedURLResponse.h
@@ -0,0 +1,17 @@
+//
+// SDCachedURLResponse.h
+// SDURLCache
+//
+// Created by Olivier Poitrey on 12/05/12.
+// Copyright (c) 2012 Dailymotion. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface SDCachedURLResponse : NSObject <NSCoding, NSCopying>
+
+@property (nonatomic, retain) NSCachedURLResponse *response;
+
++ (id)cachedURLResponseWithNSCachedURLResponse:(NSCachedURLResponse *)response;
+
+@end
View
65 SDCachedURLResponse.m
@@ -0,0 +1,65 @@
+//
+// SDCachedURLResponse.m
+// SDURLCache
+//
+// Created by Olivier Poitrey on 12/05/12.
+// Copyright (c) 2012 Dailymotion. All rights reserved.
+//
+
+#import "SDCachedURLResponse.h"
+
+@implementation SDCachedURLResponse
+
+@synthesize response;
+
++ (id)cachedURLResponseWithNSCachedURLResponse:(NSCachedURLResponse *)response
+{
+ SDCachedURLResponse *wrappedResponse = [[SDCachedURLResponse alloc] init];
+ wrappedResponse.response = response;
+ return [wrappedResponse autorelease];
+}
+
+#pragma mark NSCopying Methods
+
+- (id)copyWithZone:(NSZone *)zone
+{
+ SDCachedURLResponse *newResponse = [[[self class] allocWithZone:zone] init];
+
+ if (newResponse)
+ {
+ newResponse.response = [[self.response copyWithZone:zone] autorelease];
+ }
+
+ return newResponse;
+}
+
+#pragma mark NSCoding Methods
+- (void)encodeWithCoder:(NSCoder *)coder
+{
+ // force write the data of underlying cached response
+ [coder encodeDataObject:self.response.data];
+ [coder encodeObject:self.response.response forKey:@"response"];
+ [coder encodeObject:self.response.userInfo forKey:@"userInfo"];
+ [coder encodeInt:self.response.storagePolicy forKey:@"storagePolicy"];
+}
+
+- (id)initWithCoder:(NSCoder *)coder
+{
+ if ((self = [super init]))
+ {
+ self.response = [[[NSCachedURLResponse alloc] initWithResponse:[coder decodeObjectForKey:@"response"]
+ data:[coder decodeDataObject]
+ userInfo:[coder decodeObjectForKey:@"userInfo"]
+ storagePolicy:[coder decodeIntForKey:@"storagePolicy"]] autorelease];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [super dealloc];
+ [response release], response = nil;
@zqxiaojin
zqxiaojin added a line comment May 15, 2012

this may crash if you access "response" after super dealloc

@rs
Owner
rs added a line comment May 15, 2012

you're right

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+}
+
+@end
View
2 SDURLCache.h
@@ -13,7 +13,7 @@
@private
NSString *diskCachePath;
NSMutableDictionary *diskCacheInfo;
- BOOL diskCacheInfoDirty, ignoreMemoryOnlyStoragePolicy;
+ BOOL diskCacheInfoDirty, ignoreMemoryOnlyStoragePolicy, disabled;
NSUInteger diskCacheUsage;
NSTimeInterval minCacheInterval;
NSOperationQueue *ioQueue;
View
82 SDURLCache.m
@@ -7,7 +7,18 @@
//
#import "SDURLCache.h"
+#import "SDCachedURLResponse.h"
#import <CommonCrypto/CommonDigest.h>
+#import <UIKit/UIDevice.h>
+
+// 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 NSTimeInterval const kSDURLCacheInfoDefaultMinCacheInterval = 5 * 60; // 5 minute
static NSString *const kSDURLCacheInfoFileName = @"cacheInfo.plist";
@@ -30,31 +41,6 @@
return [dateFormatter autorelease];
}
-@implementation NSCachedURLResponse(NSCoder)
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wobjc-protocol-method-implementation"
-
-- (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"];
-}
-
-- (id)initWithCoder:(NSCoder *)coder
-{
- return [self initWithResponse:[coder decodeObjectForKey:@"response"]
- data:[coder decodeDataObject]
- userInfo:[coder decodeObjectForKey:@"userInfo"]
- storagePolicy:[coder decodeIntForKey:@"storagePolicy"]];
-}
-
-#pragma clang diagnostic pop
-
-@end
-
-
@interface SDURLCache ()
@property (nonatomic, retain) NSString *diskCachePath;
@property (nonatomic, readonly) NSMutableDictionary *diskCacheInfo;
@@ -87,8 +73,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]];
}
/*
@@ -368,7 +354,8 @@ - (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];
@@ -436,7 +423,12 @@ + (NSString *)defaultCachePath
- (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path
{
- if ((self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]))
+ // iOS 5 implements disk caching. SDURLCache then disables itself at runtime if the current device OS
+ // version is 5 or greater
+ NSArray *version = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:@"."];
+ disabled = [[version objectAtIndex:0] intValue] >= 5;
+
+ if ((self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) && !disabled)
{
self.minCacheInterval = kSDURLCacheInfoDefaultMinCacheInterval;
self.diskCachePath = path;
@@ -455,6 +447,12 @@ - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger
- (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request
{
+ if (disabled)
+ {
+ [super storeCachedResponse:cachedResponse forRequest:request];
+ return;
+ }
+
request = [SDURLCache canonicalRequestForRequest:request];
if (request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData
@@ -500,6 +498,8 @@ - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NS
- (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
{
+ if (disabled) return [super cachedResponseForRequest:request];
+
request = [SDURLCache canonicalRequestForRequest:request];
NSCachedURLResponse *memoryResponse = [super cachedResponseForRequest:request];
@@ -517,7 +517,10 @@ - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
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
{
- NSCachedURLResponse *diskResponse = [NSKeyedUnarchiver unarchiveObjectWithFile:[diskCachePath stringByAppendingPathComponent:cacheKey]];
+ // load wrapper
+ SDCachedURLResponse *diskResponseWrapper = [NSKeyedUnarchiver unarchiveObjectWithFile:[diskCachePath stringByAppendingPathComponent:cacheKey]];
+ NSCachedURLResponse *diskResponse = diskResponseWrapper.response;
+
if (diskResponse)
{
// OPTI: Log the entry last access time for LRU cache eviction algorithm but don't save the dictionary
@@ -547,6 +550,8 @@ - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request
- (NSUInteger)currentDiskUsage
{
+ if (disabled) return [super currentDiskUsage];
+
if (!diskCacheInfo)
{
[self diskCacheInfo];
@@ -556,6 +561,12 @@ - (NSUInteger)currentDiskUsage
- (void)removeCachedResponseForRequest:(NSURLRequest *)request
{
+ if (disabled)
+ {
+ [super removeCachedResponseForRequest:request];
+ return;
+ }
+
request = [SDURLCache canonicalRequestForRequest:request];
[super removeCachedResponseForRequest:request];
@@ -567,6 +578,7 @@ - (void)removeAllCachedResponses
{
[super removeAllCachedResponses];
+ if (disabled) return;
NSFileManager *fileManager = [[NSFileManager alloc] init];
[fileManager removeItemAtPath:diskCachePath error:NULL];
[fileManager release];
@@ -586,7 +598,9 @@ - (BOOL)isCached:(NSURL *)url
{
return YES;
}
-
+
+ if (disabled) return NO;
+
NSString *cacheKey = [SDURLCache cacheKeyForURL:url];
NSString *cacheFile = [diskCachePath stringByAppendingPathComponent:cacheKey];
NSFileManager *manager = [[NSFileManager alloc] init];
@@ -605,6 +619,12 @@ - (BOOL)isCached:(NSURL *)url
- (void)dealloc
{
+ if (disabled)
+ {
+ [super dealloc];
+ return;
+ }
+
[periodicMaintenanceTimer invalidate];
[periodicMaintenanceTimer release], periodicMaintenanceTimer = nil;
[periodicMaintenanceOperation release], periodicMaintenanceOperation = nil;
View
8 SDURLCache.xcodeproj/project.pbxproj
@@ -7,6 +7,8 @@
objects = {
/* Begin PBXBuildFile section */
+ 5318A9AB155DD7B500AB6767 /* SDCachedURLResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 5318A9A9155DD7B500AB6767 /* SDCachedURLResponse.h */; };
+ 5318A9AC155DD7B500AB6767 /* SDCachedURLResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 5318A9AA155DD7B500AB6767 /* SDCachedURLResponse.m */; };
53F557F4114EA63600A3DA4B /* SDURLCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53F557F2114EA63600A3DA4B /* SDURLCache.h */; };
53F557F5114EA63600A3DA4B /* SDURLCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F557F3114EA63600A3DA4B /* SDURLCache.m */; };
53F5592C114F1ED800A3DA4B /* SDURLCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */; };
@@ -25,6 +27,8 @@
/* End PBXContainerItemProxy section */
/* Begin PBXFileReference section */
+ 5318A9A9155DD7B500AB6767 /* SDCachedURLResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDCachedURLResponse.h; sourceTree = "<group>"; };
+ 5318A9AA155DD7B500AB6767 /* SDCachedURLResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDCachedURLResponse.m; sourceTree = "<group>"; };
53F557F2114EA63600A3DA4B /* SDURLCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCache.h; sourceTree = "<group>"; };
53F557F3114EA63600A3DA4B /* SDURLCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDURLCache.m; sourceTree = "<group>"; };
53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDURLCacheTestBundle.octest; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -86,6 +90,8 @@
children = (
53F557F2114EA63600A3DA4B /* SDURLCache.h */,
53F557F3114EA63600A3DA4B /* SDURLCache.m */,
+ 5318A9A9155DD7B500AB6767 /* SDCachedURLResponse.h */,
+ 5318A9AA155DD7B500AB6767 /* SDCachedURLResponse.m */,
);
name = Classes;
sourceTree = "<group>";
@@ -116,6 +122,7 @@
files = (
AA747D9F0F9514B9006C5449 /* SDURLCache_Prefix.pch in Headers */,
53F557F4114EA63600A3DA4B /* SDURLCache.h in Headers */,
+ 5318A9AB155DD7B500AB6767 /* SDCachedURLResponse.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -227,6 +234,7 @@
buildActionMask = 2147483647;
files = (
53F557F5114EA63600A3DA4B /* SDURLCache.m in Sources */,
+ 5318A9AC155DD7B500AB6767 /* SDCachedURLResponse.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

10 comments on commit 4c3acd9

@zqxiaojin

After my test, iOS 5 doesn't include disk caching support.

@rs
Owner

How did you init NSURLCache?

@zqxiaojin

SDURLCache.m: 471
(storagePolicy == NSURLCacheStorageAllowedInMemoryOnly && ignoreMemoryOnlyStoragePolicy)

should modify to

(storagePolicy == NSURLCacheStorageAllowedInMemoryOnly || ignoreMemoryOnlyStoragePolicy)

@zqxiaojin

[[SDURLCache alloc] initWithMemoryCapacity:102410245 diskCapacity:1024102450 diskPath:[SDURLCache defaultCachePath]];

then, I check [SDURLCache defaultCachePath] directory in my iTouch 4 iOS 5.0.1 .
I found the directory is empty.

@rs
Owner

NSURLCache stores its cache data in {your application}/Library/Caches/com.yourdomain.Application/{specified cache path}

@zqxiaojin

in iPhone 5.1 simulator , "{your application}/Library/Caches/com.yourdomain.Application/{specified cache path} " exist a file named Cache.db.

this file is sqlite 3 format.

but after I open 10 big page, the database size is still 53kb.
I use mesasqlite to read it , the tables is empty.

@rs
Owner

Please pull my last commit, it should fix your issue.

@zqxiaojin

what a pity, the last commit doesn't work.

Would you tell me what devices you are using for test?

@rs
Owner

simulator and iphone 4s last ios version

@zqxiaojin

well , it works , thx~

Please sign in to comment.