微信登陆,bug修改

This commit is contained in:
kai60
2020-01-09 09:25:43 +08:00
parent 82590f8fba
commit bfb68dd81c
62 changed files with 5105 additions and 275 deletions
+206
View File
@@ -0,0 +1,206 @@
//
// YYCache.h
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/2/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
#if __has_include(<YYCache/YYCache.h>)
FOUNDATION_EXPORT double YYCacheVersionNumber;
FOUNDATION_EXPORT const unsigned char YYCacheVersionString[];
#import <YYCache/YYMemoryCache.h>
#import <YYCache/YYDiskCache.h>
#import <YYCache/YYKVStorage.h>
#elif __has_include(<YYWebImage/YYCache.h>)
#import <YYWebImage/YYMemoryCache.h>
#import <YYWebImage/YYDiskCache.h>
#import <YYWebImage/YYKVStorage.h>
#else
#import "YYMemoryCache.h"
#import "YYDiskCache.h"
#import "YYKVStorage.h"
#endif
NS_ASSUME_NONNULL_BEGIN
/**
`YYCache` is a thread safe key-value cache.
It use `YYMemoryCache` to store objects in a small and fast memory cache,
and use `YYDiskCache` to persisting objects to a large and slow disk cache.
See `YYMemoryCache` and `YYDiskCache` for more information.
*/
@interface YYCache : NSObject
/** The name of the cache, readonly. */
@property (copy, readonly) NSString *name;
/** The underlying memory cache. see `YYMemoryCache` for more information.*/
@property (strong, readonly) YYMemoryCache *memoryCache;
/** The underlying disk cache. see `YYDiskCache` for more information.*/
@property (strong, readonly) YYDiskCache *diskCache;
/**
Create a new instance with the specified name.
Multiple instances with the same name will make the cache unstable.
@param name The name of the cache. It will create a dictionary with the name in
the app's caches dictionary for disk cache. Once initialized you should not
read and write to this directory.
@result A new cache object, or nil if an error occurs.
*/
- (nullable instancetype)initWithName:(NSString *)name;
/**
Create a new instance with the specified path.
Multiple instances with the same name will make the cache unstable.
@param path Full path of a directory in which the cache will write data.
Once initialized you should not read and write to this directory.
@result A new cache object, or nil if an error occurs.
*/
- (nullable instancetype)initWithPath:(NSString *)path NS_DESIGNATED_INITIALIZER;
/**
Convenience Initializers
Create a new instance with the specified name.
Multiple instances with the same name will make the cache unstable.
@param name The name of the cache. It will create a dictionary with the name in
the app's caches dictionary for disk cache. Once initialized you should not
read and write to this directory.
@result A new cache object, or nil if an error occurs.
*/
+ (nullable instancetype)cacheWithName:(NSString *)name;
/**
Convenience Initializers
Create a new instance with the specified path.
Multiple instances with the same name will make the cache unstable.
@param path Full path of a directory in which the cache will write data.
Once initialized you should not read and write to this directory.
@result A new cache object, or nil if an error occurs.
*/
+ (nullable instancetype)cacheWithPath:(NSString *)path;
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================
/**
Returns a boolean value that indicates whether a given key is in cache.
This method may blocks the calling thread until file read finished.
@param key A string identifying the value. If nil, just return NO.
@return Whether the key is in cache.
*/
- (BOOL)containsObjectForKey:(NSString *)key;
/**
Returns a boolean value with the block that indicates whether a given key is in cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param key A string identifying the value. If nil, just return NO.
@param block A block which will be invoked in background queue when finished.
*/
- (void)containsObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, BOOL contains))block;
/**
Returns the value associated with a given key.
This method may blocks the calling thread until file read finished.
@param key A string identifying the value. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
/**
Returns the value associated with a given key.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param key A string identifying the value. If nil, just return nil.
@param block A block which will be invoked in background queue when finished.
*/
- (void)objectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key, id<NSCoding> object))block;
/**
Sets the value of the specified key in the cache.
This method may blocks the calling thread until file write finished.
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param key The key with which to associate the value. If nil, this method has no effect.
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
/**
Sets the value of the specified key in the cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param block A block which will be invoked in background queue when finished.
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(nullable void(^)(void))block;
/**
Removes the value of the specified key in the cache.
This method may blocks the calling thread until file delete finished.
@param key The key identifying the value to be removed. If nil, this method has no effect.
*/
- (void)removeObjectForKey:(NSString *)key;
/**
Removes the value of the specified key in the cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param key The key identifying the value to be removed. If nil, this method has no effect.
@param block A block which will be invoked in background queue when finished.
*/
- (void)removeObjectForKey:(NSString *)key withBlock:(nullable void(^)(NSString *key))block;
/**
Empties the cache.
This method may blocks the calling thread until file delete finished.
*/
- (void)removeAllObjects;
/**
Empties the cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param block A block which will be invoked in background queue when finished.
*/
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
/**
Empties the cache with block.
This method returns immediately and executes the clear operation with block in background.
@warning You should not send message to this instance in these blocks.
@param progress This block will be invoked during removing, pass nil to ignore.
@param end This block will be invoked at the end, pass nil to ignore.
*/
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
endBlock:(nullable void(^)(BOOL error))end;
@end
NS_ASSUME_NONNULL_END
+139
View File
@@ -0,0 +1,139 @@
//
// YYCache.m
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/2/13.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYCache.h"
#import "YYMemoryCache.h"
#import "YYDiskCache.h"
@implementation YYCache
- (instancetype) init {
NSLog(@"Use \"initWithName\" or \"initWithPath\" to create YYCache instance.");
return [self initWithPath:@""];
}
- (instancetype)initWithName:(NSString *)name {
if (name.length == 0) return nil;
NSString *cacheFolder = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
NSString *path = [cacheFolder stringByAppendingPathComponent:name];
return [self initWithPath:path];
}
- (instancetype)initWithPath:(NSString *)path {
if (path.length == 0) return nil;
YYDiskCache *diskCache = [[YYDiskCache alloc] initWithPath:path];
if (!diskCache) return nil;
NSString *name = [path lastPathComponent];
YYMemoryCache *memoryCache = [YYMemoryCache new];
memoryCache.name = name;
self = [super init];
_name = name;
_diskCache = diskCache;
_memoryCache = memoryCache;
return self;
}
+ (instancetype)cacheWithName:(NSString *)name {
return [[self alloc] initWithName:name];
}
+ (instancetype)cacheWithPath:(NSString *)path {
return [[self alloc] initWithPath:path];
}
- (BOOL)containsObjectForKey:(NSString *)key {
return [_memoryCache containsObjectForKey:key] || [_diskCache containsObjectForKey:key];
}
- (void)containsObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key, BOOL contains))block {
if (!block) return;
if ([_memoryCache containsObjectForKey:key]) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(key, YES);
});
} else {
[_diskCache containsObjectForKey:key withBlock:block];
}
}
- (id<NSCoding>)objectForKey:(NSString *)key {
id<NSCoding> object = [_memoryCache objectForKey:key];
if (!object) {
object = [_diskCache objectForKey:key];
if (object) {
[_memoryCache setObject:object forKey:key];
}
}
return object;
}
- (void)objectForKey:(NSString *)key withBlock:(void (^)(NSString *key, id<NSCoding> object))block {
if (!block) return;
id<NSCoding> object = [_memoryCache objectForKey:key];
if (object) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
block(key, object);
});
} else {
[_diskCache objectForKey:key withBlock:^(NSString *key, id<NSCoding> object) {
if (object && ![_memoryCache objectForKey:key]) {
[_memoryCache setObject:object forKey:key];
}
block(key, object);
}];
}
}
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key];
}
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void (^)(void))block {
[_memoryCache setObject:object forKey:key];
[_diskCache setObject:object forKey:key withBlock:block];
}
- (void)removeObjectForKey:(NSString *)key {
[_memoryCache removeObjectForKey:key];
[_diskCache removeObjectForKey:key];
}
- (void)removeObjectForKey:(NSString *)key withBlock:(void (^)(NSString *key))block {
[_memoryCache removeObjectForKey:key];
[_diskCache removeObjectForKey:key withBlock:block];
}
- (void)removeAllObjects {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjects];
}
- (void)removeAllObjectsWithBlock:(void(^)(void))block {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjectsWithBlock:block];
}
- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
endBlock:(void(^)(BOOL error))end {
[_memoryCache removeAllObjects];
[_diskCache removeAllObjectsWithProgressBlock:progress endBlock:end];
}
- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}
@end
+412
View File
@@ -0,0 +1,412 @@
//
// YYDiskCache.h
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/2/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
YYDiskCache is a thread-safe cache that stores key-value pairs backed by SQLite
and file system (similar to NSURLCache's disk cache).
YYDiskCache has these features:
* It use LRU (least-recently-used) to remove objects.
* It can be controlled by cost, count, and age.
* It can be configured to automatically evict objects when there's no free disk space.
* It can automatically decide the storage type (sqlite/file) for each object to get
better performance.
You may compile the latest version of sqlite and ignore the libsqlite3.dylib in
iOS system to get 2x~4x speed up.
*/
@interface YYDiskCache : NSObject
#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================
/** The name of the cache. Default is nil. */
@property (nullable, copy) NSString *name;
/** The path of the cache (read-only). */
@property (readonly) NSString *path;
/**
If the object's data size (in bytes) is larger than this value, then object will
be stored as a file, otherwise the object will be stored in sqlite.
0 means all objects will be stored as separated files, NSUIntegerMax means all
objects will be stored in sqlite.
The default value is 20480 (20KB).
*/
@property (readonly) NSUInteger inlineThreshold;
/**
If this block is not nil, then the block will be used to archive object instead
of NSKeyedArchiver. You can use this block to support the objects which do not
conform to the `NSCoding` protocol.
The default value is nil.
*/
@property (nullable, copy) NSData *(^customArchiveBlock)(id object);
/**
If this block is not nil, then the block will be used to unarchive object instead
of NSKeyedUnarchiver. You can use this block to support the objects which do not
conform to the `NSCoding` protocol.
The default value is nil.
*/
@property (nullable, copy) id (^customUnarchiveBlock)(NSData *data);
/**
When an object needs to be saved as a file, this block will be invoked to generate
a file name for a specified key. If the block is nil, the cache use md5(key) as
default file name.
The default value is nil.
*/
@property (nullable, copy) NSString *(^customFileNameBlock)(NSString *key);
#pragma mark - Limit
///=============================================================================
/// @name Limit
///=============================================================================
/**
The maximum number of objects the cache should hold.
@discussion The default value is NSUIntegerMax, which means no limit.
This is not a strict limit — if the cache goes over the limit, some objects in the
cache could be evicted later in background queue.
*/
@property NSUInteger countLimit;
/**
The maximum total cost that the cache can hold before it starts evicting objects.
@discussion The default value is NSUIntegerMax, which means no limit.
This is not a strict limit — if the cache goes over the limit, some objects in the
cache could be evicted later in background queue.
*/
@property NSUInteger costLimit;
/**
The maximum expiry time of objects in cache.
@discussion The default value is DBL_MAX, which means no limit.
This is not a strict limit — if an object goes over the limit, the objects could
be evicted later in background queue.
*/
@property NSTimeInterval ageLimit;
/**
The minimum free disk space (in bytes) which the cache should kept.
@discussion The default value is 0, which means no limit.
If the free disk space is lower than this value, the cache will remove objects
to free some disk space. This is not a strict limit—if the free disk space goes
over the limit, the objects could be evicted later in background queue.
*/
@property NSUInteger freeDiskSpaceLimit;
/**
The auto trim check time interval in seconds. Default is 60 (1 minute).
@discussion The cache holds an internal timer to check whether the cache reaches
its limits, and if the limit is reached, it begins to evict objects.
*/
@property NSTimeInterval autoTrimInterval;
/**
Set `YES` to enable error logs for debug.
*/
@property BOOL errorLogsEnabled;
#pragma mark - Initializer
///=============================================================================
/// @name Initializer
///=============================================================================
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
/**
Create a new cache based on the specified path.
@param path Full path of a directory in which the cache will write data.
Once initialized you should not read and write to this directory.
@return A new cache object, or nil if an error occurs.
@warning If the cache instance for the specified path already exists in memory,
this method will return it directly, instead of creating a new instance.
*/
- (nullable instancetype)initWithPath:(NSString *)path;
/**
The designated initializer.
@param path Full path of a directory in which the cache will write data.
Once initialized you should not read and write to this directory.
@param threshold The data store inline threshold in bytes. If the object's data
size (in bytes) is larger than this value, then object will be stored as a
file, otherwise the object will be stored in sqlite. 0 means all objects will
be stored as separated files, NSUIntegerMax means all objects will be stored
in sqlite. If you don't know your object's size, 20480 is a good choice.
After first initialized you should not change this value of the specified path.
@return A new cache object, or nil if an error occurs.
@warning If the cache instance for the specified path already exists in memory,
this method will return it directly, instead of creating a new instance.
*/
- (nullable instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold NS_DESIGNATED_INITIALIZER;
#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================
/**
Returns a boolean value that indicates whether a given key is in cache.
This method may blocks the calling thread until file read finished.
@param key A string identifying the value. If nil, just return NO.
@return Whether the key is in cache.
*/
- (BOOL)containsObjectForKey:(NSString *)key;
/**
Returns a boolean value with the block that indicates whether a given key is in cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param key A string identifying the value. If nil, just return NO.
@param block A block which will be invoked in background queue when finished.
*/
- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block;
/**
Returns the value associated with a given key.
This method may blocks the calling thread until file read finished.
@param key A string identifying the value. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
- (nullable id<NSCoding>)objectForKey:(NSString *)key;
/**
Returns the value associated with a given key.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param key A string identifying the value. If nil, just return nil.
@param block A block which will be invoked in background queue when finished.
*/
- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> _Nullable object))block;
/**
Sets the value of the specified key in the cache.
This method may blocks the calling thread until file write finished.
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param key The key with which to associate the value. If nil, this method has no effect.
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key;
/**
Sets the value of the specified key in the cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param block A block which will be invoked in background queue when finished.
*/
- (void)setObject:(nullable id<NSCoding>)object forKey:(NSString *)key withBlock:(void(^)(void))block;
/**
Removes the value of the specified key in the cache.
This method may blocks the calling thread until file delete finished.
@param key The key identifying the value to be removed. If nil, this method has no effect.
*/
- (void)removeObjectForKey:(NSString *)key;
/**
Removes the value of the specified key in the cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param key The key identifying the value to be removed. If nil, this method has no effect.
@param block A block which will be invoked in background queue when finished.
*/
- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block;
/**
Empties the cache.
This method may blocks the calling thread until file delete finished.
*/
- (void)removeAllObjects;
/**
Empties the cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param block A block which will be invoked in background queue when finished.
*/
- (void)removeAllObjectsWithBlock:(void(^)(void))block;
/**
Empties the cache with block.
This method returns immediately and executes the clear operation with block in background.
@warning You should not send message to this instance in these blocks.
@param progress This block will be invoked during removing, pass nil to ignore.
@param end This block will be invoked at the end, pass nil to ignore.
*/
- (void)removeAllObjectsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
endBlock:(nullable void(^)(BOOL error))end;
/**
Returns the number of objects in this cache.
This method may blocks the calling thread until file read finished.
@return The total objects count.
*/
- (NSInteger)totalCount;
/**
Get the number of objects in this cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param block A block which will be invoked in background queue when finished.
*/
- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block;
/**
Returns the total cost (in bytes) of objects in this cache.
This method may blocks the calling thread until file read finished.
@return The total objects cost in bytes.
*/
- (NSInteger)totalCost;
/**
Get the total cost (in bytes) of objects in this cache.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param block A block which will be invoked in background queue when finished.
*/
- (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block;
#pragma mark - Trim
///=============================================================================
/// @name Trim
///=============================================================================
/**
Removes objects from the cache use LRU, until the `totalCount` is below the specified value.
This method may blocks the calling thread until operation finished.
@param count The total count allowed to remain after the cache has been trimmed.
*/
- (void)trimToCount:(NSUInteger)count;
/**
Removes objects from the cache use LRU, until the `totalCount` is below the specified value.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param count The total count allowed to remain after the cache has been trimmed.
@param block A block which will be invoked in background queue when finished.
*/
- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block;
/**
Removes objects from the cache use LRU, until the `totalCost` is below the specified value.
This method may blocks the calling thread until operation finished.
@param cost The total cost allowed to remain after the cache has been trimmed.
*/
- (void)trimToCost:(NSUInteger)cost;
/**
Removes objects from the cache use LRU, until the `totalCost` is below the specified value.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param cost The total cost allowed to remain after the cache has been trimmed.
@param block A block which will be invoked in background queue when finished.
*/
- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block;
/**
Removes objects from the cache use LRU, until all expiry objects removed by the specified value.
This method may blocks the calling thread until operation finished.
@param age The maximum age of the object.
*/
- (void)trimToAge:(NSTimeInterval)age;
/**
Removes objects from the cache use LRU, until all expiry objects removed by the specified value.
This method returns immediately and invoke the passed block in background queue
when the operation finished.
@param age The maximum age of the object.
@param block A block which will be invoked in background queue when finished.
*/
- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block;
#pragma mark - Extended Data
///=============================================================================
/// @name Extended Data
///=============================================================================
/**
Get extended data from an object.
@discussion See 'setExtendedData:toObject:' for more information.
@param object An object.
@return The extended data.
*/
+ (nullable NSData *)getExtendedDataFromObject:(id)object;
/**
Set extended data to an object.
@discussion You can set any extended data to an object before you save the object
to disk cache. The extended data will also be saved with this object. You can get
the extended data later with "getExtendedDataFromObject:".
@param extendedData The extended data (pass nil to remove).
@param object The object.
*/
+ (void)setExtendedData:(nullable NSData *)extendedData toObject:(id)object;
@end
NS_ASSUME_NONNULL_END
+458
View File
@@ -0,0 +1,458 @@
//
// YYDiskCache.m
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/2/11.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYDiskCache.h"
#import "YYKVStorage.h"
#import <UIKit/UIKit.h>
#import <CommonCrypto/CommonCrypto.h>
#import <objc/runtime.h>
#import <time.h>
#define Lock() dispatch_semaphore_wait(self->_lock, DISPATCH_TIME_FOREVER)
#define Unlock() dispatch_semaphore_signal(self->_lock)
static const int extended_data_key;
/// Free disk space in bytes.
static int64_t _YYDiskSpaceFree() {
NSError *error = nil;
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfFileSystemForPath:NSHomeDirectory() error:&error];
if (error) return -1;
int64_t space = [[attrs objectForKey:NSFileSystemFreeSize] longLongValue];
if (space < 0) space = -1;
return space;
}
/// String's md5 hash.
static NSString *_YYNSStringMD5(NSString *string) {
if (!string) return nil;
NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
unsigned char result[CC_MD5_DIGEST_LENGTH];
CC_MD5(data.bytes, (CC_LONG)data.length, result);
return [NSString stringWithFormat:
@"%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
result[0], result[1], result[2], result[3],
result[4], result[5], result[6], result[7],
result[8], result[9], result[10], result[11],
result[12], result[13], result[14], result[15]
];
}
/// weak reference for all instances
static NSMapTable *_globalInstances;
static dispatch_semaphore_t _globalInstancesLock;
static void _YYDiskCacheInitGlobal() {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_globalInstancesLock = dispatch_semaphore_create(1);
_globalInstances = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
});
}
static YYDiskCache *_YYDiskCacheGetGlobal(NSString *path) {
if (path.length == 0) return nil;
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
id cache = [_globalInstances objectForKey:path];
dispatch_semaphore_signal(_globalInstancesLock);
return cache;
}
static void _YYDiskCacheSetGlobal(YYDiskCache *cache) {
if (cache.path.length == 0) return;
_YYDiskCacheInitGlobal();
dispatch_semaphore_wait(_globalInstancesLock, DISPATCH_TIME_FOREVER);
[_globalInstances setObject:cache forKey:cache.path];
dispatch_semaphore_signal(_globalInstancesLock);
}
@implementation YYDiskCache {
YYKVStorage *_kv;
dispatch_semaphore_t _lock;
dispatch_queue_t _queue;
}
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
if (!self) return;
Lock();
[self _trimToCost:self.costLimit];
[self _trimToCount:self.countLimit];
[self _trimToAge:self.ageLimit];
[self _trimToFreeDiskSpace:self.freeDiskSpaceLimit];
Unlock();
});
}
- (void)_trimToCost:(NSUInteger)costLimit {
if (costLimit >= INT_MAX) return;
[_kv removeItemsToFitSize:(int)costLimit];
}
- (void)_trimToCount:(NSUInteger)countLimit {
if (countLimit >= INT_MAX) return;
[_kv removeItemsToFitCount:(int)countLimit];
}
- (void)_trimToAge:(NSTimeInterval)ageLimit {
if (ageLimit <= 0) {
[_kv removeAllItems];
return;
}
long timestamp = time(NULL);
if (timestamp <= ageLimit) return;
long age = timestamp - ageLimit;
if (age >= INT_MAX) return;
[_kv removeItemsEarlierThanTime:(int)age];
}
- (void)_trimToFreeDiskSpace:(NSUInteger)targetFreeDiskSpace {
if (targetFreeDiskSpace == 0) return;
int64_t totalBytes = [_kv getItemsSize];
if (totalBytes <= 0) return;
int64_t diskFreeBytes = _YYDiskSpaceFree();
if (diskFreeBytes < 0) return;
int64_t needTrimBytes = targetFreeDiskSpace - diskFreeBytes;
if (needTrimBytes <= 0) return;
int64_t costLimit = totalBytes - needTrimBytes;
if (costLimit < 0) costLimit = 0;
[self _trimToCost:(int)costLimit];
}
- (NSString *)_filenameForKey:(NSString *)key {
NSString *filename = nil;
if (_customFileNameBlock) filename = _customFileNameBlock(key);
if (!filename) filename = _YYNSStringMD5(key);
return filename;
}
- (void)_appWillBeTerminated {
Lock();
_kv = nil;
Unlock();
}
#pragma mark - public
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillTerminateNotification object:nil];
}
- (instancetype)init {
@throw [NSException exceptionWithName:@"YYDiskCache init error" reason:@"YYDiskCache must be initialized with a path. Use 'initWithPath:' or 'initWithPath:inlineThreshold:' instead." userInfo:nil];
return [self initWithPath:@"" inlineThreshold:0];
}
- (instancetype)initWithPath:(NSString *)path {
return [self initWithPath:path inlineThreshold:1024 * 20]; // 20KB
}
- (instancetype)initWithPath:(NSString *)path
inlineThreshold:(NSUInteger)threshold {
self = [super init];
if (!self) return nil;
YYDiskCache *globalCache = _YYDiskCacheGetGlobal(path);
if (globalCache) return globalCache;
YYKVStorageType type;
if (threshold == 0) {
type = YYKVStorageTypeFile;
} else if (threshold == NSUIntegerMax) {
type = YYKVStorageTypeSQLite;
} else {
type = YYKVStorageTypeMixed;
}
YYKVStorage *kv = [[YYKVStorage alloc] initWithPath:path type:type];
if (!kv) return nil;
_kv = kv;
_path = path;
_lock = dispatch_semaphore_create(1);
_queue = dispatch_queue_create("com.ibireme.cache.disk", DISPATCH_QUEUE_CONCURRENT);
_inlineThreshold = threshold;
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
_freeDiskSpaceLimit = 0;
_autoTrimInterval = 60;
[self _trimRecursively];
_YYDiskCacheSetGlobal(self);
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appWillBeTerminated) name:UIApplicationWillTerminateNotification object:nil];
return self;
}
- (BOOL)containsObjectForKey:(NSString *)key {
if (!key) return NO;
Lock();
BOOL contains = [_kv itemExistsForKey:key];
Unlock();
return contains;
}
- (void)containsObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key, BOOL contains))block {
if (!block) return;
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
BOOL contains = [self containsObjectForKey:key];
block(key, contains);
});
}
- (id<NSCoding>)objectForKey:(NSString *)key {
if (!key) return nil;
Lock();
YYKVStorageItem *item = [_kv getItemForKey:key];
Unlock();
if (!item.value) return nil;
id object = nil;
if (_customUnarchiveBlock) {
object = _customUnarchiveBlock(item.value);
} else {
@try {
object = [NSKeyedUnarchiver unarchiveObjectWithData:item.value];
}
@catch (NSException *exception) {
// nothing to do...
}
}
if (object && item.extendedData) {
[YYDiskCache setExtendedData:item.extendedData toObject:object];
}
return object;
}
- (void)objectForKey:(NSString *)key withBlock:(void(^)(NSString *key, id<NSCoding> object))block {
if (!block) return;
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
id<NSCoding> object = [self objectForKey:key];
block(key, object);
});
}
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
NSData *extendedData = [YYDiskCache getExtendedDataFromObject:object];
NSData *value = nil;
if (_customArchiveBlock) {
value = _customArchiveBlock(object);
} else {
@try {
value = [NSKeyedArchiver archivedDataWithRootObject:object];
}
@catch (NSException *exception) {
// nothing to do...
}
}
if (!value) return;
NSString *filename = nil;
if (_kv.type != YYKVStorageTypeSQLite) {
if (value.length > _inlineThreshold) {
filename = [self _filenameForKey:key];
}
}
Lock();
[_kv saveItemWithKey:key value:value filename:filename extendedData:extendedData];
Unlock();
}
- (void)setObject:(id<NSCoding>)object forKey:(NSString *)key withBlock:(void(^)(void))block {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
[self setObject:object forKey:key];
if (block) block();
});
}
- (void)removeObjectForKey:(NSString *)key {
if (!key) return;
Lock();
[_kv removeItemForKey:key];
Unlock();
}
- (void)removeObjectForKey:(NSString *)key withBlock:(void(^)(NSString *key))block {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
[self removeObjectForKey:key];
if (block) block(key);
});
}
- (void)removeAllObjects {
Lock();
[_kv removeAllItems];
Unlock();
}
- (void)removeAllObjectsWithBlock:(void(^)(void))block {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
[self removeAllObjects];
if (block) block();
});
}
- (void)removeAllObjectsWithProgressBlock:(void(^)(int removedCount, int totalCount))progress
endBlock:(void(^)(BOOL error))end {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
if (!self) {
if (end) end(YES);
return;
}
Lock();
[_kv removeAllItemsWithProgressBlock:progress endBlock:end];
Unlock();
});
}
- (NSInteger)totalCount {
Lock();
int count = [_kv getItemsCount];
Unlock();
return count;
}
- (void)totalCountWithBlock:(void(^)(NSInteger totalCount))block {
if (!block) return;
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
NSInteger totalCount = [self totalCount];
block(totalCount);
});
}
- (NSInteger)totalCost {
Lock();
int count = [_kv getItemsSize];
Unlock();
return count;
}
- (void)totalCostWithBlock:(void(^)(NSInteger totalCost))block {
if (!block) return;
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
NSInteger totalCost = [self totalCost];
block(totalCost);
});
}
- (void)trimToCount:(NSUInteger)count {
Lock();
[self _trimToCount:count];
Unlock();
}
- (void)trimToCount:(NSUInteger)count withBlock:(void(^)(void))block {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
[self trimToCount:count];
if (block) block();
});
}
- (void)trimToCost:(NSUInteger)cost {
Lock();
[self _trimToCost:cost];
Unlock();
}
- (void)trimToCost:(NSUInteger)cost withBlock:(void(^)(void))block {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
[self trimToCost:cost];
if (block) block();
});
}
- (void)trimToAge:(NSTimeInterval)age {
Lock();
[self _trimToAge:age];
Unlock();
}
- (void)trimToAge:(NSTimeInterval)age withBlock:(void(^)(void))block {
__weak typeof(self) _self = self;
dispatch_async(_queue, ^{
__strong typeof(_self) self = _self;
[self trimToAge:age];
if (block) block();
});
}
+ (NSData *)getExtendedDataFromObject:(id)object {
if (!object) return nil;
return (NSData *)objc_getAssociatedObject(object, &extended_data_key);
}
+ (void)setExtendedData:(NSData *)extendedData toObject:(id)object {
if (!object) return;
objc_setAssociatedObject(object, &extended_data_key, extendedData, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@:%@)", self.class, self, _name, _path];
else return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _path];
}
- (BOOL)errorLogsEnabled {
Lock();
BOOL enabled = _kv.errorLogsEnabled;
Unlock();
return enabled;
}
- (void)setErrorLogsEnabled:(BOOL)errorLogsEnabled {
Lock();
_kv.errorLogsEnabled = errorLogsEnabled;
Unlock();
}
@end
+325
View File
@@ -0,0 +1,325 @@
//
// YYKVStorage.h
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/4/22.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
YYKVStorageItem is used by `YYKVStorage` to store key-value pair and meta data.
Typically, you should not use this class directly.
*/
@interface YYKVStorageItem : NSObject
@property (nonatomic, strong) NSString *key; ///< key
@property (nonatomic, strong) NSData *value; ///< value
@property (nullable, nonatomic, strong) NSString *filename; ///< filename (nil if inline)
@property (nonatomic) int size; ///< value's size in bytes
@property (nonatomic) int modTime; ///< modification unix timestamp
@property (nonatomic) int accessTime; ///< last access unix timestamp
@property (nullable, nonatomic, strong) NSData *extendedData; ///< extended data (nil if no extended data)
@end
/**
Storage type, indicated where the `YYKVStorageItem.value` stored.
@discussion Typically, write data to sqlite is faster than extern file, but
reading performance is dependent on data size. In my test (on iPhone 6 64G),
read data from extern file is faster than from sqlite when the data is larger
than 20KB.
* If you want to store large number of small datas (such as contacts cache),
use YYKVStorageTypeSQLite to get better performance.
* If you want to store large files (such as image cache),
use YYKVStorageTypeFile to get better performance.
* You can use YYKVStorageTypeMixed and choice your storage type for each item.
See <http://www.sqlite.org/intern-v-extern-blob.html> for more information.
*/
typedef NS_ENUM(NSUInteger, YYKVStorageType) {
/// The `value` is stored as a file in file system.
YYKVStorageTypeFile = 0,
/// The `value` is stored in sqlite with blob type.
YYKVStorageTypeSQLite = 1,
/// The `value` is stored in file system or sqlite based on your choice.
YYKVStorageTypeMixed = 2,
};
/**
YYKVStorage is a key-value storage based on sqlite and file system.
Typically, you should not use this class directly.
@discussion The designated initializer for YYKVStorage is `initWithPath:type:`.
After initialized, a directory is created based on the `path` to hold key-value data.
Once initialized you should not read or write this directory without the instance.
You may compile the latest version of sqlite and ignore the libsqlite3.dylib in
iOS system to get 2x~4x speed up.
@warning The instance of this class is *NOT* thread safe, you need to make sure
that there's only one thread to access the instance at the same time. If you really
need to process large amounts of data in multi-thread, you should split the data
to multiple KVStorage instance (sharding).
*/
@interface YYKVStorage : NSObject
#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================
@property (nonatomic, readonly) NSString *path; ///< The path of this storage.
@property (nonatomic, readonly) YYKVStorageType type; ///< The type of this storage.
@property (nonatomic) BOOL errorLogsEnabled; ///< Set `YES` to enable error logs for debug.
#pragma mark - Initializer
///=============================================================================
/// @name Initializer
///=============================================================================
- (instancetype)init UNAVAILABLE_ATTRIBUTE;
+ (instancetype)new UNAVAILABLE_ATTRIBUTE;
/**
The designated initializer.
@param path Full path of a directory in which the storage will write data. If
the directory is not exists, it will try to create one, otherwise it will
read the data in this directory.
@param type The storage type. After first initialized you should not change the
type of the specified path.
@return A new storage object, or nil if an error occurs.
@warning Multiple instances with the same path will make the storage unstable.
*/
- (nullable instancetype)initWithPath:(NSString *)path type:(YYKVStorageType)type NS_DESIGNATED_INITIALIZER;
#pragma mark - Save Items
///=============================================================================
/// @name Save Items
///=============================================================================
/**
Save an item or update the item with 'key' if it already exists.
@discussion This method will save the item.key, item.value, item.filename and
item.extendedData to disk or sqlite, other properties will be ignored. item.key
and item.value should not be empty (nil or zero length).
If the `type` is YYKVStorageTypeFile, then the item.filename should not be empty.
If the `type` is YYKVStorageTypeSQLite, then the item.filename will be ignored.
It the `type` is YYKVStorageTypeMixed, then the item.value will be saved to file
system if the item.filename is not empty, otherwise it will be saved to sqlite.
@param item An item.
@return Whether succeed.
*/
- (BOOL)saveItem:(YYKVStorageItem *)item;
/**
Save an item or update the item with 'key' if it already exists.
@discussion This method will save the key-value pair to sqlite. If the `type` is
YYKVStorageTypeFile, then this method will failed.
@param key The key, should not be empty (nil or zero length).
@param value The key, should not be empty (nil or zero length).
@return Whether succeed.
*/
- (BOOL)saveItemWithKey:(NSString *)key value:(NSData *)value;
/**
Save an item or update the item with 'key' if it already exists.
@discussion
If the `type` is YYKVStorageTypeFile, then the `filename` should not be empty.
If the `type` is YYKVStorageTypeSQLite, then the `filename` will be ignored.
It the `type` is YYKVStorageTypeMixed, then the `value` will be saved to file
system if the `filename` is not empty, otherwise it will be saved to sqlite.
@param key The key, should not be empty (nil or zero length).
@param value The key, should not be empty (nil or zero length).
@param filename The filename.
@param extendedData The extended data for this item (pass nil to ignore it).
@return Whether succeed.
*/
- (BOOL)saveItemWithKey:(NSString *)key
value:(NSData *)value
filename:(nullable NSString *)filename
extendedData:(nullable NSData *)extendedData;
#pragma mark - Remove Items
///=============================================================================
/// @name Remove Items
///=============================================================================
/**
Remove an item with 'key'.
@param key The item's key.
@return Whether succeed.
*/
- (BOOL)removeItemForKey:(NSString *)key;
/**
Remove items with an array of keys.
@param keys An array of specified keys.
@return Whether succeed.
*/
- (BOOL)removeItemForKeys:(NSArray<NSString *> *)keys;
/**
Remove all items which `value` is larger than a specified size.
@param size The maximum size in bytes.
@return Whether succeed.
*/
- (BOOL)removeItemsLargerThanSize:(int)size;
/**
Remove all items which last access time is earlier than a specified timestamp.
@param time The specified unix timestamp.
@return Whether succeed.
*/
- (BOOL)removeItemsEarlierThanTime:(int)time;
/**
Remove items to make the total size not larger than a specified size.
The least recently used (LRU) items will be removed first.
@param maxSize The specified size in bytes.
@return Whether succeed.
*/
- (BOOL)removeItemsToFitSize:(int)maxSize;
/**
Remove items to make the total count not larger than a specified count.
The least recently used (LRU) items will be removed first.
@param maxCount The specified item count.
@return Whether succeed.
*/
- (BOOL)removeItemsToFitCount:(int)maxCount;
/**
Remove all items in background queue.
@discussion This method will remove the files and sqlite database to a trash
folder, and then clear the folder in background queue. So this method is much
faster than `removeAllItemsWithProgressBlock:endBlock:`.
@return Whether succeed.
*/
- (BOOL)removeAllItems;
/**
Remove all items.
@warning You should not send message to this instance in these blocks.
@param progress This block will be invoked during removing, pass nil to ignore.
@param end This block will be invoked at the end, pass nil to ignore.
*/
- (void)removeAllItemsWithProgressBlock:(nullable void(^)(int removedCount, int totalCount))progress
endBlock:(nullable void(^)(BOOL error))end;
#pragma mark - Get Items
///=============================================================================
/// @name Get Items
///=============================================================================
/**
Get item with a specified key.
@param key A specified key.
@return Item for the key, or nil if not exists / error occurs.
*/
- (nullable YYKVStorageItem *)getItemForKey:(NSString *)key;
/**
Get item information with a specified key.
The `value` in this item will be ignored.
@param key A specified key.
@return Item information for the key, or nil if not exists / error occurs.
*/
- (nullable YYKVStorageItem *)getItemInfoForKey:(NSString *)key;
/**
Get item value with a specified key.
@param key A specified key.
@return Item's value, or nil if not exists / error occurs.
*/
- (nullable NSData *)getItemValueForKey:(NSString *)key;
/**
Get items with an array of keys.
@param keys An array of specified keys.
@return An array of `YYKVStorageItem`, or nil if not exists / error occurs.
*/
- (nullable NSArray<YYKVStorageItem *> *)getItemForKeys:(NSArray<NSString *> *)keys;
/**
Get item infomartions with an array of keys.
The `value` in items will be ignored.
@param keys An array of specified keys.
@return An array of `YYKVStorageItem`, or nil if not exists / error occurs.
*/
- (nullable NSArray<YYKVStorageItem *> *)getItemInfoForKeys:(NSArray<NSString *> *)keys;
/**
Get items value with an array of keys.
@param keys An array of specified keys.
@return A dictionary which key is 'key' and value is 'value', or nil if not
exists / error occurs.
*/
- (nullable NSDictionary<NSString *, NSData *> *)getItemValueForKeys:(NSArray<NSString *> *)keys;
#pragma mark - Get Storage Status
///=============================================================================
/// @name Get Storage Status
///=============================================================================
/**
Whether an item exists for a specified key.
@param key A specified key.
@return `YES` if there's an item exists for the key, `NO` if not exists or an error occurs.
*/
- (BOOL)itemExistsForKey:(NSString *)key;
/**
Get total item count.
@return Total item count, -1 when an error occurs.
*/
- (int)getItemsCount;
/**
Get item value's total size in bytes.
@return Total size in bytes, -1 when an error occurs.
*/
- (int)getItemsSize;
@end
NS_ASSUME_NONNULL_END
File diff suppressed because it is too large Load Diff
+213
View File
@@ -0,0 +1,213 @@
//
// YYMemoryCache.h
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/2/7.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
YYMemoryCache is a fast in-memory cache that stores key-value pairs.
In contrast to NSDictionary, keys are retained and not copied.
The API and performance is similar to `NSCache`, all methods are thread-safe.
YYMemoryCache objects differ from NSCache in a few ways:
* It uses LRU (least-recently-used) to remove objects; NSCache's eviction method
is non-deterministic.
* It can be controlled by cost, count and age; NSCache's limits are imprecise.
* It can be configured to automatically evict objects when receive memory
warning or app enter background.
The time of `Access Methods` in YYMemoryCache is typically in constant time (O(1)).
*/
@interface YYMemoryCache : NSObject
#pragma mark - Attribute
///=============================================================================
/// @name Attribute
///=============================================================================
/** The name of the cache. Default is nil. */
@property (nullable, copy) NSString *name;
/** The number of objects in the cache (read-only) */
@property (readonly) NSUInteger totalCount;
/** The total cost of objects in the cache (read-only). */
@property (readonly) NSUInteger totalCost;
#pragma mark - Limit
///=============================================================================
/// @name Limit
///=============================================================================
/**
The maximum number of objects the cache should hold.
@discussion The default value is NSUIntegerMax, which means no limit.
This is not a strict limit—if the cache goes over the limit, some objects in the
cache could be evicted later in backgound thread.
*/
@property NSUInteger countLimit;
/**
The maximum total cost that the cache can hold before it starts evicting objects.
@discussion The default value is NSUIntegerMax, which means no limit.
This is not a strict limit—if the cache goes over the limit, some objects in the
cache could be evicted later in backgound thread.
*/
@property NSUInteger costLimit;
/**
The maximum expiry time of objects in cache.
@discussion The default value is DBL_MAX, which means no limit.
This is not a strict limit—if an object goes over the limit, the object could
be evicted later in backgound thread.
*/
@property NSTimeInterval ageLimit;
/**
The auto trim check time interval in seconds. Default is 5.0.
@discussion The cache holds an internal timer to check whether the cache reaches
its limits, and if the limit is reached, it begins to evict objects.
*/
@property NSTimeInterval autoTrimInterval;
/**
If `YES`, the cache will remove all objects when the app receives a memory warning.
The default value is `YES`.
*/
@property BOOL shouldRemoveAllObjectsOnMemoryWarning;
/**
If `YES`, The cache will remove all objects when the app enter background.
The default value is `YES`.
*/
@property BOOL shouldRemoveAllObjectsWhenEnteringBackground;
/**
A block to be executed when the app receives a memory warning.
The default value is nil.
*/
@property (nullable, copy) void(^didReceiveMemoryWarningBlock)(YYMemoryCache *cache);
/**
A block to be executed when the app enter background.
The default value is nil.
*/
@property (nullable, copy) void(^didEnterBackgroundBlock)(YYMemoryCache *cache);
/**
If `YES`, the key-value pair will be released on main thread, otherwise on
background thread. Default is NO.
@discussion You may set this value to `YES` if the key-value object contains
the instance which should be released in main thread (such as UIView/CALayer).
*/
@property BOOL releaseOnMainThread;
/**
If `YES`, the key-value pair will be released asynchronously to avoid blocking
the access methods, otherwise it will be released in the access method
(such as removeObjectForKey:). Default is YES.
*/
@property BOOL releaseAsynchronously;
#pragma mark - Access Methods
///=============================================================================
/// @name Access Methods
///=============================================================================
/**
Returns a Boolean value that indicates whether a given key is in cache.
@param key An object identifying the value. If nil, just return `NO`.
@return Whether the key is in cache.
*/
- (BOOL)containsObjectForKey:(id)key;
/**
Returns the value associated with a given key.
@param key An object identifying the value. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
- (nullable id)objectForKey:(id)key;
/**
Sets the value of the specified key in the cache (0 cost).
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param key The key with which to associate the value. If nil, this method has no effect.
@discussion Unlike an NSMutableDictionary object, a cache does not copy the key
objects that are put into it.
*/
- (void)setObject:(nullable id)object forKey:(id)key;
/**
Sets the value of the specified key in the cache, and associates the key-value
pair with the specified cost.
@param object The object to store in the cache. If nil, it calls `removeObjectForKey`.
@param key The key with which to associate the value. If nil, this method has no effect.
@param cost The cost with which to associate the key-value pair.
@discussion Unlike an NSMutableDictionary object, a cache does not copy the key
objects that are put into it.
*/
- (void)setObject:(nullable id)object forKey:(id)key withCost:(NSUInteger)cost;
/**
Removes the value of the specified key in the cache.
@param key The key identifying the value to be removed. If nil, this method has no effect.
*/
- (void)removeObjectForKey:(id)key;
/**
Empties the cache immediately.
*/
- (void)removeAllObjects;
#pragma mark - Trim
///=============================================================================
/// @name Trim
///=============================================================================
/**
Removes objects from the cache with LRU, until the `totalCount` is below or equal to
the specified value.
@param count The total count allowed to remain after the cache has been trimmed.
*/
- (void)trimToCount:(NSUInteger)count;
/**
Removes objects from the cache with LRU, until the `totalCost` is or equal to
the specified value.
@param cost The total cost allowed to remain after the cache has been trimmed.
*/
- (void)trimToCost:(NSUInteger)cost;
/**
Removes objects from the cache with LRU, until all expiry objects removed by the
specified value.
@param age The maximum age (in seconds) of objects.
*/
- (void)trimToAge:(NSTimeInterval)age;
@end
NS_ASSUME_NONNULL_END
+505
View File
@@ -0,0 +1,505 @@
//
// YYMemoryCache.m
// YYCache <https://github.com/ibireme/YYCache>
//
// Created by ibireme on 15/2/7.
// Copyright (c) 2015 ibireme.
//
// This source code is licensed under the MIT-style license found in the
// LICENSE file in the root directory of this source tree.
//
#import "YYMemoryCache.h"
#import <UIKit/UIKit.h>
#import <CoreFoundation/CoreFoundation.h>
#import <QuartzCore/QuartzCore.h>
#import <pthread.h>
static inline dispatch_queue_t YYMemoryCacheGetReleaseQueue() {
return dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0);
}
/**
A node in linked map.
Typically, you should not use this class directly.
*/
@interface _YYLinkedMapNode : NSObject {
@package
__unsafe_unretained _YYLinkedMapNode *_prev; // retained by dic
__unsafe_unretained _YYLinkedMapNode *_next; // retained by dic
id _key;
id _value;
NSUInteger _cost;
NSTimeInterval _time;
}
@end
@implementation _YYLinkedMapNode
@end
/**
A linked map used by YYMemoryCache.
It's not thread-safe and does not validate the parameters.
Typically, you should not use this class directly.
*/
@interface _YYLinkedMap : NSObject {
@package
CFMutableDictionaryRef _dic; // do not set object directly
NSUInteger _totalCost;
NSUInteger _totalCount;
_YYLinkedMapNode *_head; // MRU, do not change it directly
_YYLinkedMapNode *_tail; // LRU, do not change it directly
BOOL _releaseOnMainThread;
BOOL _releaseAsynchronously;
}
/// Insert a node at head and update the total cost.
/// Node and node.key should not be nil.
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node;
/// Bring a inner node to header.
/// Node should already inside the dic.
- (void)bringNodeToHead:(_YYLinkedMapNode *)node;
/// Remove a inner node and update the total cost.
/// Node should already inside the dic.
- (void)removeNode:(_YYLinkedMapNode *)node;
/// Remove tail node if exist.
- (_YYLinkedMapNode *)removeTailNode;
/// Remove all node in background queue.
- (void)removeAll;
@end
@implementation _YYLinkedMap
- (instancetype)init {
self = [super init];
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
_releaseOnMainThread = NO;
_releaseAsynchronously = YES;
return self;
}
- (void)dealloc {
CFRelease(_dic);
}
- (void)insertNodeAtHead:(_YYLinkedMapNode *)node {
CFDictionarySetValue(_dic, (__bridge const void *)(node->_key), (__bridge const void *)(node));
_totalCost += node->_cost;
_totalCount++;
if (_head) {
node->_next = _head;
_head->_prev = node;
_head = node;
} else {
_head = _tail = node;
}
}
- (void)bringNodeToHead:(_YYLinkedMapNode *)node {
if (_head == node) return;
if (_tail == node) {
_tail = node->_prev;
_tail->_next = nil;
} else {
node->_next->_prev = node->_prev;
node->_prev->_next = node->_next;
}
node->_next = _head;
node->_prev = nil;
_head->_prev = node;
_head = node;
}
- (void)removeNode:(_YYLinkedMapNode *)node {
CFDictionaryRemoveValue(_dic, (__bridge const void *)(node->_key));
_totalCost -= node->_cost;
_totalCount--;
if (node->_next) node->_next->_prev = node->_prev;
if (node->_prev) node->_prev->_next = node->_next;
if (_head == node) _head = node->_next;
if (_tail == node) _tail = node->_prev;
}
- (_YYLinkedMapNode *)removeTailNode {
if (!_tail) return nil;
_YYLinkedMapNode *tail = _tail;
CFDictionaryRemoveValue(_dic, (__bridge const void *)(_tail->_key));
_totalCost -= _tail->_cost;
_totalCount--;
if (_head == _tail) {
_head = _tail = nil;
} else {
_tail = _tail->_prev;
_tail->_next = nil;
}
return tail;
}
- (void)removeAll {
_totalCost = 0;
_totalCount = 0;
_head = nil;
_tail = nil;
if (CFDictionaryGetCount(_dic) > 0) {
CFMutableDictionaryRef holder = _dic;
_dic = CFDictionaryCreateMutable(CFAllocatorGetDefault(), 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (_releaseAsynchronously) {
dispatch_queue_t queue = _releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
CFRelease(holder); // hold and release in specified queue
});
} else if (_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
CFRelease(holder); // hold and release in specified queue
});
} else {
CFRelease(holder);
}
}
}
@end
@implementation YYMemoryCache {
pthread_mutex_t _lock;
_YYLinkedMap *_lru;
dispatch_queue_t _queue;
}
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}
- (void)_trimInBackground {
dispatch_async(_queue, ^{
[self _trimToCost:self->_costLimit];
[self _trimToCount:self->_countLimit];
[self _trimToAge:self->_ageLimit];
});
}
- (void)_trimToCost:(NSUInteger)costLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (costLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCost <= costLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCost > costLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
- (void)_trimToCount:(NSUInteger)countLimit {
BOOL finish = NO;
pthread_mutex_lock(&_lock);
if (countLimit == 0) {
[_lru removeAll];
finish = YES;
} else if (_lru->_totalCount <= countLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_totalCount > countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
- (void)_trimToAge:(NSTimeInterval)ageLimit {
BOOL finish = NO;
NSTimeInterval now = CACurrentMediaTime();
pthread_mutex_lock(&_lock);
if (ageLimit <= 0) {
[_lru removeAll];
finish = YES;
} else if (!_lru->_tail || (now - _lru->_tail->_time) <= ageLimit) {
finish = YES;
}
pthread_mutex_unlock(&_lock);
if (finish) return;
NSMutableArray *holder = [NSMutableArray new];
while (!finish) {
if (pthread_mutex_trylock(&_lock) == 0) {
if (_lru->_tail && (now - _lru->_tail->_time) > ageLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (node) [holder addObject:node];
} else {
finish = YES;
}
pthread_mutex_unlock(&_lock);
} else {
usleep(10 * 1000); //10 ms
}
}
if (holder.count) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[holder count]; // release in queue
});
}
}
- (void)_appDidReceiveMemoryWarningNotification {
if (self.didReceiveMemoryWarningBlock) {
self.didReceiveMemoryWarningBlock(self);
}
if (self.shouldRemoveAllObjectsOnMemoryWarning) {
[self removeAllObjects];
}
}
- (void)_appDidEnterBackgroundNotification {
if (self.didEnterBackgroundBlock) {
self.didEnterBackgroundBlock(self);
}
if (self.shouldRemoveAllObjectsWhenEnteringBackground) {
[self removeAllObjects];
}
}
#pragma mark - public
- (instancetype)init {
self = super.init;
pthread_mutex_init(&_lock, NULL);
_lru = [_YYLinkedMap new];
_queue = dispatch_queue_create("com.ibireme.cache.memory", DISPATCH_QUEUE_SERIAL);
_countLimit = NSUIntegerMax;
_costLimit = NSUIntegerMax;
_ageLimit = DBL_MAX;
_autoTrimInterval = 5.0;
_shouldRemoveAllObjectsOnMemoryWarning = YES;
_shouldRemoveAllObjectsWhenEnteringBackground = YES;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidReceiveMemoryWarningNotification) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(_appDidEnterBackgroundNotification) name:UIApplicationDidEnterBackgroundNotification object:nil];
[self _trimRecursively];
return self;
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
[_lru removeAll];
pthread_mutex_destroy(&_lock);
}
- (NSUInteger)totalCount {
pthread_mutex_lock(&_lock);
NSUInteger count = _lru->_totalCount;
pthread_mutex_unlock(&_lock);
return count;
}
- (NSUInteger)totalCost {
pthread_mutex_lock(&_lock);
NSUInteger totalCost = _lru->_totalCost;
pthread_mutex_unlock(&_lock);
return totalCost;
}
- (BOOL)releaseOnMainThread {
pthread_mutex_lock(&_lock);
BOOL releaseOnMainThread = _lru->_releaseOnMainThread;
pthread_mutex_unlock(&_lock);
return releaseOnMainThread;
}
- (void)setReleaseOnMainThread:(BOOL)releaseOnMainThread {
pthread_mutex_lock(&_lock);
_lru->_releaseOnMainThread = releaseOnMainThread;
pthread_mutex_unlock(&_lock);
}
- (BOOL)releaseAsynchronously {
pthread_mutex_lock(&_lock);
BOOL releaseAsynchronously = _lru->_releaseAsynchronously;
pthread_mutex_unlock(&_lock);
return releaseAsynchronously;
}
- (void)setReleaseAsynchronously:(BOOL)releaseAsynchronously {
pthread_mutex_lock(&_lock);
_lru->_releaseAsynchronously = releaseAsynchronously;
pthread_mutex_unlock(&_lock);
}
- (BOOL)containsObjectForKey:(id)key {
if (!key) return NO;
pthread_mutex_lock(&_lock);
BOOL contains = CFDictionaryContainsKey(_lru->_dic, (__bridge const void *)(key));
pthread_mutex_unlock(&_lock);
return contains;
}
- (id)objectForKey:(id)key {
if (!key) return nil;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
node->_time = CACurrentMediaTime();
[_lru bringNodeToHead:node];
}
pthread_mutex_unlock(&_lock);
return node ? node->_value : nil;
}
- (void)setObject:(id)object forKey:(id)key {
[self setObject:object forKey:key withCost:0];
}
- (void)setObject:(id)object forKey:(id)key withCost:(NSUInteger)cost {
if (!key) return;
if (!object) {
[self removeObjectForKey:key];
return;
}
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
NSTimeInterval now = CACurrentMediaTime();
if (node) {
_lru->_totalCost -= node->_cost;
_lru->_totalCost += cost;
node->_cost = cost;
node->_time = now;
node->_value = object;
[_lru bringNodeToHead:node];
} else {
node = [_YYLinkedMapNode new];
node->_cost = cost;
node->_time = now;
node->_key = key;
node->_value = object;
[_lru insertNodeAtHead:node];
}
if (_lru->_totalCost > _costLimit) {
dispatch_async(_queue, ^{
[self trimToCost:_costLimit];
});
}
if (_lru->_totalCount > _countLimit) {
_YYLinkedMapNode *node = [_lru removeTailNode];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
- (void)removeObjectForKey:(id)key {
if (!key) return;
pthread_mutex_lock(&_lock);
_YYLinkedMapNode *node = CFDictionaryGetValue(_lru->_dic, (__bridge const void *)(key));
if (node) {
[_lru removeNode:node];
if (_lru->_releaseAsynchronously) {
dispatch_queue_t queue = _lru->_releaseOnMainThread ? dispatch_get_main_queue() : YYMemoryCacheGetReleaseQueue();
dispatch_async(queue, ^{
[node class]; //hold and release in queue
});
} else if (_lru->_releaseOnMainThread && !pthread_main_np()) {
dispatch_async(dispatch_get_main_queue(), ^{
[node class]; //hold and release in queue
});
}
}
pthread_mutex_unlock(&_lock);
}
- (void)removeAllObjects {
pthread_mutex_lock(&_lock);
[_lru removeAll];
pthread_mutex_unlock(&_lock);
}
- (void)trimToCount:(NSUInteger)count {
if (count == 0) {
[self removeAllObjects];
return;
}
[self _trimToCount:count];
}
- (void)trimToCost:(NSUInteger)cost {
[self _trimToCost:cost];
}
- (void)trimToAge:(NSTimeInterval)age {
[self _trimToAge:age];
}
- (NSString *)description {
if (_name) return [NSString stringWithFormat:@"<%@: %p> (%@)", self.class, self, _name];
else return [NSString stringWithFormat:@"<%@: %p>", self.class, self];
}
@end