我正在参加「启航方案」
前语
项目自从接入 unity 后,关于资源方面下载数据就增多了,下载种类也变的不一样了。以前手动下载,直接调一下封装好的的 API 就好,也没什么难度,但现在又说什么静默下载,预下载,手动下载,WIFI时候下载,4G网络不下载等等。说白了便是需求做一个下载的优先级办理了。
思路
咱们做一个下载,必定有开端,暂停,撤销,持续下载,重复下载等等情况,如果有大量的 URL 在一起下载,然后 URL 的状况可能会随时发生变化,那怎么做会比较好操控状况呢。这儿供给一个思路,首要弄一个下载行列就设置最大下载数为3个,也便是说并发下载就只要3个,剩下的都放在等候行列傍边。一旦有下载完结的,或许失利的,就移除当前的,去等候行列傍边随机取出第一个,放到下载行列傍边。这样的优点是什么呢,便是我下载的线程永远最多只要3个,这样很简单方便咱们去保护状况,这便是我个人思路。
下载办理
现在咱们就按照上面的思路去写,首要咱们创立一个下载办理的单例 DownloadManager,先增加一个 NSURLSession
。
@interface DownloadManager () <NSURLSessionDelegate, NSURLSessionDownloadDelegate>
@property (nonatomic, strong) NSURLSession *session;
// 锁
@property (nonatomic, strong) NSLock *downloadsLock;
// 下载中的行列
@property (nonatomic, strong) NSMutableDictionary *downloads;
// 等候中的行列
@property (nonatomic, strong) NSMutableDictionary *waitDownloads;
// 最大下载数据
@property (nonatomic, assign) NSInteger downloadMaxCount;
@end
先进行 NSURLSession
初始化,然后增加2个字典,1个为下载中的字典,1个为等候中的字典。
- (instancetype)init
{
self = [super init];
if (self)
{
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
self.session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
self.downloadsLock = [[NSLock alloc] init];
self.downloads = [NSMutableDictionary new];
self.waitDownloads = [NSMutableDictionary new];
self.downloadMaxCount = 3;
}
return self;
}
接着咱们编写一个下载的办法,记住的是每次获取字典对应的下载目标DownloadObject
,增加修正删除等必须增加锁。
这儿的一个重点是,如果 urlString
就在咱们字典傍边,需求掩盖之前的,包括优先级。
- (void)downloadFileForURL:(NSString *)urlString
fileName:(NSString *)fileName
directory:(NSString *)directory
priority:(OPRDownloadPriority)priority
progressBlock:(void(^)(CGFloat progress))progressBlock
completionBlock:(void(^)(BOOL completed, NSInteger code))completionBlock {
// 资源原本就在本地
if ([self fileExistsWithName:fileName inDirectory:directory])
{
completionBlock(YES,0);
return;
}
{
[self.downloadsLock lock];
// 所有下载行列
NSMutableDictionary *allDownloads = [self allDownloads];
DownloadObject *download = [allDownloads objectForKey:urlString];
[self.downloadsLock unlock];
if (download)
{
// 原本就在行列中
download.completionBlock = completionBlock;
download.progressBlock = progressBlock;
download.priority = priority;
return;
}
}
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
DownloadObject *downloadObject = [[DownloadObject alloc] initWithDownloadTask:downloadTask
progressBlock:progressBlock
completionBlock:completionBlock];
downloadObject.fileName = fileName;
downloadObject.directoryName = directory;
downloadObject.priority = priority;
[self.downloadsLock lock];
//下载行列少于最大下载数量3,则把URLString 增加到行列中,否则放到等候行列
if (self.downloads.count < self.downloadMaxCount) {
[self.downloads addEntriesFromDictionary:@{urlString:downloadObject}];
[downloadTask resume];
}else {
[self.waitDownloads addEntriesFromDictionary:@{urlString:downloadObject}];
}
[self.downloadsLock unlock];
}
咱们 NSURLSession
会监听下载状况,第一个是接受服务端回来的数据。这儿咱们主要是用来监听 URLString
的下载进展,咱们DownloadObject
里边就保存了progressBlock
用来回来进展。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSString *fileIdentifier = downloadTask.originalRequest.URL.absoluteString;
if (!fileIdentifier)
{
return;
}
[self.downloadsLock lock];
DownloadObject *download = [self.downloads objectForKey:fileIdentifier];
if (download.progressBlock)
{
CGFloat progress = (CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^(void) {
download.progressBlock(progress);
});
}
[self.downloadsLock unlock];
}
第二个便是下载成功回调。这时候咱们首要需求确保咱们的目录是存在的,没有就创立一个。
重点是把下载的location
,移动到咱们的directoryName
傍边。而且最终需求把 URLString
移出下载行列,然后咱们再去等候行列中,看是否有再等候的数据,有就增加到下载行列持续下载。
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSString *fileIdentifier = downloadTask.originalRequest.URL.absoluteString;
NSURL *destinationLocation;
// 如果目录没有创立,就创立一个目录
if (download.directoryName
&& [self createDirectoryNamed:download.directoryName])
{
destinationLocation = [[[self cachesDirectoryUrlPath] URLByAppendingPathComponent:download.directoryName] URLByAppendingPathComponent:download.fileName];
}
// 把下载的TMP目录移动到咱们下载需求保存的目录
[[NSFileManager defaultManager] moveItemAtURL:location
toURL:destinationLocation
error:&error];
if (download.completionBlock)
{
dispatch_async(dispatch_get_main_queue(), ^(void) {
download.completionBlock(success,200);
});
}
[self.downloads removeObjectForKey:fileIdentifier];
[self addWaitQueueToDownload];
[self.downloadsLock unlock];
}
最终一个是下载错误。不管是超时仍是其它原因,这儿都给多一次重试的机会,最终也是经过completionBlock
回调给下载的办法,然后咱们再去等候行列中,看是否有再等候的数据,有就增加到下载行列持续下载,这儿和下载完结逻辑是一样的。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
if (error)
{
NSString *fileIdentifier = task.originalRequest.URL.absoluteString;
BOOL retry = NO;
if(error.code == NSURLErrorTimedOut)
{
retry = YES;
}
[self.downloadsLock lock];
DownloadObject *download = [self.downloads objectForKey:fileIdentifier];
if(!retry && !download.hasRetry)
{
retry = YES;
download.hasRetry = YES;
}
if(retry)
{
// 重试下载
NSURLRequest *request = [NSURLRequest requestWithURL:task.currentRequest.URL];
NSURLSessionDownloadTask *downloadTask = [self.session downloadTaskWithRequest:request];
download.downloadTask = downloadTask;
[downloadTask resume];
}
else
{
if (download.completionBlock)
{
dispatch_async(dispatch_get_main_queue(), ^(void) {
download.completionBlock(NO,error.code);
});
}
[self.downloads removeObjectForKey:fileIdentifier];
[self addWaitQueueToDownload];
}
[self.downloadsLock unlock];
}
}
至此,咱们下载的实现就基本完结了。咱们还需求做的,便是加入撤销单个下载办法,暂停单个下载的办法,持续下载单个的下载办法,这些都可以基于上述2个行列进行调整。上述代码有些还不行完好,但关于开发者来说问题不大。
最终
下载文件办理来说,上述的方案其实也是基于现有事务进行调整的,之前是一个下载的行列,现在拆分出2个,然后加了一个自定义的优先级priority
,和体系的类似,咱们去等候行列中拿数据,也是取出当前优先级最高的,然后增加到下载行列傍边。其实也不能说是行列,便是2个字典,说的好听一点罢了。对此你觉得有什么更好的方案去做呢,欢迎留意。