iOS文件统一管理

  最近在做我们公司自己的项目时,遇到了一个难题,就是随着用户使用我们公司的app时间越久,便导致app沙盒文件越来越多,严重时我查看我们公司的app使用内存量达到了7.3G,查看方式,打开你的苹果手机,(设置-通用-储存空间及iCloud用量-管理储存空间);怎么样不查不知道一查是不是吓一跳?
  细说下我们公司app占用内存量大的原因,我们公司的app属于类社交和OA类,其中有类似微信的朋友圈和聊天,及收藏功能,所以当用户发图片很多时,图片都存入自己的沙盒中,当用户在聊天页面点击图片收藏,图片会复制一份并生成新的图片再次上传服务器,同时新生成的图片也会copy到我们已经规定好的沙盒中的收藏文件夹(之所以给每个模块都在沙盒目录中规定一个文件夹,为了便于清理,注:每个聊天会话的图片都存在它特定的文件夹下,删除某一条会话时,会话下的图片也能得到及时清除)下。同理,在朋友圈中的图片也可以收藏。所以当图片少的时候暂时没有发现问题,而且似乎感觉还很好,因为每次文件清理直接拿到每一个模块的路径文件夹清理就可以。然而,随着时间的推移,用户选择的文件的越来越多,便产生一开始描述的问题。附路径地址:

/Users/liangju/Library/Developer/CoreSimulator/Devices/6184D61E-A799-4127-881E-F71E3D9CD61B/data/Containers/Data/Application/F83F1BC6-4809-40B9-80EC-D1EC7FEF2FBA/Documents/my_app/1BdWrWKyJ8MUpM7ofJsa4g/file/CC_OPEN_MESSAGE/
g_2BwfR0iZJedUvP6ispaOAp/168D8A9C8830461AAC6C6711C9136115.jpg

/Users/liangju/Library/Developer/CoreSimulator/Devices/6184D61E-A799-4127-881E-F71E3D9CD61B/data/Containers/Data/Application/F83F1BC6-4809-40B9-80EC-D1EC7FEF2FBA/Documents/my_app/1BdWrWKyJ8MUpM7ofJsa4g/file/CC_OPEN_MESSAGE/
u_2aRr2i4UF5dkXExQMVTly1I/317C7D7B10D847B09F4CE827BC8EB78F.jpg
其中地址中my_app 为app名字,1BdWrWKyJ8MUpM7ofJsa4g为登录人id,CC_OPEN_MESSAGE为服务id,也就是下文中的SERV_ID,u_2aRr2i4UF5dkXExQMVTly1I为绘画id,其中我们公司规定以u开头是单人绘画,g开头为群组绘画,同时此项也就是下文中的DATA_ID,再往下就是image对象名字了
  所以便想能不能把这些文件统一管理起来,比如聊天中的图片再收藏时(前提此图片已经上传服务器成功,上传失败的情况我们稍后讨论),能不能不在复制产生新的图片上传,收藏模块的图片直接去聊天的那张图片路径下,把这张图片读出显示。
  实现篇:统一封住文件最终下载所调的接口。每个文件下载时都给它固定了一个下载路径。当它下载成功后,把文件对应的服务器的文件id,保存到数据表中。结合每个公司的不同情况合理设置缓存模型,我们公司的设置如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 文件在服务端的id 
@property (nonatomic, copy) NSString *SERVER_FILEID;

// 功能分类:例如:在聊天中就是CC_OPEN_MESSAGE
@property (nonatomic, copy) NSString *SERV_ID;

// 在chat功能中就是msgId
@property (nonatomic, copy) NSString *DATA_ID;

// 文件长度
@property (nonatomic, copy) NSString *FILE_SIZE;

// 待定:数据创建时间
@property (nonatomic, copy) NSString *S_ATIME;

// 待定:数据创建时间
@property (nonatomic, copy) NSString *S_MTIME;

// 本地相对路径
@property (nonatomic, copy) NSString *PATH;

其次在每次下载图片的时候,1,先从sd内存缓存里读出图片,若读出直接block image对象;2,若不存在image,则需要先根据SERVER_FILEID查一下本地是否已经存在那条数据,如果存在,根据路径读出图片,再根据SERV_ID、DATA_ID、SERVER_FILEID再次查找本地表是否存在这条数据,如果不存在,则新建一条数据;当根据SERVER_FILEID在本地表中查找不到时,继续往下走,调用图片下载方法(我们采用的图片下载三方为sd),以下为相关代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
- (void)rh_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock rhImgParam:(RHImageParam *)rhImgParam {
//modify
if (!url) {
return;
}
NSDictionary *dict = [RHFileCacheHelper getServeFileIdAndImageSizeWithServerUrl:url];
NSString *fileId = dict[@"FIELD"];
NSString *image_size = dict[@"IMG_SIZE"];
NSDictionary *paramDict = rhImgParam.extraDict;
NSString *serv_Id = paramDict ? paramDict[@"SERV_ID"] : @"";
NSString *data_Id = paramDict ? paramDict[@"DATA_ID"] : @"";

if (rhImgParam.category) {
url = [[RHWebImageManager sharedManager] changeURL:url param:rhImgParam];
}

// 0 表示未传值 (默认)
if (options == 0 || ((options & SDWebImageRefreshCached) != SDWebImageRefreshCached)) { // 1,内存缓存读出图片
NSString *key = [rhImgParam.manager cacheKeyForURL:url];
UIImage *image = [rhImgParam.manager.imageCache imageFromDiskCacheForKey:key];
if (image) {
self.image = image;
if (completedBlock) {
completedBlock(image, nil, SDImageCacheTypeMemory, url);
}
return;
} else {
// 2,从缓存表中读出图片
if ([image_size integerValue] > 300) {
image = [RHFileCacheHelper fetchSqlCacheImageWithServId:serv_Id dataId:data_Id serFileId:fileId];
if (image) { // servId dataId servFileId
self.image = image;
if (completedBlock) {
completedBlock(image, nil, SDImageCacheTypeDisk, url);
}
return;
}
}
}
}
[self rh_cancelCurrentImageLoad];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

if (!(options & SDWebImageDelayPlaceholder)) {
dispatch_main_async_safe(^{
self.image = placeholder;
});
}

if (url) {
__weak __typeof(self)wself = self;
// id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
id <SDWebImageOperation> operation = [rhImgParam.manager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (!wself) return;
dispatch_main_sync_safe(^{
if (!wself) return;
if (image) {
wself.image = image;
[wself setNeedsLayout];
if (([image_size integerValue] > 300) && image) { // 保存图片到缓表的操作
NSString *fileName = [RHTools cachedFileNameForKey:url.absoluteString param:rhImgParam];
NSString *fullPath = [RHCommonHelper getCacheFileFullPathWithServID:serv_Id dataId:data_Id imageId:fileName];
NSInteger length = [UIImageJPEGRepresentation(image,1) length];
RHFileCacheModel *fileModel = [RHFileCacheHelper getFileCacheModelWithServ_Id:serv_Id dataId:data_Id server_FileId:fileId fileLocalFullPath:fullPath fileSize:([NSString stringWithFormat:@"%ld", (long)length])];
[[RHFileCacheManager sharedInstance]saveFileCacheToLocalWithFileCacheModel:fileModel];
}
} else {
if ((options & SDWebImageDelayPlaceholder)) {
wself.image = placeholder;
[wself setNeedsLayout];
}
}
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url);
}
});
}];
[self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
} else {
dispatch_main_async_safe(^{
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
if (completedBlock) {
completedBlock(nil, error, SDImageCacheTypeNone, url);
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
/**
* 根据servId、dataId,serFileId读出混存表中的图片
* @param servId 服务id
* @param dataId dataid
* @param serFileId 服务端文件id
* @return image对象
*/
+ (UIImage *)fetchSqlCacheImageWithServId:(NSString *)servId dataId:(NSString *)dataId serFileId:(NSString *)serFileId {
NSArray *fileArray = [[RHFileCacheManager sharedInstance]fetchFileCacheFileWithServer_FileId:serFileId]; // 根据serFileId 查询缓存表并转换为缓存model读出
if (fileArray.count > 0) {
RHFileCacheModel *fileModel = [fileArray firstObject]; // 缓存model
NSString *path = [RHCommonHelper getFullPathWithRelativePath:fileModel.PATH]; // 获取图片的全路径
UIImage *image = [UIImage imageWithContentsOfFile:path];
if (image) { // servId dataId servFileId
NSArray *tmpArray = [[RHFileCacheManager sharedInstance]fetchFileCacheFileWithServId:servId dataId:dataId serverFileId:serFileId];
if (tmpArray.count == 0) {
fileModel.ID = [RHTools stringUUID]; // uuid 随机生成的数据
fileModel.S_ATIME = [RHDateTools currenTime];
fileModel.S_MTIME = [RHDateTools currenTime];
fileModel.SERV_ID = servId;
fileModel.DATA_ID = dataId;
fileModel.SERVER_FILEID = serFileId;
[[RHFileCacheManager sharedInstance]saveFileCacheToLocalWithFileCacheModel:fileModel];
}
return image;
}
}
return nil;
}

接下来我们来处理图片上传的情况:在上传图片接口;无论成功,失败都要将其结果保存数据库,只是失败时不存在SERVER_FILEID;其次在处理上传成功的图片,在其保存前先根据SERV_ID,DATA_ID,PATH查寻缓存表,看这条数据是否已经存在,若存在,只需更新表,把SERVER_FILEID更新至表中,若不存在,则生成一条新的数据保存至表中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
处理上传成功的图片
* @param servId 服务Id
* @param dataId dataId
* @param serverFileId 服务端文件Id
* @param fullPath 全路径
*
*/
+ (void)dealSuccessPhotosWithServId:(NSString *)servId dataId:(NSString *)dataId serverFileId:(NSString *)serverFileId fileLocalFullPath:(NSString *)fullPath {
if ([RHTools checkIfPreviewImage:serverFileId] && [FCFileManager existsItemAtPath:fullPath]) { // 根据serverFileId后缀名检查是否是图片,并且本地路径下是否存在该文件
NSArray *cacheOldArr = [[RHFileCacheManager sharedInstance]fetchFileCacheFileWithServId:servId dataId:dataId fullPath:fullPath];
RHFileCacheModel *fileModel = [[RHFileCacheModel alloc]init];
if (cacheOldArr.count == 0) { // 缓存表中不存在这一条记录
fileModel = [self p_getFileCacheModelWithServId:servId dataId:dataId serverFileId:serverFileId fileLocalFullPath:fullPath]; // 生成缓存模型
} else {
fileModel = [cacheOldArr firstObject];
fileModel.PATH = [RHCommonHelper getRelativePathWithfullPath:fullPath]; // 根据全路径获取相对路径
fileModel.SERVER_FILEID = serverFileId;
}
[[RHFileCacheManager sharedInstance]saveFileCacheToLocalWithFileCacheModel:fileModel]; // 保存到缓存表
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
* 处理上传失败的图片数组
* @param servId 服务iId
* @param dataId dataId
* @param failures 失败数组
*/

+ (void)dealPostFailPhotosWithServId:(NSString *)servId dataId:(NSString *)dataId failures:(NSArray *)failures {
for (NSDictionary *content in failures) {
NSString *fullpath = content[@"fileURL"];
if ([FCFileManager existsItemAtPath:fullpath]) {
RHFileCacheModel *fileModel = [self p_getFileCacheModelWithServId:servId dataId:dataId serverFileId:@"" fileLocalFullPath:fullpath];
[[RHFileCacheManager sharedInstance]saveFileCacheToLocalWithFileCacheModel:fileModel];
}
}
}

  删除逻辑:删除时首先根据SERVER_FILEID查表中数据,如果大于1,说明此文件被多个地方引用,不删除文件,仅根据SERV_ID,DATA_ID,SERVER_FILEID,删出表中这条数据;若根据SERVER_FILEID查表中数据等于1,删除文件,同时根据SERV_ID,DATA_ID,SERVER_FILEID,删出表中这条数据。