废话不多所,直接上代码
github网址:https://github.com/SPStore/SPHTTPSessionManager
.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, SPDownloadWay) {
SPDownloadWayResume, // 这种下载方式支持重启app时继续上一次的下载
SPDownloadWayRestart // 这种下载方式不支持重启app时继续上一次的下载
// SPDownloadWayResume和SPDownloadWayRestart的下载方式具体异同点如下:
/*
1、SPDownloadWayResume和SPDownloadWayRestart均支持断点下载
2、SPDownloadWayResume支持重启app继续上一次下载,SPDownloadWayRestart不支持
3、SPDownloadWayResume会自动判断是否下载完成,并保存每时每刻下载的进度值,SPDownloadWayRestart没有此功能
4、SPDownloadWayResume支持任何时刻删除已经下载的文件数据,SPDownloadWayRestart不支持在下载过程中删除,只有下载完成时才能删除
5、SPDownloadWayResume不依赖于AFN,SPDownloadWayRestart依赖AFN
通俗的讲,SPDownloadWayResume和SPDownloadWayRestart的根本区别就是前者是沙盒模式,后者是内存模式
*/
};
NS_ASSUME_NONNULL_BEGIN
@interface SPHTTPSessionManager : NSObject
/** 单例对象 */
+ (instancetype)shareInstance;
/**
* get请求
*
* @param urlString 请求地址
* @param params 参数字典
* @param success 请求成功回调的block
* @param failure 请求失败回调的block
*/
- (void)GET:(NSString *)urlString
params:(NSDictionary *)params
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure;
/**
* post请求
*
* @param urlString 请求地址
* @param params 参数字典
* @param success 请求成功回调的block
* @param failure 请求失败回调的block
*/
- (void)POST:(NSString *)urlString
params:(NSDictionary *)params
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure;
/**
* 下载
*
* @param urlString 请求地址
* @param downloadProgressBlock 下载过程中回调的block
* @complete 下载完成回调的block
*/
- (NSURLSessionTask *)downloadWithURL:(NSString *)urlString
progress:(void (^)(CGFloat progress))downloadProgressBlock
complete:(void (^)(NSURLResponse *response, NSURL *filePath, NSError *error))completionHandler;
/*
* 上传 http://www.cnblogs.com/qingche/p/5489434.html
*
*/
- (void)uploadWithURL:(NSString *)urlString
params:(NSDictionary *)params
fileData:(NSData *)filedata
name:(NSString *)name
fileName:(NSString *)filename
mimeType:(NSString *) mimeType
progress:(void (^)(NSProgress *uploadProgress))uploadProgressBlock
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure;
// 以下这些操作在外界也可以另外做到,比如启动和暂停任务,外界在调用下载的方法时返回了一个task,开发者可以用该task去启动和暂停任务,之所以将其封装,一是:这个类能做到的尽量不让开发者去做,二是:让开发者完全面向我这个单例对象。开发者只需要做一些关于UI的事情
// 下载方式
@property (nonatomic, assign) SPDownloadWay downloadway;
/*
* 启动任务
*
*/
- (void)resumeTask;
/*
* 暂停任务
*
*/
- (void)suspendTask;
/*
* 取消任务
*
*/
- (void)cancelTask;
/*
* 移除已经下载好的文件数据
*
*/
- (BOOL)removeDownloadedData:(NSError * _Nullable __autoreleasing * _Nullable)error;
/** 是否正在下载,对于SPDownloadResume下载方式,该属性来源于沙盒,对于SPDownloadRestart下载方式,该属性来源于内存 */
@property (nonatomic, assign, readonly, getter=isDownloading) BOOL downloading;
// 以下两个属性只对SPDownloadResume下载方式奏效
/** 保存在沙盒中的进度值 */
@property (nonatomic, assign, readonly) CGFloat storedDownloadProgress;
/** 是否已经下载完毕 */
@property (nonatomic, assign, readonly, getter=isDownloadCompleted) BOOL downloadCompleted;
@end
NS_ASSUME_NONNULL_END
@interface NSString (MD5)
@property (nullable, nonatomic, readonly) NSString *md5String;
@end
.m文件
#import "SPHTTPSessionManager.h"
#import "AFNetworking.h"
// 文件名,MD5加密
#define SPFileName self.fileURLString.md5String
// 文件的存放路径(caches)
#define SPFileFullPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:SPFileName]
// 存储文件信息的路径(caches)
#define SPFileInfoPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"sp_fileInfo.info"]
// 文件的已下载长度
#define SPDownloadLength [[[NSFileManager defaultManager] attributesOfItemAtPath:SPFileFullPath error:nil][NSFileSize] integerValue]
@interface SPHTTPSessionManager() <NSURLSessionDelegate> {
CGFloat _storedDownloadProgress;
BOOL _downloadCompleted;
BOOL _downloading;
}
/** session */
@property (nonatomic, strong) NSURLSession *session;
/** 写文件的流对象 */
@property (nonatomic, strong) NSOutputStream *stream;
/** 文件的总长度 */
@property (nonatomic, assign) NSInteger totalLength;
/** 下载任务 */
@property (nonatomic, strong) NSURLSessionTask *task;
/** 文件的url */
@property (nonatomic, strong) NSString *fileURLString;
/** 下载过程中回调的block */
@property (nonatomic, copy) void (^downloadProgressBlock)(CGFloat progress);
/** 下载完成回调的block */
@property (nonatomic,copy) void (^completionHandler)(NSURLResponse *response, NSURL *URL, NSError *error);
/** 存储文件信息的字典,该字典要写入沙盒 */
@property (nonatomic, strong) NSMutableDictionary *fileInfoDictionry;
// ------------ 上面的额属性是针对下载2,下面的属性针对下载1 -------------
/** 下载1的文件url地址 */
@property (nonatomic, copy) NSString *downloadFromZero_UrlString;
/** 下载1完成后保存的文件路径 */
@property (nonatomic, copy) NSString *downloadFromZero_filePath;
/** 下载1过程中回调的block */
@property (nonatomic, copy) void (^downloadFromZero_ProgressBlock)(CGFloat progress);
/** 下载1完成回调的block */
@property (nonatomic,copy) void (^downloadFromZero_completionHandler)(NSURLResponse *response, NSURL *URL, NSError *error);
@end
@implementation SPHTTPSessionManager
+ (instancetype)shareInstance {
static SPHTTPSessionManager *manager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc] init];
});
return manager;
}
// get请求
- (void)GET:(NSString *)urlString params:(NSDictionary *)params
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain",@"application/json",@"text/json",@"text/javascript",@"text/html", nil];
[manager GET:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(responseObject);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(error);
};
}];
}
// post请求
- (void)POST:(NSString *)urlString params:(NSDictionary *)params
success:(void (^)(id responseObject))success failure:(void (^)(NSError *error))failure{
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"text/plain",@"application/json",@"text/json",@"text/javascript",@"text/html", nil];
[manager POST:urlString parameters:params progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(responseObject);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(error);
}
}];
}
// 下载1(重启app时从0开始开始)
- (NSURLSessionTask *)downloadFromZeroWithURL:(NSString *)urlString
progress:(void (^)(CGFloat))downloadProgressBlock
complete:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler {
self.downloadFromZero_UrlString = urlString;
self.downloadFromZero_ProgressBlock = downloadProgressBlock;
self.downloadFromZero_completionHandler = completionHandler;
return self.task;
}
// 下载2(重启app时从上一次的数据开始)
- (NSURLSessionTask *)downloadWithURL:(NSString *)urlString
progress:(void (^)(CGFloat))downloadProgressBlock
complete:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler {
if (self.downloadway == SPDownloadWayResume) {
self.fileURLString = urlString;
// 将block参数赋值给全局block变量
self.downloadProgressBlock = downloadProgressBlock;
self.completionHandler = completionHandler;
[self downloadFromZeroWithURL:urlString progress:downloadProgressBlock complete:completionHandler];
return self.task;
} else {
return [self downloadFromZeroWithURL:urlString progress:downloadProgressBlock complete:completionHandler];
}
}
// 上传
- (void)uploadWithURL:(NSString *)urlString
params:(NSDictionary *)params
fileData:(NSData *)filedata
name:(NSString *)name
fileName:(NSString *)filename
mimeType:(NSString *) mimeType
progress:(void (^)(NSProgress *uploadProgress))uploadProgressBlock
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
[manager POST:urlString parameters:params constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
[formData appendPartWithFileData:filedata name:name fileName:filename mimeType:mimeType];
} progress:^(NSProgress * _Nonnull uploadProgress) {
if (uploadProgressBlock) {
uploadProgressBlock(uploadProgress);
}
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(responseObject);
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
if (failure) {
failure(error);
}
}];
}
// 启动任务
- (void)resumeTask {
[self.task resume];
}
// 暂停任务
- (void)suspendTask {
[self.task suspend];
}
// 取消任务
- (void)cancelTask {
[self.task cancel];
}
// 删除
- (BOOL)removeDownloadedData:(NSError * _Nullable __autoreleasing * _Nullable)error {
if (self.downloadway == SPDownloadWayResume) {
if (SPDownloadLength) {
BOOL isDirectory = NO;
NSFileManager *manager = [NSFileManager defaultManager];
// 删除已经下载好的文件
if ([manager fileExistsAtPath:SPFileFullPath isDirectory:&isDirectory] && [manager fileExistsAtPath:SPFileInfoPath isDirectory:&isDirectory]) {
// 移除
BOOL removeFileSuccess = [manager removeItemAtPath:SPFileFullPath error:error];
BOOL removeFileLengthSuccess = [manager removeItemAtPath:SPFileInfoPath error:error];
if (removeFileSuccess && removeFileLengthSuccess) { // 移除成功
[self.task cancel];
self.task = nil;
return YES;
} else {
NSLog(@"移除文件失败");
return NO;
}
} else {
NSLog(@"没找到文件路径");
return NO;
}
} else {
NSLog(@"没有需要删除的数据");
return NO;
}
}
else {
if (!_downloading) { // 说明没有正在下载(下载1)
BOOL isDirectory = NO;
NSFileManager *manager = [NSFileManager defaultManager];
// 删除已经下载好的文件
if ([manager fileExistsAtPath:self.downloadFromZero_filePath isDirectory:&isDirectory]) {
// 移除
BOOL removeSuccess = [manager removeItemAtPath:self.downloadFromZero_filePath error:error];
if (removeSuccess) {
[self.task cancel];
self.task = nil;
return YES;
} else {
NSLog(@"移除失败");
return NO;
}
return YES;
} else {
NSLog(@"没找到文件路径");
return NO;
}
}
else { // 正在下载
NSLog(@"****** ‘SPDownloadWayRestart‘不支持在下载过程中删除");
return NO;
}
}
}
// 从沙盒中获取下载的进度值
- (CGFloat)storedDownloadProgress {
_storedDownloadProgress = [self.fileInfoDictionry[@"downloadProgress"] floatValue];
return _storedDownloadProgress;
}
// 从沙盒中获取下载是否完毕的标识
- (BOOL)isDownloadCompleted {
_downloadCompleted = self.fileInfoDictionry[@"downloadCompleted"];
return _downloadCompleted;
}
// 从沙盒中获取是否正在下载的标识
- (BOOL)isDownloading {
_downloading = self.fileInfoDictionry[@"downloading"];
return _downloading;
}
- (NSOutputStream *)stream {
if (!_stream) {
_stream = [NSOutputStream outputStreamToFileAtPath:SPFileFullPath append:YES];
}
return _stream;
}
- (NSURLSession *)session {
if (!_session) {
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
}
return _session;
}
- (NSURLSessionTask *)task {
if (!_task) {
if (self.downloadway == SPDownloadWayResume) {
// 取出文件的总长度
NSInteger totalLength = [self.fileInfoDictionry[SPFileName] integerValue];
if (totalLength && SPDownloadLength == totalLength) {
NSLog(@"文件已经下载完成了");
return nil;
}
// 创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.fileURLString]];
// 设置请求头
// Range : bytes=xxx-xxx
NSString *range = [NSString stringWithFormat:@"bytes=%zd-", SPDownloadLength];
[request setValue:range forHTTPHeaderField:@"Range"];
// 创建一个Data任务
_task = [self.session dataTaskWithRequest:request];
}
else {
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
NSURL *urlpath = [NSURL URLWithString:self.downloadFromZero_UrlString];
NSURLRequest *request = [NSURLRequest requestWithURL:urlpath];
_task = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
_downloading = YES;
CGFloat progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
if (self.downloadFromZero_ProgressBlock) {
self.downloadFromZero_ProgressBlock(progress);
}
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
NSString *cachesPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
NSURL *fileURL = [NSURL fileURLWithPath:cachesPath];
return fileURL;
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
_downloading = NO;
self.downloadFromZero_filePath = filePath.path;
if (self.downloadFromZero_completionHandler) {
self.downloadFromZero_completionHandler(response,filePath,error);
}
}];
}
}
return _task;
}
- (NSMutableDictionary *)fileInfoDictionry {
if (_fileInfoDictionry == nil) {
// 通过文件文件路径初始化字典,第一次取出来的必为空,因为此时还没有写进沙盒
_fileInfoDictionry = [NSMutableDictionary dictionaryWithContentsOfFile:SPFileInfoPath];
if (_fileInfoDictionry == nil) {
_fileInfoDictionry = [NSMutableDictionary dictionary];
}
}
return _fileInfoDictionry;
}
#pragma mark - <NSURLSessionDataDelegate>
/**
* 1.接收到响应
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// 打开流
[self.stream open];
// 获得服务器这次请求 返回数据的总长度
self.totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + SPDownloadLength;
// 存储总长度
self.fileInfoDictionry[SPFileName] = @(self.totalLength);
[self.fileInfoDictionry writeToFile:SPFileInfoPath atomically:YES];
// 接收这个请求,允许接收服务器的数据
completionHandler(NSURLSessionResponseAllow);
}
/**
* 2.接收到服务器返回的数据(这个方法可能会被调用N次)
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// 写入数据,不需要指定写入到哪个路径,因为stream在创建的那一刻就纪录好存储路径
[self.stream write:data.bytes maxLength:data.length];
// 回调block
if (self.downloadProgressBlock) {
// 获取进度值
CGFloat progress = 1.0 * SPDownloadLength / self.totalLength;
//NSLog(@"++++++%f",progress);
self.downloadProgressBlock(progress);
self.fileInfoDictionry[@"downloadProgress"] = @(progress); // 进度值
self.fileInfoDictionry[@"downloading"] = @(YES); // 正在下载的标识
[self.fileInfoDictionry writeToFile:SPFileInfoPath atomically:YES];
}
}
/**
* 3.请求完毕(成功\失败)
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (self.completionHandler) {
self.completionHandler(task.response,[NSURL fileURLWithPath:SPFileFullPath],error);
// 关闭流
[self.stream close];
self.stream = nil;
// 清除任务
self.task = nil;
if (!error) {
self.fileInfoDictionry[@"downloadCompleted"] = @(YES); // 下载完成的标识
self.fileInfoDictionry[@"downloading"] = @(NO); // 正在下载的标识
[self.fileInfoDictionry writeToFile:SPFileInfoPath atomically:YES];
} else {
if ([error.domain isEqualToString:@"NSURLErrorDomain"] && error.code == -999) {
return;
}
}
}
}
@end
#import <CommonCrypto/CommonDigest.h>
@implementation NSString (MD5)
- (NSString *)md5String {
const char *string = self.UTF8String;
int length = (int)strlen(string);
unsigned char bytes[CC_MD5_DIGEST_LENGTH];
CC_MD5(string, length, bytes);
return [self stringFromBytes:bytes length:CC_MD5_DIGEST_LENGTH];
}
- (NSString *)stringFromBytes:(unsigned char *)bytes length:(NSInteger)length {
NSMutableString *mutableString = @"".mutableCopy;
for (int i = 0; i < length; i++)
[mutableString appendFormat:@"%02x", bytes[i]];
return [NSString stringWithString:mutableString];
}
@end
之所以不用大家普遍使用的URLSessionDownloadTask,是因为它几乎无法做到杀死app后继续下载,尽管有个resumeData,也只能实现后台或者暂停后继续下载。