seperate iOS project from main project
This commit is contained in:
22
Example/Pods/YYCache/LICENSE
generated
Normal file
22
Example/Pods/YYCache/LICENSE
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2015 ibireme <ibireme@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
171
Example/Pods/YYCache/README.md
generated
Executable file
171
Example/Pods/YYCache/README.md
generated
Executable file
@@ -0,0 +1,171 @@
|
||||
YYCache
|
||||
==============
|
||||
|
||||
[](https://raw.githubusercontent.com/ibireme/YYCache/master/LICENSE)
|
||||
[](https://github.com/Carthage/Carthage)
|
||||
[](http://cocoapods.org/?q= YYCache)
|
||||
[](http://cocoapods.org/?q= YYCache)
|
||||
[](https://www.apple.com/nl/ios/)
|
||||
[](https://travis-ci.org/ibireme/YYCache)
|
||||
|
||||
High performance cache framework for iOS.<br/>
|
||||
(It's a component of [YYKit](https://github.com/ibireme/YYKit))
|
||||
|
||||
Performance
|
||||
==============
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
You may [download](http://www.sqlite.org/download.html) and compile the latest version of sqlite and ignore the libsqlite3.dylib in iOS system to get higher performance.
|
||||
|
||||
See `Benchmark/CacheBenchmark.xcodeproj` for more benchmark case.
|
||||
|
||||
|
||||
Features
|
||||
==============
|
||||
- **LRU**: Objects can be evicted with least-recently-used algorithm.
|
||||
- **Limitation**: Cache limitation can be controlled with count, cost, age and free space.
|
||||
- **Compatibility**: The API is similar to `NSCache`, all methods are thread-safe.
|
||||
- **Memory Cache**
|
||||
- **Release Control**: Objects can be released synchronously/asynchronously on main thread or background thread.
|
||||
- **Automatically Clear**: It can be configured to automatically evict objects when receive memory warning or app enter background.
|
||||
- **Disk Cache**
|
||||
- **Customization**: It supports custom archive and unarchive method to store object which does not adopt NSCoding.
|
||||
- **Storage Type Control**: It can automatically decide the storage type (sqlite / file) for each object to get
|
||||
better performance.
|
||||
|
||||
|
||||
Installation
|
||||
==============
|
||||
|
||||
### CocoaPods
|
||||
|
||||
1. Add `pod 'YYCache'` to your Podfile.
|
||||
2. Run `pod install` or `pod update`.
|
||||
3. Import \<YYCache/YYCache.h\>.
|
||||
|
||||
|
||||
### Carthage
|
||||
|
||||
1. Add `github "ibireme/YYCache"` to your Cartfile.
|
||||
2. Run `carthage update --platform ios` and add the framework to your project.
|
||||
3. Import \<YYCache/YYCache.h\>.
|
||||
|
||||
|
||||
### Manually
|
||||
|
||||
1. Download all the files in the YYCache subdirectory.
|
||||
2. Add the source files to your Xcode project.
|
||||
3. Link with required frameworks:
|
||||
* UIKit
|
||||
* CoreFoundation
|
||||
* QuartzCore
|
||||
* sqlite3
|
||||
4. Import `YYCache.h`.
|
||||
|
||||
|
||||
Documentation
|
||||
==============
|
||||
Full API documentation is available on [CocoaDocs](http://cocoadocs.org/docsets/YYCache/).<br/>
|
||||
You can also install documentation locally using [appledoc](https://github.com/tomaz/appledoc).
|
||||
|
||||
|
||||
Requirements
|
||||
==============
|
||||
This library requires `iOS 6.0+` and `Xcode 7.0+`.
|
||||
|
||||
|
||||
License
|
||||
==============
|
||||
YYCache is provided under the MIT license. See LICENSE file for details.
|
||||
|
||||
|
||||
<br/><br/>
|
||||
---
|
||||
中文介绍
|
||||
==============
|
||||
高性能 iOS 缓存框架。<br/>
|
||||
(该项目是 [YYKit](https://github.com/ibireme/YYKit) 组件之一)
|
||||
|
||||
性能
|
||||
==============
|
||||
|
||||
iPhone 6 上,内存缓存每秒响应次数 (越高越好):
|
||||

|
||||
|
||||
iPhone 6 上,磁盘缓存每秒响应次数 (越高越好):
|
||||

|
||||
|
||||
推荐到 SQLite 官网[下载](http://www.sqlite.org/download.html)和编译最新的 SQLite,以替换 iOS 自带的 libsqlite3.dylib,以获得最高 1.5~3 倍的性能提升。
|
||||
|
||||
更多测试代码和用例见 `Benchmark/CacheBenchmark.xcodeproj`。
|
||||
|
||||
|
||||
特性
|
||||
==============
|
||||
- **LRU**: 缓存支持 LRU (least-recently-used) 淘汰算法。
|
||||
- **缓存控制**: 支持多种缓存控制方法:总数量、总大小、存活时间、空闲空间。
|
||||
- **兼容性**: API 基本和 `NSCache` 保持一致, 所有方法都是线程安全的。
|
||||
- **内存缓存**
|
||||
- **对象释放控制**: 对象的释放(release) 可以配置为同步或异步进行,可以配置在主线程或后台线程进行。
|
||||
- **自动清空**: 当收到内存警告或 App 进入后台时,缓存可以配置为自动清空。
|
||||
- **磁盘缓存**
|
||||
- **可定制性**: 磁盘缓存支持自定义的归档解档方法,以支持那些没有实现 NSCoding 协议的对象。
|
||||
- **存储类型控制**: 磁盘缓存支持对每个对象的存储类型 (SQLite/文件) 进行自动或手动控制,以获得更高的存取性能。
|
||||
|
||||
|
||||
安装
|
||||
==============
|
||||
|
||||
### CocoaPods
|
||||
|
||||
1. 在 Podfile 中添加 `pod 'YYCache'`。
|
||||
2. 执行 `pod install` 或 `pod update`。
|
||||
3. 导入 \<YYCache/YYCache.h\>。
|
||||
|
||||
|
||||
### Carthage
|
||||
|
||||
1. 在 Cartfile 中添加 `github "ibireme/YYCache"`。
|
||||
2. 执行 `carthage update --platform ios` 并将生成的 framework 添加到你的工程。
|
||||
3. 导入 \<YYCache/YYCache.h\>。
|
||||
|
||||
|
||||
### 手动安装
|
||||
|
||||
1. 下载 YYCache 文件夹内的所有内容。
|
||||
2. 将 YYCache 内的源文件添加(拖放)到你的工程。
|
||||
3. 链接以下的 frameworks:
|
||||
* UIKit
|
||||
* CoreFoundation
|
||||
* QuartzCore
|
||||
* sqlite3
|
||||
4. 导入 `YYCache.h`。
|
||||
|
||||
|
||||
文档
|
||||
==============
|
||||
你可以在 [CocoaDocs](http://cocoadocs.org/docsets/YYCache/) 查看在线 API 文档,也可以用 [appledoc](https://github.com/tomaz/appledoc) 本地生成文档。
|
||||
|
||||
|
||||
系统要求
|
||||
==============
|
||||
该项目最低支持 `iOS 6.0` 和 `Xcode 7.0`。
|
||||
|
||||
|
||||
许可证
|
||||
==============
|
||||
YYCache 使用 MIT 许可证,详情见 LICENSE 文件。
|
||||
|
||||
|
||||
相关链接
|
||||
==============
|
||||
[YYCache 设计思路与技术细节](http://blog.ibireme.com/2015/10/26/yycache/)
|
||||
|
||||
|
206
Example/Pods/YYCache/YYCache/YYCache.h
generated
Normal file
206
Example/Pods/YYCache/YYCache/YYCache.h
generated
Normal 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
Example/Pods/YYCache/YYCache/YYCache.m
generated
Normal file
139
Example/Pods/YYCache/YYCache/YYCache.m
generated
Normal 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
Example/Pods/YYCache/YYCache/YYDiskCache.h
generated
Normal file
412
Example/Pods/YYCache/YYCache/YYDiskCache.h
generated
Normal 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
Example/Pods/YYCache/YYCache/YYDiskCache.m
generated
Normal file
458
Example/Pods/YYCache/YYCache/YYDiskCache.m
generated
Normal 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
Example/Pods/YYCache/YYCache/YYKVStorage.h
generated
Normal file
325
Example/Pods/YYCache/YYCache/YYKVStorage.h
generated
Normal 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
|
1069
Example/Pods/YYCache/YYCache/YYKVStorage.m
generated
Normal file
1069
Example/Pods/YYCache/YYCache/YYKVStorage.m
generated
Normal file
File diff suppressed because it is too large
Load Diff
213
Example/Pods/YYCache/YYCache/YYMemoryCache.h
generated
Normal file
213
Example/Pods/YYCache/YYCache/YYMemoryCache.h
generated
Normal 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
Example/Pods/YYCache/YYCache/YYMemoryCache.m
generated
Normal file
505
Example/Pods/YYCache/YYCache/YYMemoryCache.m
generated
Normal 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
|
Reference in New Issue
Block a user