MKNetworkEngine.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714
  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 TARGET_OS_IPHONE
  30. #ifndef __IPHONE_5_0
  31. #error MKNetworkKit does not support iOS 4 and lower
  32. #endif
  33. #endif
  34. #if ! __has_feature(objc_arc)
  35. #error MKNetworkKit is ARC only. Either turn on ARC for the project or use -fobjc-arc flag
  36. #endif
  37. @interface MKNetworkEngine (/*Private Methods*/)
  38. @property (copy, nonatomic) NSString *hostName;
  39. @property (strong, nonatomic) Reachability *reachability;
  40. @property (copy, nonatomic) NSDictionary *customHeaders;
  41. @property (assign, nonatomic) Class customOperationSubclass;
  42. @property (nonatomic, strong) NSMutableDictionary *memoryCache;
  43. @property (nonatomic, strong) NSMutableArray *memoryCacheKeys;
  44. @property (nonatomic, strong) NSMutableDictionary *cacheInvalidationParams;
  45. #if OS_OBJECT_USE_OBJC
  46. @property (strong, nonatomic) dispatch_queue_t backgroundCacheQueue;
  47. @property (strong, nonatomic) dispatch_queue_t operationQueue;
  48. #else
  49. @property (assign, nonatomic) dispatch_queue_t backgroundCacheQueue;
  50. @property (assign, nonatomic) dispatch_queue_t operationQueue;
  51. #endif
  52. -(void) saveCache;
  53. -(void) saveCacheData:(NSData*) data forKey:(NSString*) cacheDataKey;
  54. -(void) freezeOperations;
  55. -(void) checkAndRestoreFrozenOperations;
  56. -(BOOL) isCacheEnabled;
  57. @end
  58. static NSOperationQueue *_sharedNetworkQueue;
  59. @implementation MKNetworkEngine
  60. // Network Queue is a shared singleton object.
  61. // no matter how many instances of MKNetworkEngine is created, there is one and only one network queue
  62. // In theory an app should contain as many network engines as the number of domains it talks to
  63. #pragma mark -
  64. #pragma mark Initialization
  65. #define kHImageCacheExpire 24.0*3600*365*100
  66. +(void) initialize {
  67. if(!_sharedNetworkQueue) {
  68. static dispatch_once_t oncePredicate;
  69. dispatch_once(&oncePredicate, ^{
  70. _sharedNetworkQueue = [[NSOperationQueue alloc] init];
  71. [_sharedNetworkQueue addObserver:[self self] forKeyPath:@"operationCount" options:0 context:NULL];
  72. [_sharedNetworkQueue setMaxConcurrentOperationCount:6];
  73. });
  74. }
  75. }
  76. - (id) init {
  77. return [self initWithHostName:nil];
  78. }
  79. - (id) initWithHostName:(NSString*) hostName {
  80. return [self initWithHostName:hostName apiPath:nil customHeaderFields:nil];
  81. }
  82. - (id) initWithHostName:(NSString*) hostName apiPath:(NSString*) apiPath customHeaderFields:(NSDictionary*) headers {
  83. if((self = [super init])) {
  84. self.apiPath = apiPath;
  85. self.backgroundCacheQueue = dispatch_queue_create("com.mknetworkkit.cachequeue", DISPATCH_QUEUE_SERIAL);
  86. self.operationQueue = dispatch_queue_create("com.mknetworkkit.operationqueue", DISPATCH_QUEUE_SERIAL);
  87. if(hostName) {
  88. [[NSNotificationCenter defaultCenter] addObserver:self
  89. selector:@selector(reachabilityChanged:)
  90. name:kReachabilityChangedNotification
  91. object:nil];
  92. self.hostName = hostName;
  93. self.reachability = [Reachability reachabilityWithHostname:self.hostName];
  94. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  95. [self.reachability startNotifier];
  96. });
  97. }
  98. if(headers[@"User-Agent"] == nil) {
  99. NSMutableDictionary *newHeadersDict = [headers mutableCopy];
  100. NSString *userAgentString = [NSString stringWithFormat:@"%@/%@",
  101. [[NSBundle mainBundle] infoDictionary][(NSString *)kCFBundleNameKey],
  102. [[NSBundle mainBundle] infoDictionary][(NSString *)kCFBundleVersionKey]];
  103. newHeadersDict[@"User-Agent"] = userAgentString;
  104. self.customHeaders = newHeadersDict;
  105. } else {
  106. self.customHeaders = [headers mutableCopy];
  107. }
  108. self.customOperationSubclass = [MKNetworkOperation class];
  109. }
  110. return self;
  111. }
  112. - (id) initWithHostName:(NSString*) hostName customHeaderFields:(NSDictionary*) headers {
  113. return [self initWithHostName:hostName apiPath:nil customHeaderFields:headers];
  114. }
  115. #pragma mark -
  116. #pragma mark Memory Mangement
  117. -(void) dealloc {
  118. [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
  119. #if TARGET_OS_IPHONE
  120. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  121. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
  122. [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
  123. #elif TARGET_OS_MAC
  124. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillHideNotification object:nil];
  125. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillResignActiveNotification object:nil];
  126. [[NSNotificationCenter defaultCenter] removeObserver:self name:NSApplicationWillTerminateNotification object:nil];
  127. #endif
  128. }
  129. +(void) dealloc {
  130. [_sharedNetworkQueue removeObserver:[self self] forKeyPath:@"operationCount"];
  131. }
  132. #pragma mark -
  133. #pragma mark KVO for network Queue
  134. + (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object
  135. change:(NSDictionary *)change context:(void *)context
  136. {
  137. if (object == _sharedNetworkQueue && [keyPath isEqualToString:@"operationCount"]) {
  138. [[NSNotificationCenter defaultCenter] postNotificationName:kMKNetworkEngineOperationCountChanged
  139. object:[NSNumber numberWithInteger:(NSInteger)[_sharedNetworkQueue operationCount]]];
  140. #if TARGET_OS_IPHONE
  141. [UIApplication sharedApplication].networkActivityIndicatorVisible =
  142. ([_sharedNetworkQueue.operations count] > 0);
  143. #endif
  144. }
  145. else {
  146. [super observeValueForKeyPath:keyPath ofObject:object
  147. change:change context:context];
  148. }
  149. }
  150. #pragma mark -
  151. #pragma mark Reachability related
  152. -(void) reachabilityChanged:(NSNotification*) notification
  153. {
  154. if([self.reachability currentReachabilityStatus] == ReachableViaWiFi)
  155. {
  156. // DLog(@"Server [%@] is reachable via Wifi", self.hostName);
  157. [_sharedNetworkQueue setMaxConcurrentOperationCount:6];
  158. [self checkAndRestoreFrozenOperations];
  159. }
  160. else if([self.reachability currentReachabilityStatus] == ReachableViaWWAN)
  161. {
  162. if(self.wifiOnlyMode) {
  163. DLog(@" Disabling engine as server [%@] is reachable only via cellular data.", self.hostName);
  164. [_sharedNetworkQueue setMaxConcurrentOperationCount:0];
  165. } else {
  166. DLog(@"Server [%@] is reachable only via cellular data", self.hostName);
  167. [_sharedNetworkQueue setMaxConcurrentOperationCount:2];
  168. [self checkAndRestoreFrozenOperations];
  169. }
  170. }
  171. else if([self.reachability currentReachabilityStatus] == NotReachable)
  172. {
  173. DLog(@"Server [%@] is not reachable", self.hostName);
  174. [self freezeOperations];
  175. }
  176. if(self.reachabilityChangedHandler) {
  177. self.reachabilityChangedHandler([self.reachability currentReachabilityStatus]);
  178. }
  179. }
  180. #pragma mark Freezing operations (Called when network connectivity fails)
  181. -(void) freezeOperations {
  182. if(![self isCacheEnabled]) return;
  183. for(MKNetworkOperation *operation in _sharedNetworkQueue.operations) {
  184. // freeze only freeable operations.
  185. if(![operation freezable]) continue;
  186. if(!self.hostName) return;
  187. // freeze only operations that belong to this server
  188. if([[operation url] rangeOfString:self.hostName].location == NSNotFound) continue;
  189. NSString *archivePath = [[[self cacheDirectoryName] stringByAppendingPathComponent:[operation uniqueIdentifier]]
  190. stringByAppendingPathExtension:kFreezableOperationExtension];
  191. [NSKeyedArchiver archiveRootObject:operation toFile:archivePath];
  192. [operation cancel];
  193. }
  194. }
  195. -(void) checkAndRestoreFrozenOperations {
  196. if(![self isCacheEnabled]) return;
  197. NSError *error = nil;
  198. NSArray *files = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:[self cacheDirectoryName] error:&error];
  199. if(error)
  200. DLog(@"%@", error);
  201. NSArray *pendingOperations = [files filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  202. NSString *thisFile = (NSString*) evaluatedObject;
  203. return ([thisFile rangeOfString:kFreezableOperationExtension].location != NSNotFound);
  204. }]];
  205. for(NSString *pendingOperationFile in pendingOperations) {
  206. NSString *archivePath = [[self cacheDirectoryName] stringByAppendingPathComponent:pendingOperationFile];
  207. MKNetworkOperation *pendingOperation = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath];
  208. [self enqueueOperation:pendingOperation];
  209. NSError *error2 = nil;
  210. [[NSFileManager defaultManager] removeItemAtPath:archivePath error:&error2];
  211. if(error2)
  212. DLog(@"%@", error2);
  213. }
  214. }
  215. -(NSString*) readonlyHostName {
  216. return [_hostName copy];
  217. }
  218. -(BOOL) isReachable {
  219. return ([self.reachability currentReachabilityStatus] != NotReachable);
  220. }
  221. #pragma mark -
  222. #pragma mark Create methods
  223. -(void) registerOperationSubclass:(Class) aClass {
  224. self.customOperationSubclass = aClass;
  225. }
  226. -(MKNetworkOperation*) operationWithPath:(NSString*) path {
  227. return [self operationWithPath:path params:nil];
  228. }
  229. -(MKNetworkOperation*) operationWithPath:(NSString*) path
  230. params:(NSDictionary*) body {
  231. return [self operationWithPath:path
  232. params:body
  233. httpMethod:@"GET"];
  234. }
  235. -(MKNetworkOperation*) operationWithPath:(NSString*) path
  236. params:(NSDictionary*) body
  237. httpMethod:(NSString*)method {
  238. return [self operationWithPath:path params:body httpMethod:method ssl:NO];
  239. }
  240. -(MKNetworkOperation*) operationWithPath:(NSString*) path
  241. params:(NSDictionary*) body
  242. httpMethod:(NSString*)method
  243. ssl:(BOOL) useSSL {
  244. if(self.hostName == nil) {
  245. DLog(@"Hostname is nil, use operationWithURLString: method to create absolute URL operations");
  246. return nil;
  247. }
  248. NSMutableString *urlString = [NSMutableString stringWithFormat:@"%@://%@", useSSL ? @"https" : @"http", self.hostName];
  249. if(self.portNumber != 0)
  250. [urlString appendFormat:@":%d", self.portNumber];
  251. if(self.apiPath)
  252. [urlString appendFormat:@"/%@", self.apiPath];
  253. [urlString appendFormat:@"/%@", path];
  254. return [self operationWithURLString:urlString params:body httpMethod:method];
  255. }
  256. -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString {
  257. return [self operationWithURLString:urlString params:nil httpMethod:@"GET"];
  258. }
  259. -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString
  260. params:(NSDictionary*) body {
  261. return [self operationWithURLString:urlString params:body httpMethod:@"GET"];
  262. }
  263. -(MKNetworkOperation*) operationWithURLString:(NSString*) urlString
  264. params:(NSDictionary*) body
  265. httpMethod:(NSString*)method {
  266. MKNetworkOperation *operation = [[self.customOperationSubclass alloc] initWithURLString:urlString params:body httpMethod:method];
  267. [self prepareHeaders:operation];
  268. return operation;
  269. }
  270. -(void) prepareHeaders:(MKNetworkOperation*) operation {
  271. [operation addHeaders:self.customHeaders];
  272. }
  273. -(NSData*) cachedDataForOperation:(MKNetworkOperation*) operation {
  274. NSData *cachedData = (self.memoryCache)[[operation uniqueIdentifier]];
  275. if(cachedData) return cachedData;
  276. NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:[operation uniqueIdentifier]];
  277. if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  278. cachedData = [NSData dataWithContentsOfFile:filePath];
  279. [self saveCacheData:cachedData forKey:[operation uniqueIdentifier]]; // bring it back to the in-memory cache
  280. return cachedData;
  281. }
  282. return nil;
  283. }
  284. -(void) enqueueOperation:(MKNetworkOperation*) operation {
  285. [self enqueueOperation:operation forceReload:NO];
  286. }
  287. -(void) enqueueOperation:(MKNetworkOperation*) operation forceReload:(BOOL) forceReload {
  288. NSParameterAssert(operation != nil);
  289. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  290. [operation setCacheHandler:^(MKNetworkOperation* completedCacheableOperation) {
  291. // if this is not called, the request would have been a non cacheable request
  292. //completedCacheableOperation.cacheHeaders;
  293. NSString *uniqueId = [completedCacheableOperation uniqueIdentifier];
  294. [self saveCacheData:[completedCacheableOperation responseData]
  295. forKey:uniqueId];
  296. (self.cacheInvalidationParams)[uniqueId] = completedCacheableOperation.cacheHeaders;
  297. }];
  298. __block double expiryTimeInSeconds = 0.0f;
  299. if([operation isCacheable]) {
  300. NSData *cachedData = [self cachedDataForOperation:operation];
  301. if(cachedData) {
  302. dispatch_async(dispatch_get_main_queue(), ^{
  303. // Jump back to the original thread here since setCachedData updates the main thread
  304. [operation setCachedData:cachedData];
  305. });
  306. if(!forceReload) {
  307. NSString *uniqueId = [operation uniqueIdentifier];
  308. NSMutableDictionary *savedCacheHeaders = (self.cacheInvalidationParams)[uniqueId];
  309. // there is a cached version.
  310. // this means, the current operation is a "GET"
  311. if(savedCacheHeaders) {
  312. NSString *expiresOn = savedCacheHeaders[@"Expires"];
  313. dispatch_sync(self.operationQueue, ^{
  314. NSDate *expiresOnDate = [NSDate dateFromRFC1123:expiresOn];
  315. if (!expiresOnDate) {
  316. expiryTimeInSeconds = operation.imageCacheDuration;
  317. }
  318. else{
  319. expiryTimeInSeconds = [expiresOnDate timeIntervalSinceNow];
  320. }
  321. });
  322. [operation updateOperationBasedOnPreviousHeaders:savedCacheHeaders];
  323. }
  324. }
  325. }
  326. dispatch_sync(self.operationQueue, ^{
  327. NSArray *operations = _sharedNetworkQueue.operations;
  328. NSUInteger index = [operations indexOfObject:operation];
  329. BOOL operationFinished = NO;
  330. if(index != NSNotFound) {
  331. MKNetworkOperation *queuedOperation = (MKNetworkOperation*) (operations)[index];
  332. operationFinished = [queuedOperation isFinished];
  333. if(!operationFinished)
  334. [queuedOperation updateHandlersFromOperation:operation];
  335. }
  336. if(expiryTimeInSeconds <= 0 || forceReload || operationFinished)
  337. [_sharedNetworkQueue addOperation:operation];
  338. // else don't do anything
  339. });
  340. } else {
  341. [_sharedNetworkQueue addOperation:operation];
  342. }
  343. if([self.reachability currentReachabilityStatus] == NotReachable)
  344. [self freezeOperations];
  345. });
  346. }
  347. - (MKNetworkOperation*)imageAtURL:(NSURL *)url completionHandler:(MKNKImageBlock) imageFetchedBlock errorHandler:(MKNKResponseErrorBlock) errorBlock {
  348. #ifdef DEBUG
  349. // I could enable caching here, but that hits performance and inturn affects table view scrolling
  350. // if imageAtURL is called for loading thumbnails.
  351. if(![self isCacheEnabled]) DLog(@"imageAtURL:onCompletion: requires caching to be enabled.")
  352. #endif
  353. if (url == nil) {
  354. return nil;
  355. }
  356. MKNetworkOperation *op = [self operationWithURLString:[url absoluteString]];
  357. [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
  358. imageFetchedBlock([completedOperation responseImage],
  359. url,
  360. [completedOperation isCachedResponse]);
  361. } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
  362. errorBlock(completedOperation, error);
  363. }];
  364. [self enqueueOperation:op];
  365. return op;
  366. }
  367. #if TARGET_OS_IPHONE
  368. - (MKNetworkOperation*)imageAtURL:(NSURL *)url size:(CGSize) size completionHandler:(MKNKImageBlock) imageFetchedBlock errorHandler:(MKNKResponseErrorBlock) errorBlock {
  369. #ifdef DEBUG
  370. // I could enable caching here, but that hits performance and inturn affects table view scrolling
  371. // if imageAtURL is called for loading thumbnails.
  372. if(![self isCacheEnabled]) DLog(@"imageAtURL:size:onCompletion: requires caching to be enabled.")
  373. #endif
  374. if (url == nil) {
  375. return nil;
  376. }
  377. MKNetworkOperation *op = [self operationWithURLString:[url absoluteString]];
  378. [op addCompletionHandler:^(MKNetworkOperation *completedOperation) {
  379. [completedOperation decompressedResponseImageOfSize:size
  380. completionHandler:^(UIImage *decompressedImage) {
  381. imageFetchedBlock(decompressedImage,
  382. url,
  383. [completedOperation isCachedResponse]);
  384. }];
  385. } errorHandler:^(MKNetworkOperation *completedOperation, NSError *error) {
  386. errorBlock(completedOperation, error);
  387. DLog(@"%@", error);
  388. }];
  389. [self enqueueOperation:op];
  390. return op;
  391. }
  392. - (MKNetworkOperation*)imageAtURL:(NSURL *)url size:(CGSize) size onCompletion:(MKNKImageBlock) imageFetchedBlock {
  393. return [self imageAtURL:url size:size completionHandler:imageFetchedBlock errorHandler:^(MKNetworkOperation* op, NSError* error){}];
  394. }
  395. #endif
  396. - (MKNetworkOperation*)imageAtURL:(NSURL *)url onCompletion:(MKNKImageBlock) imageFetchedBlock
  397. {
  398. return [self imageAtURL:url completionHandler:imageFetchedBlock errorHandler:^(MKNetworkOperation* op, NSError* error){}];
  399. }
  400. #pragma mark -
  401. #pragma mark Cache related
  402. -(NSString*) cacheDirectoryName {
  403. static NSString *cacheDirectoryName = nil;
  404. static dispatch_once_t onceToken;
  405. dispatch_once(&onceToken, ^{
  406. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
  407. NSString *documentsDirectory = paths[0];
  408. cacheDirectoryName = [documentsDirectory stringByAppendingPathComponent:MKNETWORKCACHE_DEFAULT_DIRECTORY];
  409. });
  410. return cacheDirectoryName;
  411. }
  412. -(int) cacheMemoryCost {
  413. return MKNETWORKCACHE_DEFAULT_COST;
  414. }
  415. -(void) saveCache {
  416. for(NSString *cacheKey in [self.memoryCache allKeys])
  417. {
  418. NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:cacheKey];
  419. if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  420. NSError *error = nil;
  421. [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
  422. ELog(error);
  423. }
  424. [(self.memoryCache)[cacheKey] writeToFile:filePath atomically:YES];
  425. }
  426. [self.memoryCache removeAllObjects];
  427. [self.memoryCacheKeys removeAllObjects];
  428. NSString *cacheInvalidationPlistFilePath = [[self cacheDirectoryName] stringByAppendingPathExtension:@"plist"];
  429. [self.cacheInvalidationParams writeToFile:cacheInvalidationPlistFilePath atomically:YES];
  430. }
  431. -(void) saveCacheData:(NSData*) data forKey:(NSString*) cacheDataKey
  432. {
  433. dispatch_async(self.backgroundCacheQueue, ^{
  434. (self.memoryCache)[cacheDataKey] = data;
  435. NSUInteger index = [self.memoryCacheKeys indexOfObject:cacheDataKey];
  436. if(index != NSNotFound)
  437. [self.memoryCacheKeys removeObjectAtIndex:index];
  438. [self.memoryCacheKeys insertObject:cacheDataKey atIndex:0]; // remove it and insert it at start
  439. if([self.memoryCacheKeys count] >= (NSUInteger)[self cacheMemoryCost])
  440. {
  441. NSString *lastKey = [self.memoryCacheKeys lastObject];
  442. NSData *data2 = (self.memoryCache)[lastKey];
  443. NSString *filePath = [[self cacheDirectoryName] stringByAppendingPathComponent:lastKey];
  444. if([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
  445. NSError *error = nil;
  446. [[NSFileManager defaultManager] removeItemAtPath:filePath error:&error];
  447. ELog(error);
  448. }
  449. [data2 writeToFile:filePath atomically:YES];
  450. [self.memoryCacheKeys removeLastObject];
  451. [self.memoryCache removeObjectForKey:lastKey];
  452. }
  453. });
  454. }
  455. /*
  456. - (BOOL) dataOldness:(NSString*) imagePath
  457. {
  458. NSDictionary *attributes = [[NSFileManager defaultManager] attributesOfItemAtPath:imagePath error:nil];
  459. NSDate *creationDate = [attributes valueForKey:NSFileCreationDate];
  460. return abs([creationDate timeIntervalSinceNow]);
  461. }*/
  462. -(BOOL) isCacheEnabled {
  463. BOOL isDir = NO;
  464. BOOL isCachingEnabled = [[NSFileManager defaultManager] fileExistsAtPath:[self cacheDirectoryName] isDirectory:&isDir];
  465. return isCachingEnabled;
  466. }
  467. -(void) useCache {
  468. self.memoryCache = [NSMutableDictionary dictionaryWithCapacity:[self cacheMemoryCost]];
  469. self.memoryCacheKeys = [NSMutableArray arrayWithCapacity:[self cacheMemoryCost]];
  470. self.cacheInvalidationParams = [NSMutableDictionary dictionary];
  471. NSString *cacheDirectory = [self cacheDirectoryName];
  472. BOOL isDirectory = YES;
  473. BOOL folderExists = [[NSFileManager defaultManager] fileExistsAtPath:cacheDirectory isDirectory:&isDirectory] && isDirectory;
  474. if (!folderExists)
  475. {
  476. NSError *error = nil;
  477. [[NSFileManager defaultManager] createDirectoryAtPath:cacheDirectory withIntermediateDirectories:YES attributes:nil error:&error];
  478. }
  479. NSString *cacheInvalidationPlistFilePath = [cacheDirectory stringByAppendingPathExtension:@"plist"];
  480. BOOL fileExists = [[NSFileManager defaultManager] fileExistsAtPath:cacheInvalidationPlistFilePath];
  481. if (fileExists)
  482. {
  483. self.cacheInvalidationParams = [NSMutableDictionary dictionaryWithContentsOfFile:cacheInvalidationPlistFilePath];
  484. }
  485. #if TARGET_OS_IPHONE
  486. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  487. name:UIApplicationDidReceiveMemoryWarningNotification
  488. object:nil];
  489. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  490. name:UIApplicationDidEnterBackgroundNotification
  491. object:nil];
  492. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  493. name:UIApplicationWillTerminateNotification
  494. object:nil];
  495. #elif TARGET_OS_MAC
  496. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  497. name:NSApplicationWillHideNotification
  498. object:nil];
  499. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  500. name:NSApplicationWillResignActiveNotification
  501. object:nil];
  502. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(saveCache)
  503. name:NSApplicationWillTerminateNotification
  504. object:nil];
  505. #endif
  506. }
  507. -(void) emptyCache {
  508. [self saveCache]; // ensures that invalidation params are written to disk properly
  509. NSError *error = nil;
  510. NSArray *directoryContents = [[NSFileManager defaultManager]
  511. contentsOfDirectoryAtPath:[self cacheDirectoryName] error:&error];
  512. // if(error) DLog(@"%@", error);
  513. error = nil;
  514. for(NSString *fileName in directoryContents) {
  515. NSString *path = [[self cacheDirectoryName] stringByAppendingPathComponent:fileName];
  516. [[NSFileManager defaultManager] removeItemAtPath:path error:&error];
  517. // if(error) DLog(@"%@", error);
  518. }
  519. error = nil;
  520. NSString *cacheInvalidationPlistFilePath = [[self cacheDirectoryName] stringByAppendingPathExtension:@"plist"];
  521. [[NSFileManager defaultManager] removeItemAtPath:cacheInvalidationPlistFilePath error:&error];
  522. // if(error) DLog(@"%@", error);
  523. }
  524. @end