├── .gitignore ├── LICENCE ├── README.md ├── SDCachedURLResponse.h ├── SDCachedURLResponse.m ├── SDURLCache.h ├── SDURLCache.m ├── SDURLCache.xcodeproj └── project.pbxproj ├── SDURLCacheTests.h ├── SDURLCacheTests.m └── SDURLCache_Prefix.pch /.gitignore: -------------------------------------------------------------------------------- 1 | # xcode noise 2 | *.mode1v3 3 | *.pbxuser 4 | *.perspective 5 | *.perspectivev3 6 | *.xcworkspace 7 | xcuserdata/ 8 | *.pyc 9 | *~.nib/ 10 | build/* 11 | 12 | # Textmate - if you build your xcode projects with it 13 | *.tm_build_errors 14 | 15 | # osx noise 16 | .DS_Store 17 | profile 18 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2010 Olivier Poitrey 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | On iPhone OS, Apple did remove on-disk cache support for unknown reason. Some will say it's to save 2 | flash-drive life, others will arg it's to save disk capacity. As it is explained in the 3 | NSURLCacheStoragePolicy, the NSURLCacheStorageAllowed constant is always treated as 4 | NSURLCacheStorageAllowedInMemoryOnly and there is no way to force it back, the code is certainly 5 | gone on this platform. For whatever reason Apple removed this feature, you may be interested by 6 | having on-disk HTTP request caching in your application. SDURLCache gives back this feature to this 7 | iPhone OS for you. 8 | 9 | To use it, you just have create an instance, replace the default shared NSURLCache with it and 10 | that's it, you instantly give on-disk HTTP request caching capability to your application. 11 | 12 | SDURLCache *urlCache = [[SDURLCache alloc] initWithMemoryCapacity:1024*1024 // 1MB mem cache 13 | diskCapacity:1024*1024*5 // 5MB disk cache 14 | diskPath:[SDURLCache defaultCachePath]]; 15 | [NSURLCache setSharedURLCache:urlCache]; 16 | [urlCache release]; 17 | 18 | To save flash drive, SDURLCache doesn't cache on disk responses if cache expiration delay is lower 19 | than 5 minutes by default. You can change this behavior by changing the `minCacheInterval` property. 20 | 21 | Cache eviction is done automatically when disk capacity is reached in a periodic maintenance 22 | thread. All disk write operations are done in a separated thread so they can't block the main run 23 | loop. 24 | 25 | To control the caching behavior, use the `NSURLRequest`'s `cachePolicy` property. If you want a 26 | response not to be cached on disk but still in memory, you can implement the `NSURLConnection` 27 | `connection:willCacheResponse:` delegate method and change the `NSURLCachedResponse` `storagePolicy` 28 | property to `NSURLCacheStorageAllowedInMemoryOnly`. See example below: 29 | 30 | - (NSCachedURLResponse *)connection:(NSURLConnection *)connection 31 | willCacheResponse:(NSCachedURLResponse *)cachedResponse 32 | { 33 | NSCachedURLResponse *memOnlyCachedResponse = 34 | [[NSCachedURLResponse alloc] initWithResponse:cachedResponse.response 35 | data:cachedResponse.data 36 | userInfo:cachedResponse.userInfo 37 | storagePolicy:NSURLCacheStorageAllowedInMemoryOnly]; 38 | return [memOnlyCachedResponse autorelease]; 39 | } 40 | -------------------------------------------------------------------------------- /SDCachedURLResponse.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDCachedURLResponse.h 3 | // SDURLCache 4 | // 5 | // Created by Olivier Poitrey on 12/05/12. 6 | // Copyright (c) 2012 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SDCachedURLResponse : NSObject 12 | 13 | @property (nonatomic, retain) NSCachedURLResponse *response; 14 | 15 | + (id)cachedURLResponseWithNSCachedURLResponse:(NSCachedURLResponse *)response; 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /SDCachedURLResponse.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDCachedURLResponse.m 3 | // SDURLCache 4 | // 5 | // Created by Olivier Poitrey on 12/05/12. 6 | // Copyright (c) 2012 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDCachedURLResponse.h" 10 | 11 | @implementation SDCachedURLResponse 12 | 13 | @synthesize response; 14 | 15 | + (id)cachedURLResponseWithNSCachedURLResponse:(NSCachedURLResponse *)response 16 | { 17 | SDCachedURLResponse *wrappedResponse = [[SDCachedURLResponse alloc] init]; 18 | wrappedResponse.response = response; 19 | return [wrappedResponse autorelease]; 20 | } 21 | 22 | #pragma mark NSCopying Methods 23 | 24 | - (id)copyWithZone:(NSZone *)zone 25 | { 26 | SDCachedURLResponse *newResponse = [[[self class] allocWithZone:zone] init]; 27 | 28 | if (newResponse) 29 | { 30 | newResponse.response = [[self.response copyWithZone:zone] autorelease]; 31 | } 32 | 33 | return newResponse; 34 | } 35 | 36 | #pragma mark NSCoding Methods 37 | - (void)encodeWithCoder:(NSCoder *)coder 38 | { 39 | // force write the data of underlying cached response 40 | [coder encodeDataObject:self.response.data]; 41 | [coder encodeObject:self.response.response forKey:@"response"]; 42 | [coder encodeObject:self.response.userInfo forKey:@"userInfo"]; 43 | [coder encodeInt:self.response.storagePolicy forKey:@"storagePolicy"]; 44 | } 45 | 46 | - (id)initWithCoder:(NSCoder *)coder 47 | { 48 | if ((self = [super init])) 49 | { 50 | self.response = [[[NSCachedURLResponse alloc] initWithResponse:[coder decodeObjectForKey:@"response"] 51 | data:[coder decodeDataObject] 52 | userInfo:[coder decodeObjectForKey:@"userInfo"] 53 | storagePolicy:[coder decodeIntForKey:@"storagePolicy"]] autorelease]; 54 | } 55 | 56 | return self; 57 | } 58 | 59 | - (void)dealloc 60 | { 61 | [response release], response = nil; 62 | [super dealloc]; 63 | } 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /SDURLCache.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDURLCache.h 3 | // SDURLCache 4 | // 5 | // Created by Olivier Poitrey on 15/03/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SDURLCache : NSURLCache 12 | { 13 | @private 14 | NSString *diskCachePath; 15 | NSMutableDictionary *diskCacheInfo; 16 | BOOL diskCacheInfoDirty, ignoreMemoryOnlyStoragePolicy, disabled, _enableForIOS5AndUp; 17 | NSUInteger diskCacheUsage; 18 | NSTimeInterval minCacheInterval; 19 | NSOperationQueue *ioQueue; 20 | NSTimer *periodicMaintenanceTimer; 21 | NSOperation *periodicMaintenanceOperation; 22 | } 23 | 24 | /* 25 | * Defines the minimum number of seconds between now and the expiration time of a cacheable response 26 | * in order for the response to be cached on disk. This prevent from spending time and storage capacity 27 | * for an entry which will certainly expire before behing read back from disk cache (memory cache is 28 | * best suited for short term cache). The default value is set to 5 minutes (300 seconds). 29 | */ 30 | @property (nonatomic, assign) NSTimeInterval minCacheInterval; 31 | 32 | /* 33 | * Allow the responses that have a storage policy of NSURLCacheStorageAllowedInMemoryOnly to be cached 34 | * on the disk anyway. 35 | * 36 | * This is mainly a workaround against cache policies generated by the UIWebViews: starting from iOS 4.2, 37 | * it always has a cache policy of NSURLCacheStorageAllowedInMemoryOnly. 38 | * The default value is YES 39 | */ 40 | @property (nonatomic, assign) BOOL ignoreMemoryOnlyStoragePolicy; 41 | 42 | /* 43 | * Returns a default cache director path to be used at cache initialization. The generated path directory 44 | * will be located in the application's cache directory and thus won't be synced by iTunes. 45 | */ 46 | + (NSString *)defaultCachePath; 47 | 48 | /* 49 | * yosit: It turns that although ios > 5 has a disk cache it doesn't behave in a predicatable way 50 | * the added enableForIOS5AndUp will enable SDURLCache to function like it does on all version of IOS 51 | */ 52 | 53 | - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity 54 | diskCapacity:(NSUInteger)diskCapacity 55 | diskPath:(NSString *)path 56 | enableForIOS5AndUp:(BOOL)enableForIOS5AndUp; 57 | 58 | /* 59 | * Checks if the provided URL exists in cache. 60 | */ 61 | - (BOOL)isCached:(NSURL *)url; 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /SDURLCache.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDURLCache.m 3 | // SDURLCache 4 | // 5 | // Created by Olivier Poitrey on 15/03/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDURLCache.h" 10 | #import "SDCachedURLResponse.h" 11 | #import 12 | #import 13 | 14 | // The removal of the NSCachedURLResponse category means that NSKeyedArchiver 15 | // will throw an EXC_BAD_ACCESS when attempting to load NSCachedURLResponse 16 | // data. 17 | // This means that this change requires a cache refresh, and a new cache key 18 | // namespace that will prevent this from happening. 19 | // Old cache keys will eventually be evicted from the system as new keys are 20 | // populated. 21 | static NSString *const kSDURLCacheVersion = @"V2"; 22 | 23 | static NSTimeInterval const kSDURLCacheInfoDefaultMinCacheInterval = 5 * 60; // 5 minute 24 | static NSString *const kSDURLCacheInfoFileName = @"cacheInfo.plist"; 25 | static NSString *const kSDURLCacheInfoDiskUsageKey = @"diskUsage"; 26 | static NSString *const kSDURLCacheInfoAccessesKey = @"accesses"; 27 | static NSString *const kSDURLCacheInfoSizesKey = @"sizes"; 28 | static float const kSDURLCacheLastModFraction = 0.1f; // 10% since Last-Modified suggested by RFC2616 section 13.2.4 29 | static float const kSDURLCacheDefault = 3600; // Default cache expiration delay if none defined (1 hour) 30 | 31 | static NSDateFormatter* CreateDateFormatter(NSString *format) 32 | { 33 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 34 | NSLocale *locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 35 | 36 | [dateFormatter setLocale:locale]; 37 | [dateFormatter setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; 38 | [dateFormatter setDateFormat:format]; 39 | [locale release]; 40 | 41 | return [dateFormatter autorelease]; 42 | } 43 | 44 | @interface SDURLCache () 45 | @property (nonatomic, retain) NSString *diskCachePath; 46 | @property (nonatomic, readonly) NSMutableDictionary *diskCacheInfo; 47 | @property (nonatomic, retain) NSOperationQueue *ioQueue; 48 | @property (retain) NSOperation *periodicMaintenanceOperation; 49 | - (void)periodicMaintenance; 50 | @end 51 | 52 | @implementation SDURLCache 53 | 54 | @synthesize diskCachePath, minCacheInterval, ioQueue, periodicMaintenanceOperation, ignoreMemoryOnlyStoragePolicy; 55 | @dynamic diskCacheInfo; 56 | 57 | #pragma mark SDURLCache (tools) 58 | 59 | + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request 60 | { 61 | NSString *string = request.URL.absoluteString; 62 | NSRange hash = [string rangeOfString:@"#"]; 63 | if (hash.location == NSNotFound) 64 | return request; 65 | 66 | NSMutableURLRequest *copy = [[request mutableCopy] autorelease]; 67 | copy.URL = [NSURL URLWithString:[string substringToIndex:hash.location]]; 68 | return copy; 69 | } 70 | 71 | + (NSString *)cacheKeyForURL:(NSURL *)url 72 | { 73 | const char *str = [url.absoluteString UTF8String]; 74 | unsigned char r[CC_MD5_DIGEST_LENGTH]; 75 | CC_MD5(str, strlen(str), r); 76 | return [NSString stringWithFormat:@"%@_%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x", 77 | 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]]; 78 | } 79 | 80 | /* 81 | * Parse HTTP Date: http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.3.1 82 | */ 83 | + (NSDate *)dateFromHttpDateString:(NSString *)httpDate 84 | { 85 | static NSDateFormatter *RFC1123DateFormatter; 86 | static NSDateFormatter *ANSICDateFormatter; 87 | static NSDateFormatter *RFC850DateFormatter; 88 | NSDate *date = nil; 89 | 90 | @synchronized(self) // NSDateFormatter isn't thread safe 91 | { 92 | // RFC 1123 date format - Sun, 06 Nov 1994 08:49:37 GMT 93 | if (!RFC1123DateFormatter) RFC1123DateFormatter = [CreateDateFormatter(@"EEE, dd MMM yyyy HH:mm:ss z") retain]; 94 | date = [RFC1123DateFormatter dateFromString:httpDate]; 95 | if (!date) 96 | { 97 | // ANSI C date format - Sun Nov 6 08:49:37 1994 98 | if (!ANSICDateFormatter) ANSICDateFormatter = [CreateDateFormatter(@"EEE MMM d HH:mm:ss yyyy") retain]; 99 | date = [ANSICDateFormatter dateFromString:httpDate]; 100 | if (!date) 101 | { 102 | // RFC 850 date format - Sunday, 06-Nov-94 08:49:37 GMT 103 | if (!RFC850DateFormatter) RFC850DateFormatter = [CreateDateFormatter(@"EEEE, dd-MMM-yy HH:mm:ss z") retain]; 104 | date = [RFC850DateFormatter dateFromString:httpDate]; 105 | } 106 | } 107 | } 108 | 109 | return date; 110 | } 111 | 112 | /* 113 | * This method tries to determine the expiration date based on a response headers dictionary. 114 | */ 115 | + (NSDate *)expirationDateFromHeaders:(NSDictionary *)headers withStatusCode:(NSInteger)status 116 | { 117 | if (status != 200 && status != 203 && status != 300 && status != 301 && status != 302 && status != 307 && status != 410) 118 | { 119 | // Uncacheable response status code 120 | return nil; 121 | } 122 | 123 | // Check Pragma: no-cache 124 | NSString *pragma = [headers objectForKey:@"Pragma"]; 125 | if (pragma && [pragma isEqualToString:@"no-cache"]) 126 | { 127 | // Uncacheable response 128 | return nil; 129 | } 130 | 131 | // Define "now" based on the request 132 | NSString *date = [headers objectForKey:@"Date"]; 133 | NSDate *now; 134 | if (date) 135 | { 136 | now = [SDURLCache dateFromHttpDateString:date]; 137 | } 138 | else 139 | { 140 | // If no Date: header, define now from local clock 141 | now = [NSDate date]; 142 | } 143 | 144 | // Look at info from the Cache-Control: max-age=n header 145 | NSString *cacheControl = [[headers objectForKey:@"Cache-Control"] lowercaseString]; 146 | if (cacheControl) 147 | { 148 | NSRange foundRange = [cacheControl rangeOfString:@"no-store"]; 149 | if (foundRange.length > 0) 150 | { 151 | // Can't be cached 152 | return nil; 153 | } 154 | 155 | NSInteger maxAge; 156 | foundRange = [cacheControl rangeOfString:@"max-age"]; 157 | if (foundRange.length > 0) 158 | { 159 | NSScanner *cacheControlScanner = [NSScanner scannerWithString:cacheControl]; 160 | [cacheControlScanner setScanLocation:foundRange.location + foundRange.length]; 161 | [cacheControlScanner scanString:@"=" intoString:nil]; 162 | if ([cacheControlScanner scanInteger:&maxAge]) 163 | { 164 | if (maxAge > 0) 165 | { 166 | return [[[NSDate alloc] initWithTimeInterval:maxAge sinceDate:now] autorelease]; 167 | } 168 | else 169 | { 170 | return nil; 171 | } 172 | } 173 | } 174 | } 175 | 176 | // If not Cache-Control found, look at the Expires header 177 | NSString *expires = [headers objectForKey:@"Expires"]; 178 | if (expires) 179 | { 180 | NSTimeInterval expirationInterval = 0; 181 | NSDate *expirationDate = [SDURLCache dateFromHttpDateString:expires]; 182 | if (expirationDate) 183 | { 184 | expirationInterval = [expirationDate timeIntervalSinceDate:now]; 185 | } 186 | if (expirationInterval > 0) 187 | { 188 | // Convert remote expiration date to local expiration date 189 | return [NSDate dateWithTimeIntervalSinceNow:expirationInterval]; 190 | } 191 | else 192 | { 193 | // If the Expires header can't be parsed or is expired, do not cache 194 | return nil; 195 | } 196 | } 197 | 198 | if (status == 302 || status == 307) 199 | { 200 | // If not explict cache control defined, do not cache those status 201 | return nil; 202 | } 203 | 204 | // If no cache control defined, try some heristic to determine an expiration date 205 | NSString *lastModified = [headers objectForKey:@"Last-Modified"]; 206 | if (lastModified) 207 | { 208 | NSTimeInterval age = 0; 209 | NSDate *lastModifiedDate = [SDURLCache dateFromHttpDateString:lastModified]; 210 | if (lastModifiedDate) 211 | { 212 | // Define the age of the document by comparing the Date header with the Last-Modified header 213 | age = [now timeIntervalSinceDate:lastModifiedDate]; 214 | } 215 | if (age > 0) 216 | { 217 | return [NSDate dateWithTimeIntervalSinceNow:(age * kSDURLCacheLastModFraction)]; 218 | } 219 | else 220 | { 221 | return nil; 222 | } 223 | } 224 | 225 | // If nothing permitted to define the cache expiration delay nor to restrict its cacheability, use a default cache expiration delay 226 | return [[[NSDate alloc] initWithTimeInterval:kSDURLCacheDefault sinceDate:now] autorelease]; 227 | 228 | } 229 | 230 | #pragma mark SDURLCache (private) 231 | 232 | - (NSMutableDictionary *)diskCacheInfo 233 | { 234 | if (!diskCacheInfo) 235 | { 236 | @synchronized(self) 237 | { 238 | if (!diskCacheInfo) // Check again, maybe another thread created it while waiting for the mutex 239 | { 240 | diskCacheInfo = [[NSMutableDictionary alloc] initWithContentsOfFile:[diskCachePath stringByAppendingPathComponent:kSDURLCacheInfoFileName]]; 241 | if (!diskCacheInfo) 242 | { 243 | diskCacheInfo = [[NSMutableDictionary alloc] initWithObjectsAndKeys: 244 | [NSNumber numberWithUnsignedInt:0], kSDURLCacheInfoDiskUsageKey, 245 | [NSMutableDictionary dictionary], kSDURLCacheInfoAccessesKey, 246 | [NSMutableDictionary dictionary], kSDURLCacheInfoSizesKey, 247 | nil]; 248 | } 249 | diskCacheInfoDirty = NO; 250 | 251 | diskCacheUsage = [[diskCacheInfo objectForKey:kSDURLCacheInfoDiskUsageKey] unsignedIntValue]; 252 | 253 | periodicMaintenanceTimer = [[NSTimer scheduledTimerWithTimeInterval:5 254 | target:self 255 | selector:@selector(periodicMaintenance) 256 | userInfo:nil 257 | repeats:YES] retain]; 258 | } 259 | } 260 | } 261 | 262 | return diskCacheInfo; 263 | } 264 | 265 | - (void)createDiskCachePath 266 | { 267 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 268 | if (![fileManager fileExistsAtPath:diskCachePath]) 269 | { 270 | [fileManager createDirectoryAtPath:diskCachePath 271 | withIntermediateDirectories:YES 272 | attributes:nil 273 | error:NULL]; 274 | } 275 | [fileManager release]; 276 | } 277 | 278 | - (void)saveCacheInfo 279 | { 280 | [self createDiskCachePath]; 281 | @synchronized(self.diskCacheInfo) 282 | { 283 | NSData *data = [NSPropertyListSerialization dataFromPropertyList:self.diskCacheInfo format:NSPropertyListBinaryFormat_v1_0 errorDescription:NULL]; 284 | if (data) 285 | { 286 | [data writeToFile:[diskCachePath stringByAppendingPathComponent:kSDURLCacheInfoFileName] atomically:YES]; 287 | } 288 | 289 | diskCacheInfoDirty = NO; 290 | } 291 | } 292 | 293 | - (void)removeCachedResponseForCachedKeys:(NSArray *)cacheKeys 294 | { 295 | NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; 296 | 297 | NSEnumerator *enumerator = [cacheKeys objectEnumerator]; 298 | NSString *cacheKey; 299 | 300 | @synchronized(self.diskCacheInfo) 301 | { 302 | NSMutableDictionary *accesses = [self.diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey]; 303 | NSMutableDictionary *sizes = [self.diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey]; 304 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 305 | 306 | while ((cacheKey = [enumerator nextObject])) 307 | { 308 | NSUInteger cacheItemSize = [[sizes objectForKey:cacheKey] unsignedIntegerValue]; 309 | [accesses removeObjectForKey:cacheKey]; 310 | [sizes removeObjectForKey:cacheKey]; 311 | [fileManager removeItemAtPath:[diskCachePath stringByAppendingPathComponent:cacheKey] error:NULL]; 312 | 313 | diskCacheUsage -= cacheItemSize; 314 | [self.diskCacheInfo setObject:[NSNumber numberWithUnsignedInteger:diskCacheUsage] forKey:kSDURLCacheInfoDiskUsageKey]; 315 | } 316 | [fileManager release]; 317 | } 318 | 319 | [pool drain]; 320 | } 321 | 322 | - (void)balanceDiskUsage 323 | { 324 | if (diskCacheUsage < self.diskCapacity) 325 | { 326 | // Already done 327 | return; 328 | } 329 | 330 | NSMutableArray *keysToRemove = [NSMutableArray array]; 331 | 332 | @synchronized(self.diskCacheInfo) 333 | { 334 | // Apply LRU cache eviction algorithm while disk usage outreach capacity 335 | NSDictionary *sizes = [self.diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey]; 336 | 337 | NSInteger capacityToSave = diskCacheUsage - self.diskCapacity; 338 | NSArray *sortedKeys = [[self.diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey] keysSortedByValueUsingSelector:@selector(compare:)]; 339 | NSEnumerator *enumerator = [sortedKeys objectEnumerator]; 340 | NSString *cacheKey; 341 | 342 | while (capacityToSave > 0 && (cacheKey = [enumerator nextObject])) 343 | { 344 | [keysToRemove addObject:cacheKey]; 345 | capacityToSave -= [(NSNumber *)[sizes objectForKey:cacheKey] unsignedIntegerValue]; 346 | } 347 | } 348 | 349 | [self removeCachedResponseForCachedKeys:keysToRemove]; 350 | [self saveCacheInfo]; 351 | } 352 | 353 | 354 | - (void)storeToDisk:(NSDictionary *)context 355 | { 356 | NSURLRequest *request = [context objectForKey:@"request"]; 357 | // use wrapper to ensure we save appropriate fields 358 | SDCachedURLResponse *cachedResponse = [SDCachedURLResponse cachedURLResponseWithNSCachedURLResponse:[context objectForKey:@"cachedResponse"]]; 359 | 360 | NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL]; 361 | NSString *cacheFilePath = [diskCachePath stringByAppendingPathComponent:cacheKey]; 362 | 363 | [self createDiskCachePath]; 364 | 365 | // Archive the cached response on disk 366 | if (![NSKeyedArchiver archiveRootObject:cachedResponse toFile:cacheFilePath]) 367 | { 368 | // Caching failed for some reason 369 | return; 370 | } 371 | 372 | // Update disk usage info 373 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 374 | NSNumber *cacheItemSize = [[fileManager attributesOfItemAtPath:cacheFilePath error:NULL] objectForKey:NSFileSize]; 375 | [fileManager release]; 376 | @synchronized(self.diskCacheInfo) 377 | { 378 | diskCacheUsage += [cacheItemSize unsignedIntegerValue]; 379 | [self.diskCacheInfo setObject:[NSNumber numberWithUnsignedInteger:diskCacheUsage] forKey:kSDURLCacheInfoDiskUsageKey]; 380 | 381 | 382 | // Update cache info for the stored item 383 | [(NSMutableDictionary *)[self.diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey] setObject:[NSDate date] forKey:cacheKey]; 384 | [(NSMutableDictionary *)[self.diskCacheInfo objectForKey:kSDURLCacheInfoSizesKey] setObject:cacheItemSize forKey:cacheKey]; 385 | } 386 | 387 | [self saveCacheInfo]; 388 | } 389 | 390 | - (void)periodicMaintenance 391 | { 392 | // If another maintenance operation is already sceduled, cancel it so this new operation will be executed after other 393 | // operations of the queue, so we can group more work together 394 | [periodicMaintenanceOperation cancel]; 395 | self.periodicMaintenanceOperation = nil; 396 | 397 | // If disk usage exceeds capacity, run the cache eviction operation and if cacheInfo dictionary is dirty, save it in an operation 398 | if (diskCacheUsage > self.diskCapacity) 399 | { 400 | NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(balanceDiskUsage) object:nil]; 401 | self.periodicMaintenanceOperation = operation; 402 | [ioQueue addOperation:periodicMaintenanceOperation]; 403 | [operation release]; 404 | } 405 | else if (diskCacheInfoDirty) 406 | { 407 | NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(saveCacheInfo) object:nil]; 408 | self.periodicMaintenanceOperation = operation; 409 | [ioQueue addOperation:periodicMaintenanceOperation]; 410 | [operation release]; 411 | } 412 | } 413 | 414 | #pragma mark SDURLCache 415 | 416 | + (NSString *)defaultCachePath 417 | { 418 | NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES); 419 | return [[paths objectAtIndex:0] stringByAppendingPathComponent:@"SDURLCache"]; 420 | } 421 | 422 | #pragma mark NSURLCache 423 | 424 | - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity diskCapacity:(NSUInteger)diskCapacity diskPath:(NSString *)path 425 | { 426 | // iOS 5 implements disk caching. SDURLCache then disables itself at runtime if the current device OS 427 | // version is 5 or greater 428 | NSArray *version = [[UIDevice currentDevice].systemVersion componentsSeparatedByString:@"."]; 429 | disabled = [[version objectAtIndex:0] intValue] >= 5 && !_enableForIOS5AndUp; 430 | 431 | if (disabled) 432 | { 433 | // iOS NSURLCache doesn't accept a full path but a single path component 434 | path = [path lastPathComponent]; 435 | } 436 | 437 | if ((self = [super initWithMemoryCapacity:memoryCapacity diskCapacity:diskCapacity diskPath:path]) && !disabled) 438 | { 439 | self.minCacheInterval = kSDURLCacheInfoDefaultMinCacheInterval; 440 | self.diskCachePath = path; 441 | 442 | // Init the operation queue 443 | NSOperationQueue *queue = [[NSOperationQueue alloc] init]; 444 | self.ioQueue = queue; 445 | [queue release]; 446 | 447 | ioQueue.maxConcurrentOperationCount = 1; // used to streamline operations in a separate thread 448 | self.ignoreMemoryOnlyStoragePolicy = YES; 449 | } 450 | 451 | return self; 452 | } 453 | 454 | - (id)initWithMemoryCapacity:(NSUInteger)memoryCapacity 455 | diskCapacity:(NSUInteger)diskCapacity 456 | diskPath:(NSString *)path 457 | enableForIOS5AndUp:(BOOL)enableForIOS5AndUp { 458 | 459 | _enableForIOS5AndUp = enableForIOS5AndUp; 460 | return [self initWithMemoryCapacity:memoryCapacity 461 | diskCapacity:diskCapacity 462 | diskPath:path]; 463 | } 464 | 465 | 466 | - (void)storeCachedResponse:(NSCachedURLResponse *)cachedResponse forRequest:(NSURLRequest *)request 467 | { 468 | if (disabled) 469 | { 470 | [super storeCachedResponse:cachedResponse forRequest:request]; 471 | return; 472 | } 473 | 474 | request = [SDURLCache canonicalRequestForRequest:request]; 475 | 476 | if (request.cachePolicy == NSURLRequestReloadIgnoringLocalCacheData 477 | || request.cachePolicy == NSURLRequestReloadIgnoringLocalAndRemoteCacheData 478 | || request.cachePolicy == NSURLRequestReloadIgnoringCacheData) 479 | { 480 | // When cache is ignored for read, it's a good idea not to store the result as well as this option 481 | // have big chance to be used every times in the future for the same request. 482 | // NOTE: This is a change regarding default URLCache behavior 483 | return; 484 | } 485 | 486 | [super storeCachedResponse:cachedResponse forRequest:request]; 487 | 488 | NSURLCacheStoragePolicy storagePolicy = cachedResponse.storagePolicy; 489 | if ((storagePolicy == NSURLCacheStorageAllowed || (storagePolicy == NSURLCacheStorageAllowedInMemoryOnly && ignoreMemoryOnlyStoragePolicy)) 490 | && [cachedResponse.response isKindOfClass:[NSHTTPURLResponse self]] 491 | && cachedResponse.data.length < self.diskCapacity) 492 | { 493 | NSDictionary *headers = [(NSHTTPURLResponse *)cachedResponse.response allHeaderFields]; 494 | // RFC 2616 section 13.3.4 says clients MUST use Etag in any cache-conditional request if provided by server 495 | if (![headers objectForKey:@"Etag"]) 496 | { 497 | NSDate *expirationDate = [SDURLCache expirationDateFromHeaders:headers 498 | withStatusCode:((NSHTTPURLResponse *)cachedResponse.response).statusCode]; 499 | if (!expirationDate || [expirationDate timeIntervalSinceNow] - minCacheInterval <= 0) 500 | { 501 | // This response is not cacheable, headers said 502 | return; 503 | } 504 | } 505 | 506 | NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self 507 | selector:@selector(storeToDisk:) 508 | object:[NSDictionary dictionaryWithObjectsAndKeys: 509 | cachedResponse, @"cachedResponse", 510 | request, @"request", 511 | nil]]; 512 | [ioQueue addOperation:operation]; 513 | [operation release]; 514 | } 515 | } 516 | 517 | - (NSCachedURLResponse *)cachedResponseForRequest:(NSURLRequest *)request 518 | { 519 | if (disabled) return [super cachedResponseForRequest:request]; 520 | 521 | request = [SDURLCache canonicalRequestForRequest:request]; 522 | 523 | NSCachedURLResponse *memoryResponse = [super cachedResponseForRequest:request]; 524 | if (memoryResponse) 525 | { 526 | return memoryResponse; 527 | } 528 | 529 | NSString *cacheKey = [SDURLCache cacheKeyForURL:request.URL]; 530 | 531 | // NOTE: We don't handle expiration here as even staled cache data is necessary for NSURLConnection to handle cache revalidation. 532 | // Staled cache data is also needed for cachePolicies which force the use of the cache. 533 | @synchronized(self.diskCacheInfo) 534 | { 535 | NSMutableDictionary *accesses = [self.diskCacheInfo objectForKey:kSDURLCacheInfoAccessesKey]; 536 | if ([accesses objectForKey:cacheKey]) // OPTI: Check for cache-hit in a in-memory dictionary before hitting the file system 537 | { 538 | // load wrapper 539 | NSCachedURLResponse *diskResponse = nil; 540 | NSString *cacheFile = [diskCachePath stringByAppendingPathComponent:cacheKey]; 541 | @try 542 | { 543 | diskResponse = [NSKeyedUnarchiver unarchiveObjectWithFile:cacheFile]; 544 | } 545 | @catch (NSException *exception) 546 | { 547 | NSLog(@"Unable to load disk response from cache at \"%@\". Ignoring: %@", cacheFile, exception); 548 | } 549 | 550 | if (diskResponse) 551 | { 552 | // OPTI: Log the entry last access time for LRU cache eviction algorithm but don't save the dictionary 553 | // on disk now in order to save IO and time 554 | [accesses setObject:[NSDate date] forKey:cacheKey]; 555 | diskCacheInfoDirty = YES; 556 | 557 | // OPTI: Store the response to memory cache for potential future requests 558 | [super storeCachedResponse:diskResponse forRequest:request]; 559 | 560 | // SRK: Work around an interesting retainCount bug in CFNetwork on iOS << 3.2. 561 | if (kCFCoreFoundationVersionNumber < kCFCoreFoundationVersionNumber_iPhoneOS_3_2) 562 | { 563 | diskResponse = [super cachedResponseForRequest:request]; 564 | } 565 | 566 | if (diskResponse) 567 | { 568 | return diskResponse; 569 | } 570 | } 571 | } 572 | } 573 | 574 | return nil; 575 | } 576 | 577 | - (NSUInteger)currentDiskUsage 578 | { 579 | if (disabled) return [super currentDiskUsage]; 580 | 581 | if (!diskCacheInfo) 582 | { 583 | [self diskCacheInfo]; 584 | } 585 | return diskCacheUsage; 586 | } 587 | 588 | - (void)removeCachedResponseForRequest:(NSURLRequest *)request 589 | { 590 | if (disabled) 591 | { 592 | [super removeCachedResponseForRequest:request]; 593 | return; 594 | } 595 | 596 | request = [SDURLCache canonicalRequestForRequest:request]; 597 | 598 | [super removeCachedResponseForRequest:request]; 599 | [self removeCachedResponseForCachedKeys:[NSArray arrayWithObject:[SDURLCache cacheKeyForURL:request.URL]]]; 600 | [self saveCacheInfo]; 601 | } 602 | 603 | - (void)removeAllCachedResponses 604 | { 605 | [super removeAllCachedResponses]; 606 | 607 | if (disabled) return; 608 | NSFileManager *fileManager = [[NSFileManager alloc] init]; 609 | [fileManager removeItemAtPath:diskCachePath error:NULL]; 610 | [fileManager release]; 611 | 612 | @synchronized(self) 613 | { 614 | [diskCacheInfo release], diskCacheInfo = nil; 615 | } 616 | } 617 | 618 | - (BOOL)isCached:(NSURL *)url 619 | { 620 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 621 | request = [SDURLCache canonicalRequestForRequest:request]; 622 | 623 | if ([super cachedResponseForRequest:request]) 624 | { 625 | return YES; 626 | } 627 | 628 | if (disabled) return NO; 629 | 630 | NSString *cacheKey = [SDURLCache cacheKeyForURL:url]; 631 | NSString *cacheFile = [diskCachePath stringByAppendingPathComponent:cacheKey]; 632 | NSFileManager *manager = [[NSFileManager alloc] init]; 633 | 634 | BOOL exists = [manager fileExistsAtPath:cacheFile]; 635 | [manager release]; 636 | 637 | if (exists) 638 | { 639 | return YES; 640 | } 641 | return NO; 642 | } 643 | 644 | #pragma mark NSObject 645 | 646 | - (void)dealloc 647 | { 648 | if (disabled) 649 | { 650 | [super dealloc]; 651 | return; 652 | } 653 | 654 | [periodicMaintenanceTimer invalidate]; 655 | [periodicMaintenanceTimer release], periodicMaintenanceTimer = nil; 656 | [periodicMaintenanceOperation release], periodicMaintenanceOperation = nil; 657 | [diskCachePath release], diskCachePath = nil; 658 | [diskCacheInfo release], diskCacheInfo = nil; 659 | [ioQueue release], ioQueue = nil; 660 | [super dealloc]; 661 | } 662 | 663 | 664 | @end 665 | -------------------------------------------------------------------------------- /SDURLCache.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 5318A9AB155DD7B500AB6767 /* SDCachedURLResponse.h in Headers */ = {isa = PBXBuildFile; fileRef = 5318A9A9155DD7B500AB6767 /* SDCachedURLResponse.h */; }; 11 | 5318A9AC155DD7B500AB6767 /* SDCachedURLResponse.m in Sources */ = {isa = PBXBuildFile; fileRef = 5318A9AA155DD7B500AB6767 /* SDCachedURLResponse.m */; }; 12 | 53F557F4114EA63600A3DA4B /* SDURLCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 53F557F2114EA63600A3DA4B /* SDURLCache.h */; }; 13 | 53F557F5114EA63600A3DA4B /* SDURLCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F557F3114EA63600A3DA4B /* SDURLCache.m */; }; 14 | 53F5592C114F1ED800A3DA4B /* SDURLCacheTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */; }; 15 | 53F559B7114F2AA600A3DA4B /* libSDURLCache.a in Frameworks */ = {isa = PBXBuildFile; fileRef = D2AAC07E0554694100DB518D /* libSDURLCache.a */; }; 16 | AA747D9F0F9514B9006C5449 /* SDURLCache_Prefix.pch in Headers */ = {isa = PBXBuildFile; fileRef = AA747D9E0F9514B9006C5449 /* SDURLCache_Prefix.pch */; }; 17 | /* End PBXBuildFile section */ 18 | 19 | /* Begin PBXContainerItemProxy section */ 20 | 53F559C2114F2ABF00A3DA4B /* PBXContainerItemProxy */ = { 21 | isa = PBXContainerItemProxy; 22 | containerPortal = 0867D690FE84028FC02AAC07 /* Project object */; 23 | proxyType = 1; 24 | remoteGlobalIDString = D2AAC07D0554694100DB518D; 25 | remoteInfo = SDURLCache; 26 | }; 27 | /* End PBXContainerItemProxy section */ 28 | 29 | /* Begin PBXFileReference section */ 30 | 5318A9A9155DD7B500AB6767 /* SDCachedURLResponse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDCachedURLResponse.h; sourceTree = ""; }; 31 | 5318A9AA155DD7B500AB6767 /* SDCachedURLResponse.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDCachedURLResponse.m; sourceTree = ""; }; 32 | 53F557F2114EA63600A3DA4B /* SDURLCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCache.h; sourceTree = ""; }; 33 | 53F557F3114EA63600A3DA4B /* SDURLCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDURLCache.m; sourceTree = ""; }; 34 | 53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = SDURLCacheTestBundle.octest; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 53F5592A114F1ED800A3DA4B /* SDURLCacheTests.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCacheTests.h; sourceTree = ""; }; 36 | 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDURLCacheTests.m; sourceTree = ""; }; 37 | AA747D9E0F9514B9006C5449 /* SDURLCache_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDURLCache_Prefix.pch; sourceTree = SOURCE_ROOT; }; 38 | D2AAC07E0554694100DB518D /* libSDURLCache.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libSDURLCache.a; sourceTree = BUILT_PRODUCTS_DIR; }; 39 | /* End PBXFileReference section */ 40 | 41 | /* Begin PBXFrameworksBuildPhase section */ 42 | 53F55919114F1D5E00A3DA4B /* Frameworks */ = { 43 | isa = PBXFrameworksBuildPhase; 44 | buildActionMask = 2147483647; 45 | files = ( 46 | 53F559B7114F2AA600A3DA4B /* libSDURLCache.a in Frameworks */, 47 | ); 48 | runOnlyForDeploymentPostprocessing = 0; 49 | }; 50 | D2AAC07C0554694100DB518D /* Frameworks */ = { 51 | isa = PBXFrameworksBuildPhase; 52 | buildActionMask = 2147483647; 53 | files = ( 54 | ); 55 | runOnlyForDeploymentPostprocessing = 0; 56 | }; 57 | /* End PBXFrameworksBuildPhase section */ 58 | 59 | /* Begin PBXGroup section */ 60 | 034768DFFF38A50411DB9C8B /* Products */ = { 61 | isa = PBXGroup; 62 | children = ( 63 | D2AAC07E0554694100DB518D /* libSDURLCache.a */, 64 | 53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */, 65 | ); 66 | name = Products; 67 | sourceTree = ""; 68 | }; 69 | 0867D691FE84028FC02AAC07 /* SDURLCache */ = { 70 | isa = PBXGroup; 71 | children = ( 72 | 08FB77AEFE84172EC02AAC07 /* Classes */, 73 | 32C88DFF0371C24200C91783 /* Other Sources */, 74 | 53F55929114F1EA300A3DA4B /* Tests */, 75 | 0867D69AFE84028FC02AAC07 /* Frameworks */, 76 | 034768DFFF38A50411DB9C8B /* Products */, 77 | ); 78 | name = SDURLCache; 79 | sourceTree = ""; 80 | }; 81 | 0867D69AFE84028FC02AAC07 /* Frameworks */ = { 82 | isa = PBXGroup; 83 | children = ( 84 | ); 85 | name = Frameworks; 86 | sourceTree = ""; 87 | }; 88 | 08FB77AEFE84172EC02AAC07 /* Classes */ = { 89 | isa = PBXGroup; 90 | children = ( 91 | 53F557F2114EA63600A3DA4B /* SDURLCache.h */, 92 | 53F557F3114EA63600A3DA4B /* SDURLCache.m */, 93 | 5318A9A9155DD7B500AB6767 /* SDCachedURLResponse.h */, 94 | 5318A9AA155DD7B500AB6767 /* SDCachedURLResponse.m */, 95 | ); 96 | name = Classes; 97 | sourceTree = ""; 98 | }; 99 | 32C88DFF0371C24200C91783 /* Other Sources */ = { 100 | isa = PBXGroup; 101 | children = ( 102 | AA747D9E0F9514B9006C5449 /* SDURLCache_Prefix.pch */, 103 | ); 104 | name = "Other Sources"; 105 | sourceTree = ""; 106 | }; 107 | 53F55929114F1EA300A3DA4B /* Tests */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 53F5592A114F1ED800A3DA4B /* SDURLCacheTests.h */, 111 | 53F5592B114F1ED800A3DA4B /* SDURLCacheTests.m */, 112 | ); 113 | name = Tests; 114 | sourceTree = ""; 115 | }; 116 | /* End PBXGroup section */ 117 | 118 | /* Begin PBXHeadersBuildPhase section */ 119 | D2AAC07A0554694100DB518D /* Headers */ = { 120 | isa = PBXHeadersBuildPhase; 121 | buildActionMask = 2147483647; 122 | files = ( 123 | AA747D9F0F9514B9006C5449 /* SDURLCache_Prefix.pch in Headers */, 124 | 53F557F4114EA63600A3DA4B /* SDURLCache.h in Headers */, 125 | 5318A9AB155DD7B500AB6767 /* SDCachedURLResponse.h in Headers */, 126 | ); 127 | runOnlyForDeploymentPostprocessing = 0; 128 | }; 129 | /* End PBXHeadersBuildPhase section */ 130 | 131 | /* Begin PBXNativeTarget section */ 132 | 53F5591B114F1D5E00A3DA4B /* SDURLCacheTestBundle */ = { 133 | isa = PBXNativeTarget; 134 | buildConfigurationList = 53F55920114F1D5F00A3DA4B /* Build configuration list for PBXNativeTarget "SDURLCacheTestBundle" */; 135 | buildPhases = ( 136 | 53F55917114F1D5E00A3DA4B /* Resources */, 137 | 53F55918114F1D5E00A3DA4B /* Sources */, 138 | 53F55919114F1D5E00A3DA4B /* Frameworks */, 139 | 53F5591A114F1D5E00A3DA4B /* ShellScript */, 140 | ); 141 | buildRules = ( 142 | ); 143 | dependencies = ( 144 | 53F559C3114F2ABF00A3DA4B /* PBXTargetDependency */, 145 | ); 146 | name = SDURLCacheTestBundle; 147 | productName = SDURLCacheTestBundle; 148 | productReference = 53F5591C114F1D5E00A3DA4B /* SDURLCacheTestBundle.octest */; 149 | productType = "com.apple.product-type.bundle"; 150 | }; 151 | D2AAC07D0554694100DB518D /* SDURLCache */ = { 152 | isa = PBXNativeTarget; 153 | buildConfigurationList = 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "SDURLCache" */; 154 | buildPhases = ( 155 | D2AAC07A0554694100DB518D /* Headers */, 156 | D2AAC07B0554694100DB518D /* Sources */, 157 | D2AAC07C0554694100DB518D /* Frameworks */, 158 | ); 159 | buildRules = ( 160 | ); 161 | dependencies = ( 162 | ); 163 | name = SDURLCache; 164 | productName = SDURLCache; 165 | productReference = D2AAC07E0554694100DB518D /* libSDURLCache.a */; 166 | productType = "com.apple.product-type.library.static"; 167 | }; 168 | /* End PBXNativeTarget section */ 169 | 170 | /* Begin PBXProject section */ 171 | 0867D690FE84028FC02AAC07 /* Project object */ = { 172 | isa = PBXProject; 173 | attributes = { 174 | LastUpgradeCheck = 0430; 175 | }; 176 | buildConfigurationList = 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "SDURLCache" */; 177 | compatibilityVersion = "Xcode 3.2"; 178 | developmentRegion = English; 179 | hasScannedForEncodings = 1; 180 | knownRegions = ( 181 | English, 182 | Japanese, 183 | French, 184 | German, 185 | ); 186 | mainGroup = 0867D691FE84028FC02AAC07 /* SDURLCache */; 187 | productRefGroup = 034768DFFF38A50411DB9C8B /* Products */; 188 | projectDirPath = ""; 189 | projectRoot = ""; 190 | targets = ( 191 | D2AAC07D0554694100DB518D /* SDURLCache */, 192 | 53F5591B114F1D5E00A3DA4B /* SDURLCacheTestBundle */, 193 | ); 194 | }; 195 | /* End PBXProject section */ 196 | 197 | /* Begin PBXResourcesBuildPhase section */ 198 | 53F55917114F1D5E00A3DA4B /* Resources */ = { 199 | isa = PBXResourcesBuildPhase; 200 | buildActionMask = 2147483647; 201 | files = ( 202 | ); 203 | runOnlyForDeploymentPostprocessing = 0; 204 | }; 205 | /* End PBXResourcesBuildPhase section */ 206 | 207 | /* Begin PBXShellScriptBuildPhase section */ 208 | 53F5591A114F1D5E00A3DA4B /* ShellScript */ = { 209 | isa = PBXShellScriptBuildPhase; 210 | buildActionMask = 2147483647; 211 | files = ( 212 | ); 213 | inputPaths = ( 214 | ); 215 | outputPaths = ( 216 | ); 217 | runOnlyForDeploymentPostprocessing = 0; 218 | shellPath = /bin/sh; 219 | shellScript = "# Run the unit tests in this test bundle.\n\"${SYSTEM_DEVELOPER_DIR}/Tools/RunUnitTests\"\n"; 220 | }; 221 | /* End PBXShellScriptBuildPhase section */ 222 | 223 | /* Begin PBXSourcesBuildPhase section */ 224 | 53F55918114F1D5E00A3DA4B /* Sources */ = { 225 | isa = PBXSourcesBuildPhase; 226 | buildActionMask = 2147483647; 227 | files = ( 228 | 53F5592C114F1ED800A3DA4B /* SDURLCacheTests.m in Sources */, 229 | ); 230 | runOnlyForDeploymentPostprocessing = 0; 231 | }; 232 | D2AAC07B0554694100DB518D /* Sources */ = { 233 | isa = PBXSourcesBuildPhase; 234 | buildActionMask = 2147483647; 235 | files = ( 236 | 53F557F5114EA63600A3DA4B /* SDURLCache.m in Sources */, 237 | 5318A9AC155DD7B500AB6767 /* SDCachedURLResponse.m in Sources */, 238 | ); 239 | runOnlyForDeploymentPostprocessing = 0; 240 | }; 241 | /* End PBXSourcesBuildPhase section */ 242 | 243 | /* Begin PBXTargetDependency section */ 244 | 53F559C3114F2ABF00A3DA4B /* PBXTargetDependency */ = { 245 | isa = PBXTargetDependency; 246 | target = D2AAC07D0554694100DB518D /* SDURLCache */; 247 | targetProxy = 53F559C2114F2ABF00A3DA4B /* PBXContainerItemProxy */; 248 | }; 249 | /* End PBXTargetDependency section */ 250 | 251 | /* Begin XCBuildConfiguration section */ 252 | 1DEB921F08733DC00010E9CD /* Debug */ = { 253 | isa = XCBuildConfiguration; 254 | buildSettings = { 255 | ALWAYS_SEARCH_USER_PATHS = NO; 256 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 257 | COPY_PHASE_STRIP = NO; 258 | DSTROOT = /tmp/SDURLCache.dst; 259 | GCC_DYNAMIC_NO_PIC = NO; 260 | GCC_MODEL_TUNING = G5; 261 | GCC_OPTIMIZATION_LEVEL = 0; 262 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 263 | GCC_PREFIX_HEADER = SDURLCache_Prefix.pch; 264 | INSTALL_PATH = /usr/local/lib; 265 | PRODUCT_NAME = SDURLCache; 266 | SDKROOT = iphoneos; 267 | }; 268 | name = Debug; 269 | }; 270 | 1DEB922008733DC00010E9CD /* Release */ = { 271 | isa = XCBuildConfiguration; 272 | buildSettings = { 273 | ALWAYS_SEARCH_USER_PATHS = NO; 274 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 275 | DSTROOT = /tmp/SDURLCache.dst; 276 | GCC_MODEL_TUNING = G5; 277 | GCC_PRECOMPILE_PREFIX_HEADER = YES; 278 | GCC_PREFIX_HEADER = SDURLCache_Prefix.pch; 279 | INSTALL_PATH = /usr/local/lib; 280 | PRODUCT_NAME = SDURLCache; 281 | SDKROOT = iphoneos; 282 | }; 283 | name = Release; 284 | }; 285 | 1DEB922308733DC00010E9CD /* Debug */ = { 286 | isa = XCBuildConfiguration; 287 | buildSettings = { 288 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 289 | GCC_C_LANGUAGE_STANDARD = c99; 290 | GCC_OPTIMIZATION_LEVEL = 0; 291 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 292 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 293 | GCC_WARN_ABOUT_MISSING_NEWLINE = NO; 294 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO; 295 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 296 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 297 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 298 | GCC_WARN_MISSING_PARENTHESES = YES; 299 | GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; 300 | GCC_WARN_SHADOW = YES; 301 | GCC_WARN_SIGN_COMPARE = YES; 302 | GCC_WARN_STRICT_SELECTOR_MATCH = NO; 303 | GCC_WARN_UNDECLARED_SELECTOR = YES; 304 | GCC_WARN_UNINITIALIZED_AUTOS = NO; 305 | GCC_WARN_UNUSED_FUNCTION = YES; 306 | GCC_WARN_UNUSED_LABEL = YES; 307 | GCC_WARN_UNUSED_PARAMETER = NO; 308 | GCC_WARN_UNUSED_VALUE = YES; 309 | GCC_WARN_UNUSED_VARIABLE = YES; 310 | OTHER_LDFLAGS = "-ObjC"; 311 | RUN_CLANG_STATIC_ANALYZER = YES; 312 | SDKROOT = iphoneos; 313 | }; 314 | name = Debug; 315 | }; 316 | 1DEB922408733DC00010E9CD /* Release */ = { 317 | isa = XCBuildConfiguration; 318 | buildSettings = { 319 | ARCHS = "$(ARCHS_STANDARD_32_BIT)"; 320 | GCC_C_LANGUAGE_STANDARD = c99; 321 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 322 | GCC_WARN_ABOUT_MISSING_FIELD_INITIALIZERS = YES; 323 | GCC_WARN_ABOUT_MISSING_NEWLINE = NO; 324 | GCC_WARN_ABOUT_MISSING_PROTOTYPES = NO; 325 | GCC_WARN_ABOUT_RETURN_TYPE = YES; 326 | GCC_WARN_CHECK_SWITCH_STATEMENTS = YES; 327 | GCC_WARN_INITIALIZER_NOT_FULLY_BRACKETED = YES; 328 | GCC_WARN_MISSING_PARENTHESES = YES; 329 | GCC_WARN_MULTIPLE_DEFINITION_TYPES_FOR_SELECTOR = NO; 330 | GCC_WARN_SHADOW = YES; 331 | GCC_WARN_SIGN_COMPARE = YES; 332 | GCC_WARN_STRICT_SELECTOR_MATCH = NO; 333 | GCC_WARN_UNDECLARED_SELECTOR = YES; 334 | GCC_WARN_UNINITIALIZED_AUTOS = NO; 335 | GCC_WARN_UNUSED_FUNCTION = YES; 336 | GCC_WARN_UNUSED_LABEL = YES; 337 | GCC_WARN_UNUSED_PARAMETER = NO; 338 | GCC_WARN_UNUSED_VALUE = YES; 339 | GCC_WARN_UNUSED_VARIABLE = YES; 340 | OTHER_LDFLAGS = "-ObjC"; 341 | RUN_CLANG_STATIC_ANALYZER = YES; 342 | SDKROOT = iphoneos; 343 | }; 344 | name = Release; 345 | }; 346 | 53F5591E114F1D5F00A3DA4B /* Debug */ = { 347 | isa = XCBuildConfiguration; 348 | buildSettings = { 349 | ALWAYS_SEARCH_USER_PATHS = NO; 350 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 351 | COPY_PHASE_STRIP = NO; 352 | FRAMEWORK_SEARCH_PATHS = ( 353 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 354 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 355 | ); 356 | GCC_DYNAMIC_NO_PIC = NO; 357 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 358 | GCC_OPTIMIZATION_LEVEL = 0; 359 | OTHER_LDFLAGS = ( 360 | "-framework", 361 | Foundation, 362 | "-framework", 363 | SenTestingKit, 364 | "-framework", 365 | UIKit, 366 | ); 367 | PRODUCT_NAME = SDURLCacheTestBundle; 368 | SDKROOT = iphoneos; 369 | WRAPPER_EXTENSION = octest; 370 | }; 371 | name = Debug; 372 | }; 373 | 53F5591F114F1D5F00A3DA4B /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ALWAYS_SEARCH_USER_PATHS = NO; 377 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; 378 | COPY_PHASE_STRIP = YES; 379 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 380 | FRAMEWORK_SEARCH_PATHS = ( 381 | "\"$(SDKROOT)/Developer/Library/Frameworks\"", 382 | "\"$(DEVELOPER_LIBRARY_DIR)/Frameworks\"", 383 | ); 384 | GCC_ENABLE_OBJC_EXCEPTIONS = YES; 385 | INFOPLIST_FILE = "SDURLCacheTestBundle-Info.plist"; 386 | OTHER_LDFLAGS = ( 387 | "-framework", 388 | Foundation, 389 | "-framework", 390 | SenTestingKit, 391 | "-framework", 392 | UIKit, 393 | ); 394 | PRODUCT_NAME = SDURLCacheTestBundle; 395 | SDKROOT = iphoneos; 396 | WRAPPER_EXTENSION = octest; 397 | ZERO_LINK = NO; 398 | }; 399 | name = Release; 400 | }; 401 | /* End XCBuildConfiguration section */ 402 | 403 | /* Begin XCConfigurationList section */ 404 | 1DEB921E08733DC00010E9CD /* Build configuration list for PBXNativeTarget "SDURLCache" */ = { 405 | isa = XCConfigurationList; 406 | buildConfigurations = ( 407 | 1DEB921F08733DC00010E9CD /* Debug */, 408 | 1DEB922008733DC00010E9CD /* Release */, 409 | ); 410 | defaultConfigurationIsVisible = 0; 411 | defaultConfigurationName = Release; 412 | }; 413 | 1DEB922208733DC00010E9CD /* Build configuration list for PBXProject "SDURLCache" */ = { 414 | isa = XCConfigurationList; 415 | buildConfigurations = ( 416 | 1DEB922308733DC00010E9CD /* Debug */, 417 | 1DEB922408733DC00010E9CD /* Release */, 418 | ); 419 | defaultConfigurationIsVisible = 0; 420 | defaultConfigurationName = Release; 421 | }; 422 | 53F55920114F1D5F00A3DA4B /* Build configuration list for PBXNativeTarget "SDURLCacheTestBundle" */ = { 423 | isa = XCConfigurationList; 424 | buildConfigurations = ( 425 | 53F5591E114F1D5F00A3DA4B /* Debug */, 426 | 53F5591F114F1D5F00A3DA4B /* Release */, 427 | ); 428 | defaultConfigurationIsVisible = 0; 429 | defaultConfigurationName = Release; 430 | }; 431 | /* End XCConfigurationList section */ 432 | }; 433 | rootObject = 0867D690FE84028FC02AAC07 /* Project object */; 434 | } 435 | -------------------------------------------------------------------------------- /SDURLCacheTests.h: -------------------------------------------------------------------------------- 1 | // 2 | // SDURLCacheTests.h 3 | // SDURLCache 4 | // 5 | // Created by Olivier Poitrey on 16/03/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | // See Also: http://developer.apple.com/iphone/library/documentation/Xcode/Conceptual/iphone_development/135-Unit_Testing_Applications/unit_testing_applications.html 9 | 10 | // Application unit tests contain unit test code that must be injected into an application to run correctly. 11 | // Define USE_APPLICATION_UNIT_TEST to 0 if the unit test code is designed to be linked into an independent test executable. 12 | 13 | #define USE_APPLICATION_UNIT_TEST 1 14 | 15 | #import 16 | #import 17 | //#import "application_headers" as required 18 | 19 | 20 | @interface SDURLCacheTests : SenTestCase 21 | { 22 | 23 | } 24 | 25 | - (void)testExpirationDateFromHeader; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /SDURLCacheTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // SDURLCacheTests.m 3 | // SDURLCache 4 | // 5 | // Created by Olivier Poitrey on 16/03/10. 6 | // Copyright 2010 Dailymotion. All rights reserved. 7 | // 8 | 9 | #import "SDURLCacheTests.h" 10 | #import "SDURLCache.h" 11 | 12 | @interface SDURLCache () 13 | + (NSDate *)dateFromHttpDateString:(NSString *)httpDate; 14 | + (NSDate *)expirationDateFromHeaders:(NSDictionary *)headers withStatusCode:(NSInteger)status; 15 | @end 16 | 17 | @implementation SDURLCacheTests 18 | 19 | - (void)testHttpDateParser 20 | { 21 | NSDate *date; 22 | NSTimeInterval referenceTime = 784111777; 23 | 24 | // RFC 1123 date format 25 | date = [SDURLCache dateFromHttpDateString:@"Sun, 06 Nov 1994 08:49:37 GMT"]; 26 | STAssertEquals([date timeIntervalSince1970], referenceTime, @"RFC 1123 date format"); 27 | 28 | // ANSI C date format 29 | date = [SDURLCache dateFromHttpDateString:@"Sun Nov 6 08:49:37 1994"]; 30 | STAssertEquals([date timeIntervalSince1970], referenceTime, @"ANSI C date format %f", [date timeIntervalSince1970]); 31 | 32 | // RFC 850 date format 33 | date = [SDURLCache dateFromHttpDateString:@"Sunday, 06-Nov-94 08:49:37 GMT"]; 34 | STAssertEquals([date timeIntervalSince1970], referenceTime, @"RFC 850 date format"); 35 | } 36 | 37 | - (void)testExpirationDateFromHeader 38 | { 39 | NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; 40 | [dateFormatter setDateFormat:@"EEE, dd MMM yyyy HH:mm:ss z"]; 41 | NSDate *now = [NSDate date]; 42 | NSString *pastDate = [dateFormatter stringFromDate:[NSDate dateWithTimeInterval:-1000 sinceDate:now]]; 43 | NSString *nowDate = [dateFormatter stringFromDate:now]; 44 | NSString *futureDate = [dateFormatter stringFromDate:[NSDate dateWithTimeInterval:1000 sinceDate:now]]; 45 | 46 | NSDate *expDate; 47 | 48 | // No cache control 49 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:nowDate, @"Date", nil] withStatusCode:200]; 50 | STAssertNotNil(expDate, @"No cache control returns a default expiration date"); 51 | STAssertEqualsWithAccuracy([expDate timeIntervalSinceNow], (NSTimeInterval)3600, 1, @"Default expiration date is 1 hour"); 52 | 53 | // No cache control but last-modified 54 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:pastDate, @"Last-Modified", nowDate, @"Date", nil] withStatusCode:200]; 55 | STAssertNotNil(expDate, @"No cache control with last-modified header returns an expiration date"); 56 | STAssertEqualsWithAccuracy([expDate timeIntervalSinceNow], (NSTimeInterval)100, 1, @"Expiration date relative to last-modified is 10%% of the age"); 57 | 58 | // Pragma: no-cache 59 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"no-cache", @"Pragma", futureDate, @"Expires", nil] withStatusCode:200]; 60 | STAssertNil(expDate, @"Pragma no-cache"); 61 | 62 | // Expires in the past 63 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:pastDate, @"Expires", nil] withStatusCode:200]; 64 | STAssertNil(expDate, @"Expires in the past"); 65 | 66 | // Expires in the past 67 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:futureDate, @"Expires", nil] withStatusCode:200]; 68 | STAssertTrue([expDate timeIntervalSinceNow] > 0, @"Expires in the future"); 69 | 70 | // Cache-Control: no-cache with Expires in the future 71 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"no-cache", @"Cache-Control", futureDate, @"Expires", nil] withStatusCode:200]; 72 | STAssertTrue([expDate timeIntervalSinceNow] > 0, @"Cache-Control no-cache with Expires in the future"); 73 | 74 | // Cache-Control with future date 75 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=1000", @"Cache-Control", nil] withStatusCode:200]; 76 | STAssertNotNil(expDate, @"Cache-Control with future date"); 77 | STAssertTrue([expDate timeIntervalSinceNow] > 0, @"Cache-Control with future date"); 78 | 79 | // Cache-Control with max-age=0 and Expires future date 80 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=0", @"Cache-Control", 81 | futureDate, @"Expires", nil] withStatusCode:200]; 82 | STAssertNil(expDate, @"Cache-Control with max-age=0 and Expires future date"); 83 | 84 | // Cache-Control with future date and Expires past date 85 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=1000", @"Cache-Control", pastDate, @"Expires", nil] withStatusCode:200]; 86 | STAssertNotNil(expDate, @"Cache-Control with future date and Expires past date"); 87 | STAssertTrue([expDate timeIntervalSinceNow] > 0, @"Cache-Control with future date and Expires past date"); 88 | 89 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:100], @"Response status code 100 is not cacheable"); 90 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:101], @"Response status code 101 is not cacheable"); 91 | STAssertNotNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:200], @"Response status code 200 is cacheable"); 92 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:201], @"Response status code 201 is not cacheable"); 93 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:202], @"Response status code 202 is not cacheable"); 94 | STAssertNotNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:203], @"Response status code 203 is cacheable"); 95 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:204], @"Response status code 204 is not cacheable"); 96 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:205], @"Response status code 205 is not cacheable"); 97 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:206], @"Response status code 206 is not cacheable"); 98 | STAssertNotNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:300], @"Response status code 300 is cacheable"); 99 | STAssertNotNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:301], @"Response status code 301 is cacheable"); 100 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:302], @"Response status code 302 is not cacheable if not explicitly instructed"); 101 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=1000", @"Cache-Control", nil] withStatusCode:302]; 102 | STAssertNotNil(expDate, @"Response status code 302 is cacheable if explicitly instructed"); 103 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:303], @"Response status code 303 is not cacheable"); 104 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:304], @"Response status code 304 is not cacheable"); 105 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:305], @"Response status code 305 is not cacheable"); 106 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:307], @"Response status code 305 is not cacheable if not explicitly instructed"); 107 | expDate = [SDURLCache expirationDateFromHeaders:[NSDictionary dictionaryWithObjectsAndKeys:@"public, max-age=1000", @"Cache-Control", nil] withStatusCode:307]; 108 | STAssertNotNil(expDate, @"Response status code 307 is cacheable if explicitly instructed"); 109 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:400], @"Response status code 400 is not cacheable"); 110 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:401], @"Response status code 401 is not cacheable"); 111 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:402], @"Response status code 402 is not cacheable"); 112 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:403], @"Response status code 403 is not cacheable"); 113 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:404], @"Response status code 404 is not cacheable"); 114 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:405], @"Response status code 405 is not cacheable"); 115 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:406], @"Response status code 406 is not cacheable"); 116 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:407], @"Response status code 407 is not cacheable"); 117 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:408], @"Response status code 408 is not cacheable"); 118 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:409], @"Response status code 409 is not cacheable"); 119 | STAssertNotNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:410], @"Response status code 410 is cacheable"); 120 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:411], @"Response status code 411 is not cacheable"); 121 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:412], @"Response status code 412 is not cacheable"); 122 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:413], @"Response status code 413 is not cacheable"); 123 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:414], @"Response status code 414 is not cacheable"); 124 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:415], @"Response status code 415 is not cacheable"); 125 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:416], @"Response status code 416 is not cacheable"); 126 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:417], @"Response status code 417 is not cacheable"); 127 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:500], @"Response status code 500 is not cacheable"); 128 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:501], @"Response status code 501 is not cacheable"); 129 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:502], @"Response status code 502 is not cacheable"); 130 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:503], @"Response status code 503 is not cacheable"); 131 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:504], @"Response status code 504 is not cacheable"); 132 | STAssertNil([SDURLCache expirationDateFromHeaders:nil withStatusCode:505], @"Response status code 505 is not cacheable"); 133 | } 134 | 135 | - (void)testCaching 136 | { 137 | // TODO 138 | } 139 | 140 | - (void)testCacheCapacity 141 | { 142 | // TODO 143 | } 144 | 145 | @end 146 | -------------------------------------------------------------------------------- /SDURLCache_Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header for all source files of the 'CocoaTouchStaticLibrary' target in the 'CocoaTouchStaticLibrary' project. 3 | // 4 | 5 | #ifdef __OBJC__ 6 | #import 7 | #endif 8 | --------------------------------------------------------------------------------