KHolder.m 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273
  1. //
  2. // KHolder.m
  3. // KacheDemo
  4. //
  5. // Created by jiajun on 7/25/12.
  6. // Copyright (c) 2012 __MyCompanyName__. All rights reserved.
  7. //
  8. #define Kache_Objects_Disk_Path @"Caches/Kache_objects"
  9. #import "KConfig.h"
  10. #import "KHolder.h"
  11. #import "KObject.h"
  12. #import "KPool.h"
  13. #import "KQueue.h"
  14. #import "KUtil.h"
  15. @interface KHolder ()
  16. // 正在进行归档的状态位
  17. @property (assign, atomic) BOOL archiving;
  18. @property (assign, atomic) BOOL cleaning;
  19. @property (strong, nonatomic) NSFileManager *fileManager;
  20. @property (strong, atomic) NSMutableArray *keys;
  21. @property (assign, nonatomic) NSUInteger size;
  22. @property (strong, nonatomic) NSMutableDictionary *objects;
  23. @property (strong, nonatomic) NSString *path;
  24. // 把数据写到磁盘
  25. - (void)archiveData;
  26. - (void)archiveAllData;
  27. - (void)cleanExpiredObjects;
  28. @end
  29. @implementation KHolder
  30. @synthesize archiving = _archiving;
  31. @synthesize cleaning = _cleaning;
  32. @synthesize fileManager = _fileManager;
  33. // 缓存Key列表
  34. @synthesize keys = _keys;
  35. // 缓存大小
  36. @synthesize size = _size;
  37. // 缓存内容
  38. @synthesize objects = _objects;
  39. @synthesize path = _path;
  40. #pragma mark - init
  41. - (id)init
  42. {
  43. self = [self initWithToken:@"_KacheDefault"];
  44. if (self) {
  45. return self;
  46. }
  47. return nil;
  48. }
  49. - (id)initWithToken:(NSString *)token
  50. {
  51. self = [super init];
  52. if (self) {
  53. self.objects = [[NSMutableDictionary alloc] init];
  54. self.keys = [[NSMutableArray alloc] init];
  55. self.size = 0;
  56. NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
  57. NSString *libDirectory = [paths objectAtIndex:0];
  58. self.path = [libDirectory stringByAppendingPathComponent:[Kache_Objects_Disk_Path stringByAppendingPathExtension:token]];
  59. return self;
  60. }
  61. return nil;
  62. }
  63. #pragma mark - private
  64. - (NSFileManager *)fileManager
  65. {
  66. return [NSFileManager defaultManager];
  67. }
  68. - (void)archiveData
  69. {
  70. self.archiving = YES;
  71. // dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  72. BOOL isDirectory = NO;
  73. if (! [[NSFileManager defaultManager] fileExistsAtPath:self.path isDirectory:&isDirectory]) {
  74. [self.fileManager createDirectoryAtPath:self.self.path
  75. withIntermediateDirectories:YES
  76. attributes:nil
  77. error:nil];
  78. }
  79. NSMutableArray *copiedKeys = [self.keys mutableCopy];
  80. while (0 < [copiedKeys count]) {
  81. // 归档至阈值一半的数据
  82. if ((ARCHIVING_THRESHOLD / 2) >= self.size) {
  83. break;
  84. }
  85. NSString *key = [copiedKeys lastObject];
  86. NSString *filePath = [self.path stringByAppendingPathComponent:key];
  87. NSData *data = [self.objects objectForKey:key];
  88. [data writeToFile:filePath atomically:YES];
  89. self.size -= data.length;
  90. [copiedKeys removeLastObject];
  91. [self.objects removeObjectForKey:key];
  92. }
  93. copiedKeys = nil;
  94. self.archiving = NO;
  95. // });
  96. }
  97. - (void)archiveAllData
  98. {
  99. self.archiving = YES;
  100. // dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
  101. BOOL isDirectory = NO;
  102. if (! [[NSFileManager defaultManager] fileExistsAtPath:self.path isDirectory:&isDirectory]) {
  103. [self.fileManager createDirectoryAtPath:self.self.path
  104. withIntermediateDirectories:YES
  105. attributes:nil
  106. error:nil];
  107. }
  108. NSMutableArray *copiedKeys = [self.keys mutableCopy];
  109. while (0 < [copiedKeys count]) {
  110. NSString *key = [copiedKeys lastObject];
  111. NSString *filePath = [self.path stringByAppendingPathComponent:key];
  112. NSData *data = [self.objects objectForKey:key];
  113. [data writeToFile:filePath atomically:YES];
  114. self.size -= data.length;
  115. [copiedKeys removeLastObject];
  116. [self.objects removeObjectForKey:key];
  117. }
  118. copiedKeys = nil;
  119. self.archiving = NO;
  120. // });
  121. }
  122. - (void)cleanExpiredObjects
  123. {
  124. self.cleaning = YES;
  125. // dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  126. if (self.keys && 0 < [self.keys count]) {
  127. for (int i = 0; i < [self.keys count] - 1; i ++) {
  128. NSString *tmpKey = [self.keys objectAtIndex:i];
  129. KObject *leftObject = [self objectForKey:tmpKey];
  130. if ([leftObject expiredTimestamp] < [KUtil nowTimestamp]) {
  131. [self removeObjectForKey:tmpKey];
  132. }
  133. else {
  134. break;
  135. }
  136. }
  137. }
  138. self.cleaning = NO;
  139. // });
  140. }
  141. #pragma mark - public
  142. - (void)removeObjectForKey:(NSString *)key {
  143. [self.keys removeObject:key];
  144. if ([[self.objects allKeys] containsObject:key]) {
  145. [self.objects removeObjectForKey:key];
  146. }
  147. else {
  148. NSString *filePath = [self.path stringByAppendingPathComponent:key];
  149. [self.fileManager removeItemAtPath:filePath error:nil];
  150. }
  151. }
  152. - (void)setValue:(id)value forKey:(NSString *)key expiredAfter:(NSInteger)duration
  153. {
  154. KObject *object = [[KObject alloc] initWithData:value andLifeDuration:duration];
  155. if (self.archiving) {
  156. NSString *filePath = [self.path stringByAppendingPathComponent:key];
  157. [object.data writeToFile:filePath atomically:YES];
  158. }
  159. else {
  160. [self.objects setValue:object.data forKey:key];
  161. self.size += [object size];
  162. }
  163. // TODO sort the key by expired time.
  164. [self.keys removeObject:key];
  165. if (! self.cleaning && (0 < [self.keys count])) {
  166. [self cleanExpiredObjects];
  167. for (int i = [self.keys count] - 1; i >= 0; i --) {
  168. NSString *tmpKey = [self.keys objectAtIndex:i];
  169. KObject *leftObject = [self objectForKey:tmpKey];
  170. // 过期时间越晚
  171. if ([leftObject expiredTimestamp] <= [object expiredTimestamp]) {
  172. if (([self.keys count] - 1) == i) {
  173. [self.keys addObject:key];
  174. }
  175. else {
  176. [self.keys insertObject:key atIndex:i + 1];
  177. }
  178. break;
  179. }
  180. }
  181. }
  182. if (! [self.keys containsObject:key]) {
  183. [self.keys insertObject:key atIndex:0];
  184. }
  185. // 超过阈值,归档
  186. if ((! self.archiving)
  187. && 0 < ARCHIVING_THRESHOLD
  188. && ARCHIVING_THRESHOLD < self.size) {
  189. [self archiveData];
  190. }
  191. }
  192. - (id)valueForKey:(NSString *)key
  193. {
  194. KObject *object = [self objectForKey:key];
  195. if (object && ! [object expired]) {
  196. return [object value];
  197. }
  198. // No such object.
  199. return nil;
  200. }
  201. - (KObject *)objectForKey:(NSString *)key
  202. {
  203. if (! [[self.objects allKeys] containsObject:key]) {
  204. NSString *filePath = [self.path stringByAppendingPathComponent:key];
  205. if ([self.fileManager fileExistsAtPath:filePath isDirectory:NO]) {
  206. [self.objects setValue:[NSData dataWithContentsOfFile:filePath] forKey:key];
  207. [self.fileManager removeItemAtPath:filePath error:nil];
  208. }
  209. else {
  210. return nil;
  211. }
  212. }
  213. return [[KObject alloc] initWithData:[self.objects objectForKey:key]];
  214. }
  215. // Convert object to NSDictionary.
  216. - (NSDictionary *)serialize
  217. {
  218. [self archiveAllData];
  219. return [NSDictionary dictionaryWithObjectsAndKeys:
  220. self.keys, @"keys",
  221. [NSString stringWithFormat:@"%d", self.size], @"size",
  222. nil];
  223. }
  224. // Convert NSDictionary to object.
  225. - (void)unserializeFrom:(NSDictionary *)dict
  226. {
  227. if ([[dict allKeys] containsObject:@"keys"]
  228. && [[dict allKeys] containsObject:@"meta"]) {
  229. self.keys = [dict objectForKey:@"keys"];
  230. self.size = [[dict objectForKey:@"size"] intValue];
  231. }
  232. }
  233. @end