MKNetworkEngine.m 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652
  1. //
  2. // MKNetworkEngine.m
  3. // MKNetworkKit
  4. //
  5. // Created by Mugunth Kumar (@mugunthkumar) on 11/11/11.
  6. // Copyright (C) 2011-2020 by Steinlogic Consulting and Training Pte Ltd
  7. // Permission is hereby granted, free of charge, to any person obtaining a copy
  8. // of this software and associated documentation files (the "Software"), to deal
  9. // in the Software without restriction, including without limitation the rights
  10. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  11. // copies of the Software, and to permit persons to whom the Software is
  12. // furnished to do so, subject to the following conditions:
  13. //
  14. // The above copyright notice and this permission notice shall be included in
  15. // all copies or substantial portions of the Software.
  16. //
  17. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  18. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  19. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  20. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  21. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  22. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  23. // THE SOFTWARE.
  24. #import "MKNetworkKit.h"
  25. #define kFreezableOperationExtension @"mknetworkkitfrozenoperation"
  26. #ifdef __OBJC_GC__
  27. #error MKNetworkKit does not support Objective-C Garbage Collection
  28. #endif
  29. #if ! __has_feature(objc_arc)
  30. #error MKNetworkKit is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
  31. #endif
  32. @interface MKNetworkEngine (/*Private Methods*/)
  33. @property (strong, nonatomic) NSString *hostName;
  34. @property (strong, nonatomic) Reachability *reachability;
  35. @property (strong, nonatomic) NSDictionary *customHeaders;
  36. @property (assign, nonatomic) Class customOperationSubclass;
  37. @property (nonatomic, strong) NSMutableDictionary *memoryCache;
  38. @property (nonatomic, strong) NSMutableArray *memoryCacheKeys;
  39. @property (nonatomic, strong) NSMutableDictionary *cacheInvalidationParams;
  40. -(void) saveCache;
  41. -(void) saveCacheData:(NSData*) data forKey:(NSString*) cacheDataKey;
  42. -(void) freezeOperations;
  43. -(void) checkAndRestoreFrozenOperations;
  44. -(BOOL) isCacheEnabled;
  45. @end
  46. static NSOperationQueue *_sharedNetworkQueue;
  47. @implementation MKNetworkEngine
  48. @synthesize hostName = _hostName;
  49. @synthesize reachability = _reachability;
  50. @synthesize customHeaders = _customHeaders;
  51. @synthesize customOperationSubclass = _customOperationSubclass;
  52. @synthesize memoryCache = _memoryCache;
  53. @synthesize memoryCacheKeys = _memoryCacheKeys;
  54. @synthesize cacheInvalidationParams = _cacheInvalidationParams;
  55. @synthesize reachabilityChangedHandler = _reachabilityChangedHandler;
  56. @synthesize portNumber = _portNumber;
  57. @synthesize apiPath = _apiPath;
  58. // Network Queue is a shared singleton object.
  59. // no matter how many instances of MKNetworkEngine is created, there is one and only one network queue
  60. // In theory an app should contain as many network engines as the number of domains it talks to
  61. #pragma mark -
  62. #pragma mark Initialization
  63. +(void) initialize {
  64. if(!_sharedNetworkQueue) {
  65. static dispatch_once_t oncePredicate;
  66. dispatch_once(&oncePredicate, ^{
  67. _sharedNetworkQueue = [[NSOperationQueue alloc] init];
  68. [_sharedNetworkQueue addObserver:[self self] forKeyPath:@"operationCount" options:0 context:NULL];
  69. [_sharedNetworkQueue setMaxConcurrentOperationCount:6];
  70. });
  71. }
  72. }
  73. - (id) init {
  74. return [self initWithHostName:nil];
  75. }
  76. - (id) initWithHostName:(NSString*) hostName {
  77. return [self initWithHostName:hostName apiPath:nil customHeaderFields:nil];
  78. }
  79. - (id) initWithHostName:(NSString*) hostName apiPath:(NSString*) apiPath customHeaderFields:(NSDictionary*) headers {
  80. if((self = [super init])) {
  81. self.apiPath = apiPath;
  82. if(hostName) {
  83. [[NSNotificationCenter defaultCenter] addObserver:self
  84. selector:@selector(reachabilityChanged:)
  85. name:kReachabilityChangedNotification
  86. object:nil];
  87. self.hostName = hostName;
  88. self.reachability = [Reachability reachabilityWithHostname:self.hostName];
  89. [self.reachability startNotifier];
  90. }
  91. if([headers objectForKey:@"User-Agent"] == nil) {
  92. NSMutableDictionary *newHeadersDict = [headers mutableCopy];
  93. NSString *userAgentString = [NSString stringWithFormat:@"%@/%@",
  94. [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleNameKey],
  95. [[[NSBundle mainBundle] infoDictionary] objectForKey:(NSString *)kCFBundleVersionKey]];
  96. [newHeadersDict setObject:userAgentString forKey:@"User-Agent"];
  97. self.customHeaders = newHeadersDict;
  98. } else {
  99. self.customHeaders = headers;
  100. }
  101. self.customOperationSubclass = [MKNetworkOperation class];
  102. }
  103. return self;
  104. }
  105. - (id) initWithHostName:(NSString*) hostName customHeaderFields:(NSDictionary*) headers {
  106. return [self initWithHostName:hostName apiPath:nil customHeaderFields:headers];
  107. }
  108. #pragma mark -
  109. #pragma mark Memory Mangement
  110. -(void) dealloc {
  111. [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
  112. #if TARGET_OS_IPHONE
  113. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  114. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
  115. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
  116. #elif TARGET_OS_MAC
  117. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillHideNotification object:nil];
  118. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillResignActiveNotification object:nil];
  119. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
  120. #endif
  121. }
  122. +(void) dealloc {
  123. [_sharedNetworkQueue removeObserver:[self self] forKeyPath:@"operationCount"];
  124. }
  125. #pragma mark -
  126. #pragma mark KVO for network Queue
  127. + (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
  128. change:(NSDictionary *)change context:(void *)context
  129. {
  130. if (object == _sharedNetworkQueue && [keyPath isEqualToString:@"operationCount"]) {
  131. [[NSNotificationCenter defaultCenter] postNotificationName:kMKNetworkEngineOperationCountChanged
  132. object:[NSNumber numberWithInteger:[_sharedNetworkQueue operationCount]]];
  133. #if TARGET_OS_IPHONE
  134. [UIApplication sharedApplication].networkActivityIndicatorVisible =
  135. ([_sharedNetworkQueue.operations count] > 0);
  136. #endif
  137. }
  138. else {
  139. [super observeValueForKeyPath:keyPath ofObject:object
  140. change:change context:context];
  141. }
  142. }
  143. #pragma mark -
  144. #pragma mark Reachability related
  145. -(void) reachabilityChanged:(NSNotification*) notification
  146. {
  147. if([self.reachability currentReachabilityStatus] == ReachableViaWiFi)
  148. {
  149. DLog(@"Server [%@] is reachable via Wifi", self.hostName);
  150. [_sharedNetworkQueue setMaxConcurrentOperationCount:6];
  151. [self checkAndRestoreFrozenOperations];
  152. }
  153. else if([self.reachability currentReachabilityStatus] == ReachableViaWWAN)
  154. {
  155. DLog(@"Server [%@] is reachable only via cellular data", self.hostName);
  156. [_sharedNetworkQueue setMaxConcurrentOperationCount:2];
  157. [self checkAndRestoreFrozenOperations];
  158. }
  159. else if([self.reachability currentReachabilityStatus] == NotReachable)
  160. {
  161. DLog(@"Server [%@] is not reachable", self.hostName);
  162. [self freezeOperations];
  163. }
  164. if(self.reachabilityChangedHandler) {
  165. self.reachabilityChangedHandler([self.reachability currentReachabilityStatus]);
  166. }
  167. }
  168. #pragma mark Freezing operations (Called when network connectivity fails)
  169. -(void) freezeOperations {
  170. if(![self isCacheEnabled]) return;
  171. for(MKNetworkOperation *operation in _sharedNetworkQueue.operations) {
  172. // freeze only freeable operations.
  173. if(![operation freezable]) continue;
  174. if(!self.hostName) return;
  175. // freeze only operations that belong to this server
  176. if([[operation url] rangeOfString:self.hostName].location == NSNotFound) continue;
  177. NSString *archivePath = [[[self cacheDirectoryName] stringByAppendingPathComponent:[operation uniqueIdentifier]]
  178. stringByAppendingPathExtension:kFreezableOperationExtension];
  179. [NSKeyedArchiver archiveRootObject:operation toFile:archivePath];
  180. [operation cancel];
  181. }
  182. }
  183. -(void) checkAndRestoreFrozenOperations {
  184. if(![self isCacheEnabled]) return;
  185. NSError *error = nil;
  186. NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self cacheDirectoryName] error:&error];
  187. if(error)
  188. DLog(@"%@", error);
  189. NSArray *pendingOperations = [files filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  190. NSString *thisFile = (NSString*) evaluatedObject;
  191. return ([thisFile rangeOfString:kFreezableOperationExtension].location != NSNotFound);
  192. }]];
  193. for(NSString *pendingOperationFile in pendingOperations) {
  194. NSString *archivePath = [[self cacheDirectoryName] stringByAppendingPathComponent:pendingOperationFile];
  195. MKNetworkOperation *pendingOperation = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
  196. [self enqueueOperation:pendingOperation];
  197. NSError *error = nil;
  198. [[NSFileManager defaultManager] removeItemAtPath:archivePath error:&error];
  199. if(error)
  200. DLog(@"%@", error);
  201. }
  202. }
  203. -(NSString*) readonlyHostName {
  204. return [_hostName copy];
  205. }
  206. -(BOOL) isReachable {
  207. return ([self.reachability currentReachabilityStatus] != NotReachable);
  208. }
  209. #pragma mark -
  210. #pragma mark Create methods
  211. -(void) registerOperationSubclass:(Class) aClass {
  212. self.customOperationSubclass = aClass;
  213. }
  214. -(MKNetworkOperation*) operationWithPath:(NSString*) path {
  215. return [self operationWithPath:path params:nil];
  216. }
  217. -(MKNetworkOperation*) operationWithPath:(NSString*) path
  218. params:(NSMutableDictionary*) body {
  219. return [self operationWithPath:path
  220. params:body
  221. httpMethod:@"GET"];
  222. }
  223. -(MKNetworkOperation*) operationWithPath:(NSString*) path
  224. params:(NSMutableDictionary*) body
  225. httpMethod:(NSString*)method {
  226. return [self operationWithPath:path params:body httpMethod:method ssl:NO];
  227. }
  228. -(MKNetworkOperation*) operationWithPath:(NSString*) path
  229. params:(NSMutableDictionary*) body
  230. httpMethod:(NSString*)method
  231. ssl:(BOOL) useSSL {
  232. if(self.hostName == nil) {
  233. DLog(@"Hostname is nil, use operationWithURLString: method to create absolute URL operations");
  234. return nil;
  235. }
  236. NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@://%@", useSSL ? @"https" : @"http", self.hostName];
  237. if(self.portNumber != 0)
  238. [urlString appendFormat:@":%d", self.portNumber];
  239. if(self.apiPath)
  240. [urlString appendFormat:@"/%@", self.apiPath];
  241. [urlString appendFormat:@"/%@", path];
  242. return [self operationWithURLString:urlString params:body httpMethod:method];
  243. }
  244. -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString {
  245. return [self operationWithURLString:urlString params:nil httpMethod:@"GET"];
  246. }
  247. -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString
  248. params:(NSMutableDictionary*) body {
  249. return [self operationWithURLString:urlString params:body httpMethod:@"GET"];
  250. }
  251. -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString
  252. params:(NSMutableDictionary*) body
  253. httpMethod:(NSString*)method {
  254. MKNetworkOperation *operation = [[self.customOperationSubclass alloc] initWithURLString:urlString params:body httpMethod:method];
  255. [self prepareHeaders:operation];
  256. return operation;
  257. }
  258. -(void) prepareHeaders:(MKNetworkOperation*) operation {
  259. [operation addHeaders:self.customHeaders];
  260. }
  261. -(NSData*) cachedDataForOperation:(MKNetworkOperation*) operation {
  262. NSData *cachedData = [self.memoryCache objectForKey:[operation uniqueIdentifier]];
  263. if(cachedData) return cachedData;
  264. NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:[operation uniqueIdentifier]];
  265. if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  266. cachedData = [NSData dataWithContentsOfFile:filePath];
  267. [self saveCacheData:cachedData forKey:[operation uniqueIdentifier]]; // bring it back to the in-memory cache
  268. return cachedData;
  269. }
  270. return nil;
  271. }
  272. -(void) enqueueOperation:(MKNetworkOperation*) operation {
  273. [self enqueueOperation:operation forceReload:NO];
  274. }
  275. -(void) enqueueOperation:(MKNetworkOperation*) operation forceReload:(BOOL) forceReload {
  276. NSParameterAssert(operation != nil);
  277. // Grab on to the current queue (We need it later)
  278. dispatch_queue_t originalQueue = dispatch_get_current_queue();
  279. #if DO_GCD_RETAIN_RELEASE
  280. dispatch_retain(originalQueue);
  281. #endif
  282. // Jump off the main thread, mainly for disk cache reading purposes
  283. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  284. [operation setCacheHandler:^(MKNetworkOperation* completedCacheableOperation) {
  285. // if this is not called, the request would have been a non cacheable request
  286. //completedCacheableOperation.cacheHeaders;
  287. NSString *uniqueId = [completedCacheableOperation uniqueIdentifier];
  288. [self saveCacheData:[completedCacheableOperation responseData]
  289. forKey:uniqueId];
  290. [self.cacheInvalidationParams setObject:completedCacheableOperation.cacheHeaders forKey:uniqueId];
  291. }];
  292. __block double expiryTimeInSeconds = 0.0f;
  293. if([operation isCacheable]) {
  294. if(!forceReload) {
  295. NSData *cachedData = [self cachedDataForOperation:operation];
  296. if(cachedData) {
  297. dispatch_async(originalQueue, ^{
  298. // Jump back to the original thread here since setCachedData updates the main thread
  299. [operation setCachedData:cachedData];
  300. });
  301. NSString *uniqueId = [operation uniqueIdentifier];
  302. NSMutableDictionary *savedCacheHeaders = [self.cacheInvalidationParams objectForKey:uniqueId];
  303. // there is a cached version.
  304. // this means, the current operation is a "GET"
  305. if(savedCacheHeaders) {
  306. NSString *expiresOn = [savedCacheHeaders objectForKey:@"Expires"];
  307. dispatch_sync(originalQueue, ^{
  308. NSDate *expiresOnDate = [NSDate dateFromRFC1123:expiresOn];
  309. expiryTimeInSeconds = [expiresOnDate timeIntervalSinceNow];
  310. });
  311. [operation updateOperationBasedOnPreviousHeaders:savedCacheHeaders];
  312. }
  313. }
  314. }
  315. dispatch_async(originalQueue, ^{
  316. NSUInteger index = [_sharedNetworkQueue.operations indexOfObject:operation];
  317. if(index == NSNotFound) {
  318. if(expiryTimeInSeconds <= 0)
  319. [_sharedNetworkQueue addOperation:operation];
  320. else if(forceReload)
  321. [_sharedNetworkQueue addOperation:operation];
  322. // else don't do anything
  323. }
  324. else {
  325. // This operation is already being processed
  326. MKNetworkOperation *queuedOperation = (MKNetworkOperation*) [_sharedNetworkQueue.operations objectAtIndex:index];
  327. [queuedOperation updateHandlersFromOperation:operation];
  328. }
  329. });
  330. } else {
  331. [_sharedNetworkQueue addOperation:operation];
  332. }
  333. if([self.reachability currentReachabilityStatus] == NotReachable)
  334. [self freezeOperations];
  335. #if DO_GCD_RETAIN_RELEASE
  336. dispatch_release(originalQueue);
  337. #endif
  338. });
  339. }
  340. - (MKNetworkOperation*)imageAtURL:(NSURL *)url onCompletion:(MKNKImageBlock) imageFetchedBlock
  341. {
  342. #ifdef DEBUG
  343. // I could enable caching here, but that hits performance and inturn affects table view scrolling
  344. // if imageAtURL is called for loading thumbnails.
  345. if(![self isCacheEnabled]) DLog(@"imageAtURL:onCompletion: requires caching to be enabled.")
  346. #endif
  347. if (url == nil) {
  348. return nil;
  349. }
  350. MKNetworkOperation *op = [self operationWithURLString:[url absoluteString]];
  351. [op
  352. onCompletion:^(MKNetworkOperation *completedOperation)
  353. {
  354. imageFetchedBlock([completedOperation responseImage],
  355. url,
  356. [completedOperation isCachedResponse]);
  357. }
  358. onError:^(NSError* error) {
  359. DLog(@"%@", error);
  360. }];
  361. [self enqueueOperation:op];
  362. return op;
  363. }
  364. #pragma mark -
  365. #pragma mark Cache related
  366. -(NSString*) cacheDirectoryName {
  367. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  368. NSString *documentsDirectory = [paths objectAtIndex:0];
  369. NSString *cacheDirectoryName = [documentsDirectory stringByAppendingPathComponent:MKNETWORKCACHE_DEFAULT_DIRECTORY];
  370. return cacheDirectoryName;
  371. }
  372. -(int) cacheMemoryCost {
  373. return MKNETWORKCACHE_DEFAULT_COST;
  374. }
  375. -(void) saveCache {
  376. for(NSString *cacheKey in [self.memoryCache allKeys])
  377. {
  378. NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:cacheKey];
  379. if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  380. NSError *error = nil;
  381. [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
  382. ELog(error);
  383. }
  384. [[self.memoryCache objectForKey:cacheKey] writeToFile:filePath atomically:YES];
  385. }
  386. [self.memoryCache removeAllObjects];
  387. [self.memoryCacheKeys removeAllObjects];
  388. NSString *cacheInvalidationPlistFilePath = [[self cacheDirectoryName] stringByAppendingPathExtension:@"plist"];
  389. [self.cacheInvalidationParams writeToFile:cacheInvalidationPlistFilePath atomically:YES];
  390. }
  391. -(void) saveCacheData:(NSData*) data forKey:(NSString*) cacheDataKey
  392. {
  393. @synchronized(self) {
  394. [self.memoryCache setObject:data forKey:cacheDataKey];
  395. NSUInteger index = [self.memoryCacheKeys indexOfObject:cacheDataKey];
  396. if(index != NSNotFound)
  397. [self.memoryCacheKeys removeObjectAtIndex:index];
  398. [self.memoryCacheKeys insertObject:cacheDataKey atIndex:0]; // remove it and insert it at start
  399. if([self.memoryCacheKeys count] >= [self cacheMemoryCost])
  400. {
  401. NSString *lastKey = [self.memoryCacheKeys lastObject];
  402. NSData *data = [self.memoryCache objectForKey:lastKey];
  403. NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:lastKey];
  404. if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  405. NSError *error = nil;
  406. [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
  407. ELog(error);
  408. }
  409. [data writeToFile:filePath atomically:YES];
  410. [self.memoryCacheKeys removeLastObject];
  411. [self.memoryCache removeObjectForKey:lastKey];
  412. }
  413. }
  414. }
  415. /*
  416. - (BOOL) dataOldness:(NSString*) imagePath
  417. {
  418. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:imagePath error:nil];
  419. NSDate *creationDate = [attributes valueForKey:NSFileCreationDate];
  420. return abs([creationDate timeIntervalSinceNow]);
  421. }*/
  422. -(BOOL) isCacheEnabled {
  423. BOOL isDir = NO;
  424. BOOL isCachingEnabled = [[NSFileManager defaultManager] fileExistsAtPath:[self cacheDirectoryName] isDirectory:&isDir];
  425. return isCachingEnabled;
  426. }
  427. -(void) useCache {
  428. self.memoryCache = [NSMutableDictionary dictionaryWithCapacity:[self cacheMemoryCost]];
  429. self.memoryCacheKeys = [NSMutableArray arrayWithCapacity:[self cacheMemoryCost]];
  430. self.cacheInvalidationParams = [NSMutableDictionary dictionary];
  431. NSString *cacheDirectory = [self cacheDirectoryName];
  432. BOOL isDirectory = YES;
  433. BOOL folderExists = [[NSFileManager defaultManager] fileExistsAtPath:cacheDirectory isDirectory:&isDirectory] && isDirectory;
  434. if (!folderExists)
  435. {
  436. NSError *error = nil;
  437. [[NSFileManager defaultManager] createDirectoryAtPath:cacheDirectory withIntermediateDirectories:YES attributes:nil error:&error];
  438. }
  439. NSString *cacheInvalidationPlistFilePath = [cacheDirectory stringByAppendingPathExtension:@"plist"];
  440. BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:cacheInvalidationPlistFilePath];
  441. if (fileExists)
  442. {
  443. self.cacheInvalidationParams = [NSMutableDictionary dictionaryWithContentsOfFile:cacheInvalidationPlistFilePath];
  444. }
  445. #if TARGET_OS_IPHONE
  446. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  447. name:UIApplicationDidReceiveMemoryWarningNotification
  448. object:nil];
  449. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  450. name:UIApplicationDidEnterBackgroundNotification
  451. object:nil];
  452. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  453. name:UIApplicationWillTerminateNotification
  454. object:nil];
  455. #elif TARGET_OS_MAC
  456. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  457. name:NSApplicationWillHideNotification
  458. object:nil];
  459. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  460. name:NSApplicationWillResignActiveNotification
  461. object:nil];
  462. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  463. name:NSApplicationWillTerminateNotification
  464. object:nil];
  465. #endif
  466. }
  467. -(void) emptyCache {
  468. [self saveCache]; // ensures that invalidation params are written to disk properly
  469. NSError *error = nil;
  470. NSArray *directoryContents = [[NSFileManager defaultManager]
  471. contentsOfDirectoryAtPath:[self cacheDirectoryName] error:&error];
  472. if(error) DLog(@"%@", error);
  473. error = nil;
  474. for(NSString *fileName in directoryContents) {
  475. NSString *path = [[self cacheDirectoryName] stringByAppendingPathComponent:fileName];
  476. [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
  477. if(error) DLog(@"%@", error);
  478. }
  479. error = nil;
  480. NSString *cacheInvalidationPlistFilePath = [[self cacheDirectoryName] stringByAppendingPathExtension:@"plist"];
  481. [[NSFileManager defaultManager] removeItemAtPath:cacheInvalidationPlistFilePath error:&error];
  482. if(error) DLog(@"%@", error);
  483. }
  484. @end