现在的大多数APP都带有缓存功能,尤其是使用一些第三方框架,比如SDWebImage 就自带缓存功能。当用了一段时间后,缓存就会越来愈大,如果不清除的话就会影响用户体验和性能,所以大多📚APP加了清除缓存的功能。这里的主要实现思路也是根据SDWebImage 的思路去实现。这里需要用到一个
simpholders
工具可以直接查看沙盒下的东西。
1.获取缓存
既然是参考SDWebImage去实现,那么首先就是进去看看他是怎么实现的,首先先导入
#import <SDWebImage/SDImageCache.h>
头文件,这是SDWebImage 的一个管理缓存的类,点进去在文件中搜索关键词cache
,会看到好多结果,一直往下拉,会看到这么几个方法:
我们看到里面有这样一个方法,很明显这个是我们常写的单例。待会儿会用到
/**
* Returns global shared cache instance
*
* @return SDImageCache global instance
*/
+ (SDImageCache *)sharedImageCache;
再往下就看到下面的方法:
这些根据字面意思很容易看出来都是干什么的。既然这里我们的第一步是想先获取缓存,那么我们就主要先看一下
getSize
方法,点击进去会看到如下方法:
- (NSUInteger)getSize {
__block NSUInteger size = 0;
dispatch_sync(self.ioQueue, ^{
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
for (NSString *fileName in fileEnumerator) {
NSString *filePath = [self.diskCachePath stringByAppendingPathComponent:fileName];
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];
size += [attrs fileSize];
}
});
return size;
}
这里我们看到需要用到一个 _fileManager
的对象,这是一个NSFileManager
的对象,我们先回到我们的类文件中,去调用一下getSize 方法获取缓存。通过上面的工具我们可以查看SDWebImage的缓存文件:在default文件下面就是
NSUInteger cach = [[SDImageCache sharedImageCache] getSize];
NXLog(@"sdweb-----caches:%lu",(unsigned long)cach);
打印结果如下
2017-02-06 23:41:09.554 BaiSi[44360:1314372] sdweb-----caches:2824429
通过右键查看文件,得到结果如下,比打印结果大是因为这里还包括了隐藏文件.DS
我们也以获取这个文件夹缓存的大小为试验,看看自己写的结果是否和SD 的一样:
- (void)clearCach{
//1.先获取cache 目录的路径
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 这里先以获取 default 文件夹大小做实验,看看是否和 SDImageCache 获取的一样
NSString * directorPath = [cachePath stringByAppendingPathComponent:@"default"];
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
NSDictionary * attrs = [fileManager attributesOfItemAtPath:directorPath error:nil];
//4.最后获取size
NSUInteger size = [attrs fileSize];
NXLog(@"custom----size:%lu",(unsigned long)size);
//打印结果如下
// 2017-02-06 23:47:01.843 BaiSi[44442:1317486] custom----size:136
}
可以看到最后的结果和SD差了相当多,初步判断这个可能不可以获取文件夹的大小,智能或许文件的大小,为了验证,我们拼接一个文件的路径,在上面第一步获取路径下面再加上一个image 的路径,如下:
NSString * imagePath = [directorPath stringByAppendingPathComponent:@"com.hackemist.SDWebImageCache.default/aa96232fe024aeba562815618fd5e02a.jpg"];
第三步里的directorPath 改成 imagePath ,运行
//打印结果如下:
2017-02-07 00:00:04.244 BaiSi[44688:1326243] custom----size:169817
我们直接通过查看图片属性结果如下:
和我们得到的结果一模一样,说明该属性方法只是获取文件的大小,所以我们需要遍历目的文件夹下的所有文件,将大小累加起来,就是该文件夹的总大小。
在SD 的 getSize 方法里面我们可以看到他调用的是
NSDirectoryEnumerator *fileEnumerator = [_fileManager enumeratorAtPath:self.diskCachePath];
方法,然后去遍历fileEnumerator
。这里我们可以用另一个属性subpathsAtPath:directorPath
,改写clearCach
如下:
- (void)clearCach{
//1.先获取cache 目录的路径
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 这里先以获取 default 文件夹大小做实验,看看是否和 SDImageCache 获取的一样
NSString * directorPath = [cachePath stringByAppendingPathComponent:@"default"];
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
//这个可以获取所有的子文件及子文件下的文件
NSArray * subPaths = [fileManager subpathsAtPath:directorPath];
NXLog(@"subPaths:%@",subPaths);
NSDictionary * attrs = [fileManager attributesOfItemAtPath:directorPath error:nil];
//4.最后获取size
NSUInteger size = [attrs fileSize];
NXLog(@"custom----size:%lu",(unsigned long)size);
//打印结果如下
// 2017-02-06 23:47:01.843 BaiSi[44442:1317486] custom----size:136
}
打印结果:
可以看到 这个方法获取到了default 文件夹下的子文件夹及子文件夹下的文件以及隐藏文件.接下来我们继续修改clearCach 方法:
- (void)clearCach{
//1.先获取cache 目录的路径
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 这里先以获取 default 文件夹大小做实验,看看是否和 SDImageCache 获取的一样
NSString * directorPath = [cachePath stringByAppendingPathComponent:@"default"];
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
//这个可以获取所有的子文件及子文件下的文件
NSArray * subPaths = [fileManager subpathsAtPath:directorPath];
NSUInteger totoalSize = 0 ;
for (NSString * path in subPaths) {
NSString * filePath = [directorPath stringByAppendingPathComponent:path];
NSDictionary * attrs = [fileManager attributesOfItemAtPath:filePath error:nil];
//4.最后获取size
totoalSize += [attrs fileSize];
}
NXLog(@"custom----size:%lu",(unsigned long)totoalSize);
}
最终打印结果如下
基本上和SD 自己获取的大小差不多,这里比SD 的大是因为这里没有排除隐藏文件.DS_Store和下面那个default 文件夹,接下来对这两个文件进行排除:
- (void)clearCach{
//1.先获取cache 目录的路径
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 这里先以获取 default 文件夹大小做实验,看看是否和 SDImageCache 获取的一样
NSString * directorPath = [cachePath stringByAppendingPathComponent:@"default"];
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
//这个可以获取所有的子文件及子文件下的文件
NSArray * subPaths = [fileManager subpathsAtPath:directorPath];
NSUInteger totoalSize = 0 ;
for (NSString * path in subPaths) {
NSString * filePath = [directorPath stringByAppendingPathComponent:path];
//判断是否是隐藏文件
if ([filePath containsString:@".DS"]) {
continue;
}
//判断是否是文件夹
BOOL isDirector;
BOOL fileExist = [fileManager fileExistsAtPath:filePath isDirectory:&isDirector];
if (!fileExist || isDirector) {
continue;
}
NSDictionary * attrs = [fileManager attributesOfItemAtPath:filePath error:nil];
//4.最后获取size
totoalSize += [attrs fileSize];
}
NXLog(@"custom----size:%lu",(unsigned long)totoalSize);
}
这个时候得到的结果就和SD 的一样了。
上面的结果基本上已经可以满足功能了,不过为了复用性,还得进行一些修改,最好是能传入一个文件夹的路径然后获取大小,这里吧方法名改了一下:
- (NSUInteger)getFileSize:(NSString *)directorPath{
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
//这个可以获取所有的子文件及子文件下的文件
NSArray * subPaths = [fileManager subpathsAtPath:directorPath];
NSUInteger totoalSize = 0 ;
for (NSString * path in subPaths) {
NSString * filePath = [directorPath stringByAppendingPathComponent:path];
//判断是否是隐藏文件
if ([filePath containsString:@".DS"]) {
continue;
}
//判断是否是文件夹
BOOL isDirector;
BOOL fileExist = [fileManager fileExistsAtPath:filePath isDirectory:&isDirector];
if (!fileExist || isDirector) {
continue;
}
NSDictionary * attrs = [fileManager attributesOfItemAtPath:filePath error:nil];
//4.最后获取size
totoalSize += [attrs fileSize];
}
NXLog(@"custom----size:%lu",(unsigned long)totoalSize);
return totoalSize;
}
// 然后在调用的地方如下就可以了:
//1.先获取cache 目录的路径
NSString * cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
// 这里先以获取 default 文件夹大小做实验,看看是否和 SDImageCache 获取的一样
NSString * directorPath = [cachePath stringByAppendingPathComponent:@"default"];
[self getFileSize:directorPath];
上面的结果已经基本满足功能了。但是如果是遇到一个很大的文件夹的话,该操作可能会造成卡线程,所以最好能放到异步线程里去操作。由于是异步操作,这里就不适合用return 来返回结果了,可以用block的形式来传结果:
- (void)getFileSize:(NSString *)directorPath completion:(void(^)(NSUInteger totoalSize))completion{
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//这个可以获取所有的子文件及子文件下的文件
NSArray * subPaths = [fileManager subpathsAtPath:directorPath];
NSUInteger totoalSize = 0 ;
for (NSString * path in subPaths) {
NSString * filePath = [directorPath stringByAppendingPathComponent:path];
//判断是否是隐藏文件
if ([filePath containsString:@".DS"]) {
continue;
}
//判断是否是文件夹
BOOL isDirector;
BOOL fileExist = [fileManager fileExistsAtPath:filePath isDirectory:&isDirector];
if (!fileExist || isDirector) {
continue;
}
NSDictionary * attrs = [fileManager attributesOfItemAtPath:filePath error:nil];
//4.最后获取size
totoalSize += [attrs fileSize];
}
//完成之后回调
//这里需要在主线程去回调。不然接收不到值。这里可以用同步操作,不会太耗时
dispatch_sync(dispatch_get_main_queue(), ^{
if (completion) {
completion(totoalSize);
}
});
});
}
到这一步已经可以完成了。不过我们还可以在完善一点,因为我们永远无法想象用户会做什么操作。同样我们将这样的方法丢给同事去调用,也无法去想象他们会传一些什么稀奇古怪的参数,比如,他直接给你传个文件的路径而不是文件夹,所以我们还得加一些异常处理,比如抛异常,就像苹果经常干的那样。
最终我们修改后的方法如下:
- (void)getFileSize:(NSString *)directorPath completion:(void(^)(NSUInteger totoalSize))completion{
//2.声明一个文件管理对象
NSFileManager * fileManager = [NSFileManager defaultManager];
// 在这里做一些异常的处理
//判断是否是文件夹
BOOL isDirector;
BOOL fileExist = [fileManager fileExistsAtPath:directorPath isDirectory:&isDirector];
if (!fileExist || !isDirector) {
NSException * exception = [NSException exceptionWithName:@"PathError" reason:@"hey,man,这里需要的是一个文件夹的路径,并且是存在的" userInfo:nil];
[exception raise];
}
//3.在 SDImageCache 的getSize 方法里面我们看到调用了一个方法 NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:filePath error:nil];所以我们也模仿他去做
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//这个可以获取所有的子文件及子文件下的文件
NSArray * subPaths = [fileManager subpathsAtPath:directorPath];
NSUInteger totoalSize = 0 ;
for (NSString * path in subPaths) {
NSString * filePath = [directorPath stringByAppendingPathComponent:path];
//判断是否是隐藏文件
if ([filePath containsString:@".DS"]) {
continue;
}
//判断是否是文件夹
BOOL isDirector;
BOOL fileExist = [fileManager fileExistsAtPath:filePath isDirectory:&isDirector];
if (!fileExist || isDirector) {
continue;
}
NSDictionary * attrs = [fileManager attributesOfItemAtPath:filePath error:nil];
//4.最后获取size
totoalSize += [attrs fileSize];
}
//完成之后回调
//这里需要在主线程去回调。不然接收不到值。这里可以用同步操作,不会太耗时
dispatch_sync(dispatch_get_main_queue(), ^{
if (completion) {
completion(totoalSize);
}
});
});
}
然后在调用的时候传入一个错误的地址,正如我们所料,Xcode崩了,在控制台出来结果如下:
OK,可以收工了。
清除缓存
清楚缓存相对比较简单,直接贴代码吧:
- (void)removeFiles:(NSString *)directorPath{
NSFileManager * manager = [NSFileManager defaultManager];
//这个方法是获取该文件下的所有子文件夹,不会获取子文件夹里的文件(这里没必要了,直接删除文件夹就可以)
NSArray * subDirectors = [manager contentsOfDirectoryAtPath:directorPath error:nil];
for (NSString * path in subDirectors) {
NSString * filePath = [directorPath stringByAppendingPathComponent:path];
//删除文件夹
[manager removeItemAtPath:filePath error:nil];
}
}