PINDiskCache.h 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498
  1. // PINCache is a modified version of TMCache
  2. // Modifications by Garrett Moon
  3. // Copyright (c) 2015 Pinterest. All rights reserved.
  4. #import <Foundation/Foundation.h>
  5. #import "Nullability.h"
  6. #import "PINCacheObjectSubscripting.h"
  7. NS_ASSUME_NONNULL_BEGIN
  8. @class PINDiskCache;
  9. @class PINOperationQueue;
  10. /**
  11. A callback block which provides only the cache as an argument
  12. */
  13. typedef void (^PINDiskCacheBlock)(PINDiskCache *cache);
  14. /**
  15. A callback block which provides the cache, key and object as arguments
  16. */
  17. typedef void (^PINDiskCacheObjectBlock)(PINDiskCache *cache, NSString *key, id <NSCoding> __nullable object);
  18. /**
  19. A callback block which provides the key and fileURL of the object
  20. */
  21. typedef void (^PINDiskCacheFileURLBlock)(NSString *key, NSURL * __nullable fileURL);
  22. /**
  23. A callback block which provides a BOOL value as argument
  24. */
  25. typedef void (^PINDiskCacheContainsBlock)(BOOL containsObject);
  26. /**
  27. * A block used to serialize object before writing to disk
  28. *
  29. * @param object Object to serialize
  30. * @param key The key associated with the object
  31. *
  32. * @return Serialized object representation
  33. */
  34. typedef NSData* __nonnull(^PINDiskCacheSerializerBlock)(id<NSCoding> object, NSString *key);
  35. /**
  36. * A block used to deserialize objects
  37. *
  38. * @param data Serialized object data
  39. * @param key The key associated with the object
  40. *
  41. * @return Deserialized object
  42. */
  43. typedef id<NSCoding> __nonnull(^PINDiskCacheDeserializerBlock)(NSData* data, NSString *key);
  44. /**
  45. `PINDiskCache` is a thread safe key/value store backed by the file system. It accepts any object conforming
  46. to the `NSCoding` protocol, which includes the basic Foundation data types and collection classes and also
  47. many UIKit classes, notably `UIImage`. All work is performed on a serial queue shared by all instances in
  48. the app, and archiving is handled by `NSKeyedArchiver`. This is a particular advantage for `UIImage` because
  49. it skips `UIImagePNGRepresentation()` and retains information like scale and orientation.
  50. The designated initializer for `PINDiskCache` is <initWithName:>. The <name> string is used to create a directory
  51. under Library/Caches that scopes disk access for this instance. Multiple instances with the same name are *not*
  52. allowed as they would conflict with each other.
  53. Unless otherwise noted, all properties and methods are safe to access from any thread at any time. All blocks
  54. will cause the queue to wait, making it safe to access and manipulate the actual cache files on disk for the
  55. duration of the block.
  56. Because this cache is bound by disk I/O it can be much slower than <PINMemoryCache>, although values stored in
  57. `PINDiskCache` persist after application relaunch. Using <PINCache> is recommended over using `PINDiskCache`
  58. by itself, as it adds a fast layer of additional memory caching while still writing to disk.
  59. All access to the cache is dated so the that the least-used objects can be trimmed first. Setting an optional
  60. <ageLimit> will trigger a GCD timer to periodically to trim the cache with <trimToDate:>.
  61. */
  62. @interface PINDiskCache : NSObject <PINCacheObjectSubscripting>
  63. #pragma mark -
  64. /// @name Core
  65. /**
  66. The name of this cache, used to create a directory under Library/Caches and also appearing in stack traces.
  67. */
  68. @property (readonly) NSString *name;
  69. /**
  70. The URL of the directory used by this cache, usually `Library/Caches/com.pinterest.PINDiskCache.(name)`
  71. @warning Do not interact with files under this URL except in <lockFileAccessWhileExecutingBlock:> or
  72. <synchronouslyLockFileAccessWhileExecutingBlock:>.
  73. */
  74. @property (readonly) NSURL *cacheURL;
  75. /**
  76. The total number of bytes used on disk, as reported by `NSURLTotalFileAllocatedSizeKey`.
  77. @warning This property should only be read from a call to <synchronouslyLockFileAccessWhileExecutingBlock:> or
  78. its asynchronous equivalent <lockFileAccessWhileExecutingBlock:>
  79. For example:
  80. // some background thread
  81. __block NSUInteger byteCount = 0;
  82. [_diskCache synchronouslyLockFileAccessWhileExecutingBlock:^(PINDiskCache *diskCache) {
  83. byteCount = diskCache.byteCount;
  84. }];
  85. */
  86. @property (readonly) NSUInteger byteCount;
  87. /**
  88. The maximum number of bytes allowed on disk. This value is checked every time an object is set, if the written
  89. size exceeds the limit a trim call is queued. Defaults to `0.0`, meaning no practical limit.
  90. */
  91. @property (assign) NSUInteger byteLimit;
  92. /**
  93. The maximum number of seconds an object is allowed to exist in the cache. Setting this to a value
  94. greater than `0.0` will start a recurring GCD timer with the same period that calls <trimToDate:>.
  95. Setting it back to `0.0` will stop the timer. Defaults to `0.0`, meaning no limit.
  96. */
  97. @property (assign) NSTimeInterval ageLimit;
  98. /**
  99. Extension for all cache files on disk. Defaults to no extension.
  100. */
  101. @property (readonly) NSString * __nullable fileExtension;
  102. /**
  103. The writing protection option used when writing a file on disk. This value is used every time an object is set.
  104. NSDataWritingAtomic and NSDataWritingWithoutOverwriting are ignored if set
  105. Defaults to NSDataWritingFileProtectionNone.
  106. @warning Only new files are affected by the new writing protection. If you need all files to be affected,
  107. you'll have to purge and set the objects back to the cache
  108. Only available on iOS
  109. */
  110. #if TARGET_OS_IPHONE
  111. @property (assign) NSDataWritingOptions writingProtectionOption;
  112. #endif
  113. /**
  114. If ttlCache is YES, the cache behaves like a ttlCache. This means that once an object enters the
  115. cache, it only lives as long as self.ageLimit. This has the following implications:
  116. - Accessing an object in the cache does not extend that object's lifetime in the cache
  117. - When attempting to access an object in the cache that has lived longer than self.ageLimit,
  118. the cache will behave as if the object does not exist
  119. */
  120. @property (nonatomic, assign, getter=isTTLCache) BOOL ttlCache;
  121. #pragma mark -
  122. /// @name Event Blocks
  123. /**
  124. A block to be executed just before an object is added to the cache. The queue waits during execution.
  125. */
  126. @property (copy) PINDiskCacheObjectBlock __nullable willAddObjectBlock;
  127. /**
  128. A block to be executed just before an object is removed from the cache. The queue waits during execution.
  129. */
  130. @property (copy) PINDiskCacheObjectBlock __nullable willRemoveObjectBlock;
  131. /**
  132. A block to be executed just before all objects are removed from the cache as a result of <removeAllObjects:>.
  133. The queue waits during execution.
  134. */
  135. @property (copy) PINDiskCacheBlock __nullable willRemoveAllObjectsBlock;
  136. /**
  137. A block to be executed just after an object is added to the cache. The queue waits during execution.
  138. */
  139. @property (copy) PINDiskCacheObjectBlock __nullable didAddObjectBlock;
  140. /**
  141. A block to be executed just after an object is removed from the cache. The queue waits during execution.
  142. */
  143. @property (copy) PINDiskCacheObjectBlock __nullable didRemoveObjectBlock;
  144. /**
  145. A block to be executed just after all objects are removed from the cache as a result of <removeAllObjects:>.
  146. The queue waits during execution.
  147. */
  148. @property (copy) PINDiskCacheBlock __nullable didRemoveAllObjectsBlock;
  149. #pragma mark -
  150. /// @name Initialization
  151. /**
  152. A shared cache.
  153. @result The shared singleton cache instance.
  154. */
  155. + (instancetype)sharedCache;
  156. /**
  157. Empties the trash with `DISPATCH_QUEUE_PRIORITY_BACKGROUND`. Does not use lock.
  158. */
  159. + (void)emptyTrash;
  160. - (instancetype)init NS_UNAVAILABLE;
  161. /**
  162. Multiple instances with the same name are allowed and can safely access
  163. the same data on disk thanks to the magic of seriality.
  164. @see name
  165. @param name The name of the cache.
  166. @result A new cache with the specified name.
  167. */
  168. - (instancetype)initWithName:(nonnull NSString *)name;
  169. /**
  170. Multiple instances with the same name are allowed and can safely access
  171. the same data on disk thanks to the magic of seriality.
  172. @see name
  173. @param name The name of the cache.
  174. @param fileExtension The file extension for files on disk.
  175. @result A new cache with the specified name.
  176. */
  177. - (instancetype)initWithName:(nonnull NSString *)name fileExtension:(nullable NSString *)fileExtension;
  178. /**
  179. Multiple instances with the same name are allowed and can safely access
  180. the same data on disk thanks to the magic of seriality.
  181. @see name
  182. @param name The name of the cache.
  183. @param rootPath The path of the cache.
  184. @param fileExtension The file extension for files on disk.
  185. @result A new cache with the specified name.
  186. */
  187. - (instancetype)initWithName:(nonnull NSString *)name rootPath:(nonnull NSString *)rootPath fileExtension:(nullable NSString *)fileExtension;
  188. /**
  189. @see name
  190. @param name The name of the cache.
  191. @param rootPath The path of the cache.
  192. @param serializer A block used to serialize object. If nil provided, default NSKeyedArchiver serialized will be used.
  193. @param deserializer A block used to deserialize object. If nil provided, default NSKeyedUnarchiver serialized will be used.
  194. @param fileExtension The file extension for files on disk.
  195. @result A new cache with the specified name.
  196. */
  197. - (instancetype)initWithName:(nonnull NSString *)name rootPath:(nonnull NSString *)rootPath serializer:(nullable PINDiskCacheSerializerBlock)serializer deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer fileExtension:(nullable NSString *)fileExtension;
  198. /**
  199. The designated initializer allowing you to override default NSKeyedArchiver/NSKeyedUnarchiver serialization.
  200. @see name
  201. @param name The name of the cache.
  202. @param rootPath The path of the cache.
  203. @param serializer A block used to serialize object. If nil provided, default NSKeyedArchiver serialized will be used.
  204. @param deserializer A block used to deserialize object. If nil provided, default NSKeyedUnarchiver serialized will be used.
  205. @param fileExtension The file extension for files on disk.
  206. @param operationQueue A PINOperationQueue to run asynchronous operations
  207. @result A new cache with the specified name.
  208. */
  209. - (instancetype)initWithName:(nonnull NSString *)name rootPath:(nonnull NSString *)rootPath serializer:(nullable PINDiskCacheSerializerBlock)serializer deserializer:(nullable PINDiskCacheDeserializerBlock)deserializer fileExtension:(nullable NSString *)fileExtension operationQueue:(nonnull PINOperationQueue *)operationQueue NS_DESIGNATED_INITIALIZER;
  210. #pragma mark -
  211. /// @name Asynchronous Methods
  212. /**
  213. Locks access to ivars and allows safe interaction with files on disk. This method returns immediately.
  214. @warning Calling synchronous methods on the diskCache inside this block will likely cause a deadlock.
  215. @param block A block to be executed when a lock is available.
  216. */
  217. - (void)lockFileAccessWhileExecutingBlock:(nullable PINDiskCacheBlock)block;
  218. /**
  219. This method determines whether an object is present for the given key in the cache. This method returns immediately
  220. and executes the passed block after the object is available, potentially in parallel with other blocks on the
  221. <concurrentQueue>.
  222. @see containsObjectForKey:
  223. @param key The key associated with the object.
  224. @param block A block to be executed concurrently after the containment check happened
  225. */
  226. - (void)containsObjectForKey:(NSString *)key block:(PINDiskCacheContainsBlock)block;
  227. /**
  228. Retrieves the object for the specified key. This method returns immediately and executes the passed
  229. block as soon as the object is available.
  230. @param key The key associated with the requested object.
  231. @param block A block to be executed serially when the object is available.
  232. */
  233. - (void)objectForKey:(NSString *)key block:(nullable PINDiskCacheObjectBlock)block;
  234. /**
  235. Retrieves the fileURL for the specified key without actually reading the data from disk. This method
  236. returns immediately and executes the passed block as soon as the object is available.
  237. @warning Access is protected for the duration of the block, but to maintain safe disk access do not
  238. access this fileURL after the block has ended.
  239. @warning The PINDiskCache lock is held while block is executed. Any synchronous calls to the diskcache
  240. or a cache which owns the instance of the disk cache are likely to cause a deadlock. This is why the block is
  241. *not* passed the instance of the disk cache. You should also avoid doing extensive work while this
  242. lock is held.
  243. @param key The key associated with the requested object.
  244. @param block A block to be executed serially when the file URL is available.
  245. */
  246. - (void)fileURLForKey:(NSString *)key block:(nullable PINDiskCacheFileURLBlock)block;
  247. /**
  248. Stores an object in the cache for the specified key. This method returns immediately and executes the
  249. passed block as soon as the object has been stored.
  250. @param object An object to store in the cache.
  251. @param key A key to associate with the object. This string will be copied.
  252. @param block A block to be executed serially after the object has been stored, or nil.
  253. */
  254. - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(nullable PINDiskCacheObjectBlock)block;
  255. /**
  256. Removes the object for the specified key. This method returns immediately and executes the passed block
  257. as soon as the object has been removed.
  258. @param key The key associated with the object to be removed.
  259. @param block A block to be executed serially after the object has been removed, or nil.
  260. */
  261. - (void)removeObjectForKey:(NSString *)key block:(nullable PINDiskCacheObjectBlock)block;
  262. /**
  263. Removes all objects from the cache that have not been used since the specified date.
  264. This method returns immediately and executes the passed block as soon as the cache has been trimmed.
  265. @param date Objects that haven't been accessed since this date are removed from the cache.
  266. @param block A block to be executed serially after the cache has been trimmed, or nil.
  267. */
  268. - (void)trimToDate:(NSDate *)date block:(nullable PINDiskCacheBlock)block;
  269. /**
  270. Removes objects from the cache, largest first, until the cache is equal to or smaller than the specified byteCount.
  271. This method returns immediately and executes the passed block as soon as the cache has been trimmed.
  272. @param byteCount The cache will be trimmed equal to or smaller than this size.
  273. @param block A block to be executed serially after the cache has been trimmed, or nil.
  274. */
  275. - (void)trimToSize:(NSUInteger)byteCount block:(nullable PINDiskCacheBlock)block;
  276. /**
  277. Removes objects from the cache, ordered by date (least recently used first), until the cache is equal to or smaller
  278. than the specified byteCount. This method returns immediately and executes the passed block as soon as the cache has
  279. been trimmed.
  280. @param byteCount The cache will be trimmed equal to or smaller than this size.
  281. @param block A block to be executed serially after the cache has been trimmed, or nil.
  282. */
  283. - (void)trimToSizeByDate:(NSUInteger)byteCount block:(nullable PINDiskCacheBlock)block;
  284. /**
  285. Removes all objects from the cache. This method returns immediately and executes the passed block as soon as the
  286. cache has been cleared.
  287. @param block A block to be executed serially after the cache has been cleared, or nil.
  288. */
  289. - (void)removeAllObjects:(nullable PINDiskCacheBlock)block;
  290. /**
  291. Loops through all objects in the cache (reads and writes are suspended during the enumeration). Data is not actually
  292. read from disk, the `object` parameter of the block will be `nil` but the `fileURL` will be available.
  293. This method returns immediately.
  294. @param block A block to be executed for every object in the cache.
  295. @param completionBlock An optional block to be executed after the enumeration is complete.
  296. @warning The PINDiskCache lock is held while block is executed. Any synchronous calls to the diskcache
  297. or a cache which owns the instance of the disk cache are likely to cause a deadlock. This is why the block is
  298. *not* passed the instance of the disk cache. You should also avoid doing extensive work while this
  299. lock is held.
  300. */
  301. - (void)enumerateObjectsWithBlock:(PINDiskCacheFileURLBlock)block completionBlock:(nullable PINDiskCacheBlock)completionBlock;
  302. #pragma mark -
  303. /// @name Synchronous Methods
  304. /**
  305. Locks access to ivars and allows safe interaction with files on disk. This method only returns once the block
  306. has been run.
  307. @warning Calling synchronous methods on the diskCache inside this block will likely cause a deadlock.
  308. @param block A block to be executed when a lock is available.
  309. */
  310. - (void)synchronouslyLockFileAccessWhileExecutingBlock:(nullable PINDiskCacheBlock)block;
  311. /**
  312. This method determines whether an object is present for the given key in the cache.
  313. @see containsObjectForKey:block:
  314. @param key The key associated with the object.
  315. @result YES if an object is present for the given key in the cache, otherwise NO.
  316. */
  317. - (BOOL)containsObjectForKey:(NSString *)key;
  318. /**
  319. Retrieves the object for the specified key. This method blocks the calling thread until the
  320. object is available.
  321. @see objectForKey:block:
  322. @param key The key associated with the object.
  323. @result The object for the specified key.
  324. */
  325. - (__nullable id <NSCoding>)objectForKey:(NSString *)key;
  326. /**
  327. Retrieves the file URL for the specified key. This method blocks the calling thread until the
  328. url is available. Do not use this URL anywhere except with <lockFileAccessWhileExecutingBlock:>. This method probably
  329. shouldn't even exist, just use the asynchronous one.
  330. @see fileURLForKey:block:
  331. @param key The key associated with the object.
  332. @result The file URL for the specified key.
  333. */
  334. - (nullable NSURL *)fileURLForKey:(nullable NSString *)key;
  335. /**
  336. Stores an object in the cache for the specified key. This method blocks the calling thread until
  337. the object has been stored.
  338. @see setObject:forKey:block:
  339. @param object An object to store in the cache.
  340. @param key A key to associate with the object. This string will be copied.
  341. */
  342. - (void)setObject:(id <NSCoding>)object forKey:(NSString *)key;
  343. /**
  344. Removes the object for the specified key. This method blocks the calling thread until the object
  345. has been removed.
  346. @param key The key associated with the object to be removed.
  347. */
  348. - (void)removeObjectForKey:(NSString *)key;
  349. /**
  350. Removes all objects from the cache that have not been used since the specified date.
  351. This method blocks the calling thread until the cache has been trimmed.
  352. @param date Objects that haven't been accessed since this date are removed from the cache.
  353. */
  354. - (void)trimToDate:(nullable NSDate *)date;
  355. /**
  356. Removes objects from the cache, largest first, until the cache is equal to or smaller than the
  357. specified byteCount. This method blocks the calling thread until the cache has been trimmed.
  358. @param byteCount The cache will be trimmed equal to or smaller than this size.
  359. */
  360. - (void)trimToSize:(NSUInteger)byteCount;
  361. /**
  362. Removes objects from the cache, ordered by date (least recently used first), until the cache is equal to or
  363. smaller than the specified byteCount. This method blocks the calling thread until the cache has been trimmed.
  364. @param byteCount The cache will be trimmed equal to or smaller than this size.
  365. */
  366. - (void)trimToSizeByDate:(NSUInteger)byteCount;
  367. /**
  368. Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared.
  369. */
  370. - (void)removeAllObjects;
  371. /**
  372. Loops through all objects in the cache (reads and writes are suspended during the enumeration). Data is not actually
  373. read from disk, the `object` parameter of the block will be `nil` but the `fileURL` will be available.
  374. This method blocks the calling thread until all objects have been enumerated.
  375. @param block A block to be executed for every object in the cache.
  376. @warning Do not call this method within the event blocks (<didRemoveObjectBlock>, etc.)
  377. Instead use the asynchronous version, <enumerateObjectsWithBlock:completionBlock:>.
  378. @warning The PINDiskCache lock is held while block is executed. Any synchronous calls to the diskcache
  379. or a cache which owns the instance of the disk cache are likely to cause a deadlock. This is why the block is
  380. *not* passed the instance of the disk cache. You should also avoid doing extensive work while this
  381. lock is held.
  382. */
  383. - (void)enumerateObjectsWithBlock:(nullable PINDiskCacheFileURLBlock)block;
  384. @end
  385. NS_ASSUME_NONNULL_END