iOS关于文件上传与下载
前言
最近还在跟着Coding-iOS源码学习iOS开发,现在来介绍一下Coding-iOS的文件下载和上传机制。其实说实话,在写这文章的时候我对这个机制的总体把握也不是很全面;下面我将通过源码再来熟悉一遍这个全过程。这篇文章主要介绍的是下载流程。
源码先行
Coding_FileManager
Coding-iOS中将文件下载与上传有关的方法都封装在Coding_FileManager.h
和Coding_FileManager.m
文件里面。先来看看其中的主要代码👇Coding_FileManager
里面包含3个类:Coding_FileManager
类,Coding_DownloadTask
类和Coding_UploadTask
类。其中的Coding_UploadTask类与上传有关,这里不做介绍。
Coding_DownloadTask
类
Coding_DownloadTask类用来封装每一个下载任务,它包含一个下载任务,下载进度和当前下载的文件名,代码如下👇
1 | @interface Coding_DownloadTask : NSObject |
他的两个方法的实现也很简单:👇
1 | + (Coding_DownloadTask *)cDownloadTaskWith:(NSURLSessionDownloadTask *)task progress:(NSProgress *)progress fileName:(NSString *)fileName { |
Coding_FileManager
类
这个类是文件下载上传机制里面的最最最核心的类,好好来看看👇
- 类的声明
1
2
3
4
5
6
7
8
9@interface Coding_FileManager : NSObject
+ (Coding_FileManager *)sharedManager; // 单例模式
+ (AFURLSessionManager *)af_manager;
- (AFURLSessionManager *)af_manager;
+ (Coding_DownloadTask *)cDownloadTaskForKey:(NSString *)storage_key; // 根据特定的key获取特定的Coding_DownloadTask
+ (NSURL *)diskDownloadUrlForKey:(NSString *)storage_key; // 暂时不明
- (Coding_DownloadTask *)addDownloadTaskForObj:(id)obj completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler; // 对于新文件,新建下载任务
@end - 类的实现
属性
- 1个监控文件夹变化的属性:docDownloadWatcher,它是一个
DirectoryWatcher
对象,用来监控APP下载文件存放的文件夹的动态 - 2个字典,downloadDict和diskDownloadDict。downloadDict用来存放当前的下载任务(Coding_DownloadTask),它的key是storage_key;diskDownloadDict用来存放下载的文件的路径URL,它的key也是storage_key
- 1个NSURL,downloadDirectory, 用来存放下载文件的文件夹的路径URL
- 1个监控文件夹变化的属性:docDownloadWatcher,它是一个
方法
sharedManager
1
2
3
4
5
6
7
8
9
10
11
12// 单例模式初始化Coding_FileManager
+ (Coding_FileManager *)sharedManager {
static Coding_FileManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [Coding_FileManager alloc] init];
// 创建存放下载文件的文件夹
[manager urlForDownloadFolder];
});
return manager;
}init
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 初始化
- (instancetype)init {
self = [super init];
if (self) {
[[self class] createFolder:[[self class] downloadPath]]; // 创建下载目录
_downloadDict = [NSMutableDictionary alloc] init]; // 初始化存放下载任务的字典
_diskDownloadDict = [NSMutableDictionary alloc] init]; //
_downloadDirectoryURL = nil; //初始化下载目录的文件夹URL
_docDownloadWatcher = [DirectoryWatcher watchFolderWithPath:[[self class] downloadPath] delegate:self]; // 添加文件夹状态监控器
[self directoryDidChange:_docDownloadWatcher]; // 初始化的时候先来之行一遍监控程序
}
return self;
}urlForDownloadFolder
1
2
3
4
5
6
7
8
9
10
11
12
13// 创建存放下载图片的文件夹
- (NSURL *)urlForDownloadFolder {
if (!_downloadDirectoryURL) {
if ([self class] createFolder:[[self class] downloadPath]) { // 创建存放下载文件的文件夹
// 文件夹创建成功
_downloadDirectoryURL = [NSURL fileURLWithPath:[[self class] downloadPath] isDirectory:YES];
} else {
// alert create folder error
}
}
return _downloadDirectoryURL;
}downloadPath
1
2
3
4
5
6// 得到下载目录文件夹的路径, 该APP的Document目录下面的Coding_Download文件夹
+ (NSString *)downloadPath {
NSString *documentPath = [NSSearchPathForDirecrotiesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
NSString *downloadPath = [documentPath stringByAppendingPathComponent:@"Coding_Download"];
return downloadPath;
}createFolder
1
2
3
4
5
6
7
8
9
10
11
12
13
14// 创建文件夹
+ (BOOL) createFolder:(NSString *)path {
BOOL isDir = NO;
NSFileManager *fileManager = [NSFileManager defaultManager];
BOOL existed = [fileManager fileExistsAtPath:path isDirectory:&isDir];
BOOL isCreated = NO;
if (!(isDir == YES && existed == YES)) {
isCreated = [fileManager createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:nil];
} else {
isCreated = YES;
}
return isCreated;
}cDownloadTaskForKey
1
2
3
4
5
6
7
8+ (Coding_DownloadTask *)cDownloadTaskForKey:(NSString *)storage_key {
if (!storage_key) {
return nil;
}
// 当前的每个下载任务 Coding_DownloadTask 都存放在 _downloadDict 里面,对应的key是storage_key
return [self sharedManager].downloadDict objectForKey:storage_key];
}directoryDidChange:(DirectoryWatcher *)folderWatcher
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20// DirectoryWatcher的delegate, 当文件夹里面的文件变动时触发
- (void)directoryDidChange:(DirectoryWatcher *)folderWatcher {
NSMutableDictionary *diskDict = _diskDownloadDict;
NSString *path = [[self class] downloadPath];
BOOL isDownload = YES;
/* 先移除diskDict里面的所有文件URL,再将当前下载目录里面的所有文件URL添加到diskDict里面 */
[diskDict removeAllObjects];
NSArray *fileContents = [NSFileManager defaultManager] contentsOfDirectoryAtPath:path error:NULL]; // 获取path文件夹下面的所有内容,将所有文件名存放在fileContents数组里面
for (NSString *curFileName in [fileContents objectEnumerator]) { // 遍历文件名
NSString *filePath = [path stringByAppendingPathComponent:curFileName]; // 当前文件的完整的路径
NSURL *fileUrl = [NSURL fileURLWithPath:filePath]; // 当前文件的URL
BOOL isDirectory;
[NSFileManager defaultManager] fileExistsAtPath:filePath isDirectory:&isDirectory]; // 判断当前文件是否是文件夹
if (!isDirectory) { // 如果不是文件夹, 存入diskDict
NSString *keyStr = [curFileName componentsSeparatedByString:@"|"].lastObject; // 因为下载文件名命名为'IMG_0002.JPG|||264314|||QiniuStorage|c09bd7ea-6be9-431c-afc1-884293895719.JPG', 最后的|后面是storage_key
[diskDict setObject:fileUrl forKey:keyStr];
}
}
}addDownloadTaskForObj:completionHandler
1
2
3
4
5
6
7
8// 新建下载任务
- (void)addDownloadTaskForObj:(id)obj completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler {
Coding_DownloadTask *cTask = nil;
ProjectFile *file = (ProjectFile *)obj; // Coding-iOS中的一个model
cTask = [self addDownloadTaskWithPath:file.downloadPath diskFileName:fileDiskFileName storage_key:file.storage_key completionHandler:completionHandler];
return cTask;
}addDownloadTaskWithPath:diskFileName:storage_key:completionHandler
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// 新建下载任务 具体的
- (void)addDownloadTaskWithPath:(NSString *)downloadPath diskFileName:(NSString *)diskFileName storage_key:(NSString)storage_key completionHandler:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler {
NSProgress *progress;
NSURL *downloadPath = [NSURL URLWithString:downloadPath];
NSURLRequest *request = [NSURLRequest requestWithURL:downloadPath];
NSURLSessionDownloadTask *downloadTask = [self.af_manager downloadTaskWithRequest:request progress:&progress destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *downloadURL = [Coding_FileManager sharedManager] urlForDownloadFolder];
Coding_DownloadTask *cDownloadTask = [Coding_FileManager cDownloadTaskForResponse:response];
if (cDownloadTask) {
downloadUrl = [downloadUrl URLByAppendingPathComponent:cDownloadTask.diskFileName];
} else {
downloadUrl = [downloadUrl URLByAppendingPathComponent:[response suggestedFileName]];
}
return downloadUrl; // 返回存储的目标位置
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
if (error) {
[Coding_FileManager cancelCDownloadTaskForKey:storage_key];
} else {
[Coding_FileManager cancelCDownloadTaskForResponse:response];
}
}];
Coding_DownloadTask *cDownloadTask = [Coding_DownloadTask cDownloadTaskWithTask:downloadTask progress:progress fileName:diskFileName];
[self.downloadDict setObject:cDownloadTask forKey:storage_key];
[downloadTask resume];
return cDownloadTask;
}
实力分析
源码已经介绍完了,来分析一波。注意下面几个要点👇
- 每个下载任务都是一个
Coding_DownloadTask
类的实例,这个类里面有个NSURLSessionDownloadTask属性 - 所有下载有关的操作都会涉及到一个类
Coding_FileManager
,Coding会创建一个Coding_FileManager
的单例类(既:APP生命周期内只会有一个这个类的实例)。该实例有两个字典:1个负责存储已经下载下来的文件的文件URL,另一个负责存储当前还在进行的下载任务(Coding_DownloadTask)。这两个字典的key由后台定义,每个文件/任务对应唯一的一个storage_key - 所有下载的文件存放在APP的Document文件夹下面的Coding_Download文件夹下面,在创建单例类Coding_FileManager的时候检测该文件夹是否存在,不存在就创建。
- 开始一个新的下载任务其实就是创建一个NSURLSessionDownloadTask,然后再将该NSURLSessionDownloadTask封装进Coding_DownloadTask。而创建该NSURLSessionDownloadTask用的是AFURLSessionManager的downloadTaskWithRequest方法。
- 有一个监控下载文件夹内容变化的监控器,每当文件夹内容变化时(文件被删除或文件下载完成)它会自动更新存放下载文件路径URL的字典,也就是更新当前APP下载了哪些文件
总结
最后来用精炼的语言来总结一番:
- 每个下载任务/文件都有唯一与之对应的storage_key。每当新来一个下载任务时,先查看已经完成的文件里面是否包括了该文件(即:查看存放已下载文件的字典是否有该storage_key)。若已经下载,则完成下载任务;还未下载,进行第二步。
- 判断该任务是否存在(即:查看存储下载任务的字典,看是否有该storage_key)。若该任务存在,说明之前这个任务可能被暂停了,那么继续该下载任务,并跳过步骤3;若该任务不存在,那么进行第三步。
- 根据任务的storage_key,文件的url创建Coding_DownloadTask,其实主要的是创建一个NSURLSessionDownloadTask,然后将该Coding_DownloadTask存放到存储下载任务的字典中,并开始下载文件。
- 当文件下载完成时候,从存储下载任务的字典中将这个任务删除;并且文件会被存放到相应的文件夹,触发文件夹监控器的函数,将该文件的文件URL添加到存储已经下载完成的文件URL的字典中。
参考
P.S
已经过了2个大冰红茶了,一立还会远嘛~~~
All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.