[Original] third-party cache framework of PINCache for iOS learning, iospincache
In projects, you always need to cache some network request data to reduce the server pressure. There are also many excellent open-source solutions in the industry. Generally, the cache scheme is composed of memory cache and disk cache. The memory cache speed is small, and the disk cache capacity is large, which is slow and persistent.
1. PINCache Overview
PINCache is developed by Pinterest programmers Based on Tumblr's TMCache. The main improvement is to fix the dealock bug. TMCache is no longer maintained, and PINCache is v3.0.1 in the latest version.
PINCache is multi-thread secure and uses key-value pairs to store data. PINCache contains two similar object attributes: memory cache PINMemoryCache and disk cache PINDiskCache. Specific operations include get, set, remove, trim, these two internal objects are all used.
PINCache itself does not handle much of the specific work of caching, but is completely implemented by two internal object attributes. It only provides some synchronous or asynchronous interfaces. In iOS, when the App receives a memory warning or enters the background, PINCache can clear all the memory caches.
2. PINCache Implementation Method
The Demo of the PINCache project shows that PINCache loads data from the server, caches the data, and then performs business logic processing. If the same data is required next time, if this data exists in the cache, you do not need to initiate a network request again, but directly use this data.
PINCache adopts Disk (File) + Memory (actually NSDictionary) dual storage mode. In terms of cache data management, PINCache uses key-value pairs for management, the storage path of the Disk file is APP/Library/Caches/com. pinterest. PINDiskCache. (name), Memory objects are stored as key-value storage.
In addition to key values, key-store values, and key-delete values, PINCache can also remove cache data before a certain date, delete all caches, and limit the cache size. When the set operation is executed, the updated date and cost of the file/object will be recorded. For the date and cost attributes, corresponding APIs allow developers to clear files and memory managed by PINCache by date and cost, such as clearing cache data before a certain date and clearing cache data greater than X by cost.
In the implementation of Cache operations, PINCache adopts the dispatch_queue + dispatch_semaphore method, and dispatch_queue is a concurrent queue. To ensure thread security, use dispatch_semaphore as the lock. This article explains from bireme, the advantage of dispatch_semaphore is that it does not change the polling status and is suitable for low-frequency Disk operations. High-frequency operations such as Memory will reduce the performance.
- Synchronization operation Cache
The access thread is blocked in synchronous mode until the operation is successful:
/// @name Synchronous Methods/** This method determines whether an object is present for the given key in the cache. @see containsObjectForKey:block: @param key The key associated with the object. @result YES if an object is present for the given key in the cache, otherwise NO. */- (BOOL)containsObjectForKey:(NSString *)key;/** Retrieves the object for the specified key. This method blocks the calling thread until the object is available. Uses a lock to achieve synchronicity on the disk cache. @see objectForKey:block: @param key The key associated with the object. @result The object for the specified key. */- (__nullable id)objectForKey:(NSString *)key;/** Stores an object in the cache for the specified key. This method blocks the calling thread until the object has been set. Uses a lock to achieve synchronicity on the disk cache. @see setObject:forKey:block: @param object An object to store in the cache. @param key A key to associate with the object. This string will be copied. */- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key;/** Removes the object for the specified key. This method blocks the calling thread until the object has been removed. Uses a lock to achieve synchronicity on the disk cache. @param key The key associated with the object to be removed. */- (void)removeObjectForKey:(NSString *)key;/** Removes all objects from the cache that have not been used since the specified date. This method blocks the calling thread until the cache has been trimmed. Uses a lock to achieve synchronicity on the disk cache. @param date Objects that haven't been accessed since this date are removed from the cache. */- (void)trimToDate:(NSDate *)date;/** Removes all objects from the cache. This method blocks the calling thread until the cache has been cleared. Uses a lock to achieve synchronicity on the disk cache. */- (void)removeAllObjects;
- Asynchronous Operation Cache
After a specific asynchronous operation is completed in a concurrent queue, the result is returned based on the input block:
/// @name Asynchronous Methods/** This method determines whether an object is present for the given key in the cache. This method returns immediately and executes the passed block after the object is available, potentially in parallel with other blocks on the <concurrentQueue>. @see containsObjectForKey: @param key The key associated with the object. @param block A block to be executed concurrently after the containment check happened */- (void)containsObjectForKey:(NSString *)key block:(PINCacheObjectContainmentBlock)block;/** Retrieves the object for the specified key. This method returns immediately and executes the passed block after the object is available, potentially in parallel with other blocks on the <concurrentQueue>. @param key The key associated with the requested object. @param block A block to be executed concurrently when the object is available. */- (void)objectForKey:(NSString *)key block:(PINCacheObjectBlock)block;/** Stores an object in the cache for the specified key. This method returns immediately and executes the passed block after the object has been stored, potentially in parallel with other blocks on the <concurrentQueue>. @param object An object to store in the cache. @param key A key to associate with the object. This string will be copied. @param block A block to be executed concurrently after the object has been stored, or nil. */- (void)setObject:(id <NSCoding>)object forKey:(NSString *)key block:(nullable PINCacheObjectBlock)block;/** Removes the object for the specified key. This method returns immediately and executes the passed block after the object has been removed, potentially in parallel with other blocks on the <concurrentQueue>. @param key The key associated with the object to be removed. @param block A block to be executed concurrently after the object has been removed, or nil. */- (void)removeObjectForKey:(NSString *)key block:(nullable PINCacheObjectBlock)block;/** Removes all objects from the cache that have not been used since the specified date. This method returns immediately and executes the passed block after the cache has been trimmed, potentially in parallel with other blocks on the <concurrentQueue>. @param date Objects that haven't been accessed since this date are removed from the cache. @param block A block to be executed concurrently after the cache has been trimmed, or nil. */- (void)trimToDate:(NSDate *)date block:(nullable PINCacheBlock)block;/** Removes all objects from the cache.This method returns immediately and executes the passed block after the cache has been cleared, potentially in parallel with other blocks on the <concurrentQueue>. @param block A block to be executed concurrently after the cache has been cleared, or nil. */- (void)removeAllObjects:(nullable PINCacheBlock)block;
3. PINDiskCache
- DiskCache has the following attributes:
@ Property (readonly) NSString * name; // specify the cache name, such as MyPINCacheName. In the Library/Caches/directory, @ property (readonly) NSURL * cacheURL; // cache directory URL, such as Library/Caches/com. pinterest. PINDiskCache. myPINCacheName. This is the actual storage path @ property (readonly) NSUInteger byteCount; // disk File Size @ property (assign) NSUInteger byteLimit; // maximum byte that can be stored on disk @ property (assign) NSTimeInterval ageLimit; // maximum lifecycle of the stored file @ property (nonatomic, assign, g Etter = isTTLCache) BOOL ttlCache; // TTL mandatory storage. If YES, the access operation will not prolong the lifecycle of the cache object. the cache object of ageLimit will be treated as this object does not exist.
- In order to follow the Cocoa design philosophy, PINCache also allows users to customize the block to listen to add, remove operation events, not KVO, but like KVO:
/// @name Event Blocks/** A block to be executed just before an object is added to the cache. The queue waits during execution. */@property (copy) PINDiskCacheObjectBlock __nullable willAddObjectBlock;/** A block to be executed just before an object is removed from the cache. The queue waits during execution. */@property (copy) PINDiskCacheObjectBlock __nullable willRemoveObjectBlock;/** A block to be executed just before all objects are removed from the cache as a result of <removeAllObjects:>. The queue waits during execution. */@property (copy) PINDiskCacheBlock __nullable willRemoveAllObjectsBlock;/** A block to be executed just after an object is added to the cache. The queue waits during execution. */@property (copy) PINDiskCacheObjectBlock __nullable didAddObjectBlock;/** A block to be executed just after an object is removed from the cache. The queue waits during execution. */@property (copy) PINDiskCacheObjectBlock __nullable didRemoveObjectBlock;/** A block to be executed just after all objects are removed from the cache as a result of <removeAllObjects:>. The queue waits during execution. */@property (copy) PINDiskCacheBlock __nullable didRemoveAllObjectsBlock;
Corresponding to the synchronous and asynchronous APIs of PINCache, PINDiskCache also has two sets of implementations, the difference is that the synchronization operation will lock the function and release the lock at the end of the function, the Asynchronous Operation locks key data operations only, and is immediately released after execution. In this way, multiple locks and unlocking operations may be completed within a function, this improves the efficiency of concurrent operations on PINCache, but it is also a test of performance.
4. PINMemoryCache
- Attributes of PINMemoryCache:
@ Property (readonly) NSUInteger totalCost; // Total overhead @ property (assign) NSUInteger costLimit; // maximum overhead of memory allowed @ property (assign) NSTimeInterval ageLimit; // same as PINDiskCache @ property (nonatomic, assign, getter = isTTLCache) BOOL ttlCache; // same as PINDiskCache @ property (assign) BOOL removeAllObjectsOnMemoryWarning; // whether to clear memory cache @ property (assign) BOOL removeAllObjectsOnEnteringBackground during memory warning; // whether to clear memory cache when the App enters the background
5. Operation Security
- Synchronization API of PINDiskCache
-(Void) setObject :( id) object forKey :( NSString *) key fileURL :( NSURL **) outFileURL {... [self lock]; // 1. save the object archive to fileURL.
// 2. Modify the object access date to the current date
// 3. Update the PINDiskCache member variable
[Self unlock];
}
The entire operation is completed in the lock state, ensuring the mutual exclusion of disk File Operations
Other objectForKey and removeObjectForKey operations are also implemented in this way.
- Asynchronous API of PINDiskCache
-(Void) setObject :( id) object forKey :( NSString *) key block :( PINDiskCacheObjectBlock) block {_ weak PINDiskCache * weakSelf = self; dispatch_async (_ asyncQueue, ^ {// Add a task to the concurrent queue. This task is also used to synchronously execute the PINDiskCache synchronization API PINDiskCache * strongSelf = weakSelf; [strongSelf setObject: object forKey: key fileURL: & fileURL]; if (block) {[strongSelf lock];
NSURL * fileURL = nil;
block(strongSelf, key, object, fileURL); [strongSelf unlock]; }});}
- Synchronization API of PINMemoryCache
-(Void) setObject :( id) object forKey :( NSString *) key withCost :( NSUInteger) cost {[self lock]; PINMemoryCacheObjectBlock willAddObjectBlock = _ blank; PINMemoryCacheObjectBlock bytes = _ blank; NSUInteger costLimit = _ costLimit; [self unlock]; if (willAddObjectBlock) willAddObjectBlock (self, key, object); [self lock]; _ dictionary [key] = object; // update the object _ dates [key] = [[NSDate alloc] init]; _ costs [key] = @ (cost); _ totalCost + = cost; [self unlock]; // release the lock. In this case, other operations on the concurrent queue, such as objectForKey, can obtain the object corresponding to the same key, but get the same object ...}
The concurrent Security of PINMemoryCache depends on the fact that PINMemoryCache maintains an NSMutableDictionary. The read and setting of each key-value are mutually exclusive. That is, the semaphore ensures that the NSMutableDictionary operation is thread-safe, in fact, Cocoa containers such as NSArray, NSDictionary, and NSSet are thread-safe, while NSMutableArray and NSMutableDictionary are not thread-safe. Therefore, the NSMutableDictionary of PINMemoryCache needs to be locked for mutual exclusion.
If a mutable Collection object is obtained from PINMemoryCache based on a key, the following situation occurs:
1) Both thread A and thread B read A value, NSMutableDictionary, which is the same object.
2) thread A updates the read NSMutableDictionary.
3) thread B updates the read NSMutableDictionary.
This may cause execution errors because NSMutableDictionary is NOT thread-safe. Therefore, when encapsulating the business layer of PINCache, update operations must be serialized to avoid parallel update operations.