廢話不多所,直接上代碼
github網(wǎng)址:https://github.com/SPStore/SPHTTPSessionManager
.h文件
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
typedef NS_ENUM(NSInteger, SPDownloadWay) {
SPDownloadWayResume, // 這種下載方式支持重啟app時(shí)繼續(xù)上一次的下載
SPDownloadWayRestart // 這種下載方式不支持重啟app時(shí)繼續(xù)上一次的下載
// SPDownloadWayResume和SPDownloadWayRestart的下載方式具體異同點(diǎn)如下:
/*
1、SPDownloadWayResume和SPDownloadWayRestart均支持?jǐn)帱c(diǎn)下載
2、SPDownloadWayResume支持重啟app繼續(xù)上一次下載,SPDownloadWayRestart不支持
3、SPDownloadWayResume會(huì)自動(dòng)判斷是否下載完成,并保存每時(shí)每刻下載的進(jìn)度值,SPDownloadWayRestart沒(méi)有此功能
4、SPDownloadWayResume支持任何時(shí)刻刪除已經(jīng)下載的文件數(shù)據(jù),SPDownloadWayRestart不支持在下載過(guò)程中刪除,只有下載完成時(shí)才能刪除
5、SPDownloadWayResume不依賴于AFN,SPDownloadWayRestart依賴AFN
通俗的講,SPDownloadWayResume和SPDownloadWayRestart的根本區(qū)別就是前者是沙盒模式,后者是內(nèi)存模式
*/
};
NS_ASSUME_NONNULL_BEGIN
@interface SPHTTPSessionManager : NSObject
/** 單例對(duì)象 */
+ (instancetype)shareInstance;
/**
* get請(qǐng)求
*
* @param urlString 請(qǐng)求地址
* @param params 參數(shù)字典
* @param success 請(qǐng)求成功回調(diào)的block
* @param failure 請(qǐng)求失敗回調(diào)的block
*/
- (void)GET:(NSString *)urlString
params:(NSDictionary *)params
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure;
/**
* post請(qǐng)求
*
* @param urlString 請(qǐng)求地址
* @param params 參數(shù)字典
* @param success 請(qǐng)求成功回調(diào)的block
* @param failure 請(qǐng)求失敗回調(diào)的block
*/
- (void)POST:(NSString *)urlString
params:(NSDictionary *)params
success:(void (^)(id responseObject))success
failure:(void (^)(NSError *error))failure;
/**
* 下載
*
* @param urlString 請(qǐng)求地址
* @param downloadProgressBlock 下載過(guò)程中回調(diào)的block
* @complete 下載完成回調(diào)的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;
// 以下這些操作在外界也可以另外做到,比如啟動(dòng)和暫停任務(wù),外界在調(diào)用下載的方法時(shí)返回了一個(gè)task,開(kāi)發(fā)者可以用該task去啟動(dòng)和暫停任務(wù),之所以將其封裝,一是:這個(gè)類能做到的盡量不讓開(kāi)發(fā)者去做,二是:讓開(kāi)發(fā)者完全面向我這個(gè)單例對(duì)象。開(kāi)發(fā)者只需要做一些關(guān)于UI的事情
// 下載方式
@property (nonatomic, assign) SPDownloadWay downloadway;
/*
* 啟動(dòng)任務(wù)
*
*/
- (void)resumeTask;
/*
* 暫停任務(wù)
*
*/
- (void)suspendTask;
/*
* 取消任務(wù)
*
*/
- (void)cancelTask;
/*
* 移除已經(jīng)下載好的文件數(shù)據(jù)
*
*/
- (BOOL)removeDownloadedData:(NSError * _Nullable __autoreleasing * _Nullable)error;
/** 是否正在下載,對(duì)于SPDownloadResume下載方式,該屬性來(lái)源于沙盒,對(duì)于SPDownloadRestart下載方式,該屬性來(lái)源于內(nèi)存 */
@property (nonatomic, assign, readonly, getter=isDownloading) BOOL downloading;
// 以下兩個(gè)屬性只對(duì)SPDownloadResume下載方式奏效
/** 保存在沙盒中的進(jìn)度值 */
@property (nonatomic, assign, readonly) CGFloat storedDownloadProgress;
/** 是否已經(jīng)下載完畢 */
@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]
// 存儲(chǔ)文件信息的路徑(caches)
#define SPFileInfoPath [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"sp_fileInfo.info"]
// 文件的已下載長(zhǎng)度
#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;
/** 寫(xiě)文件的流對(duì)象 */
@property (nonatomic, strong) NSOutputStream *stream;
/** 文件的總長(zhǎng)度 */
@property (nonatomic, assign) NSInteger totalLength;
/** 下載任務(wù) */
@property (nonatomic, strong) NSURLSessionTask *task;
/** 文件的url */
@property (nonatomic, strong) NSString *fileURLString;
/** 下載過(guò)程中回調(diào)的block */
@property (nonatomic, copy) void (^downloadProgressBlock)(CGFloat progress);
/** 下載完成回調(diào)的block */
@property (nonatomic,copy) void (^completionHandler)(NSURLResponse *response, NSURL *URL, NSError *error);
/** 存儲(chǔ)文件信息的字典,該字典要寫(xiě)入沙盒 */
@property (nonatomic, strong) NSMutableDictionary *fileInfoDictionry;
// ------------ 上面的額屬性是針對(duì)下載2,下面的屬性針對(duì)下載1 -------------
/** 下載1的文件url地址 */
@property (nonatomic, copy) NSString *downloadFromZero_UrlString;
/** 下載1完成后保存的文件路徑 */
@property (nonatomic, copy) NSString *downloadFromZero_filePath;
/** 下載1過(guò)程中回調(diào)的block */
@property (nonatomic, copy) void (^downloadFromZero_ProgressBlock)(CGFloat progress);
/** 下載1完成回調(diào)的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請(qǐng)求
- (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請(qǐng)求
- (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時(shí)從0開(kāi)始開(kāi)始)
- (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時(shí)從上一次的數(shù)據(jù)開(kāi)始)
- (NSURLSessionTask *)downloadWithURL:(NSString *)urlString
progress:(void (^)(CGFloat))downloadProgressBlock
complete:(void (^)(NSURLResponse *, NSURL *, NSError *))completionHandler {
if (self.downloadway == SPDownloadWayResume) {
self.fileURLString = urlString;
// 將block參數(shù)賦值給全局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);
}
}];
}
// 啟動(dòng)任務(wù)
- (void)resumeTask {
[self.task resume];
}
// 暫停任務(wù)
- (void)suspendTask {
[self.task suspend];
}
// 取消任務(wù)
- (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];
// 刪除已經(jīng)下載好的文件
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(@"沒(méi)找到文件路徑");
return NO;
}
} else {
NSLog(@"沒(méi)有需要?jiǎng)h除的數(shù)據(jù)");
return NO;
}
}
else {
if (!_downloading) { // 說(shuō)明沒(méi)有正在下載(下載1)
BOOL isDirectory = NO;
NSFileManager *manager = [NSFileManager defaultManager];
// 刪除已經(jīng)下載好的文件
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(@"沒(méi)找到文件路徑");
return NO;
}
}
else { // 正在下載
NSLog(@"****** ‘SPDownloadWayRestart‘不支持在下載過(guò)程中刪除");
return NO;
}
}
}
// 從沙盒中獲取下載的進(jìn)度值
- (CGFloat)storedDownloadProgress {
_storedDownloadProgress = [self.fileInfoDictionry[@"downloadProgress"] floatValue];
return _storedDownloadProgress;
}
// 從沙盒中獲取下載是否完畢的標(biāo)識(shí)
- (BOOL)isDownloadCompleted {
_downloadCompleted = self.fileInfoDictionry[@"downloadCompleted"];
return _downloadCompleted;
}
// 從沙盒中獲取是否正在下載的標(biāo)識(shí)
- (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) {
// 取出文件的總長(zhǎng)度
NSInteger totalLength = [self.fileInfoDictionry[SPFileName] integerValue];
if (totalLength && SPDownloadLength == totalLength) {
NSLog(@"文件已經(jīng)下載完成了");
return nil;
}
// 創(chuàng)建請(qǐng)求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:self.fileURLString]];
// 設(shè)置請(qǐng)求頭
// Range : bytes=xxx-xxx
NSString *range = [NSString stringWithFormat:@"bytes=%zd-", SPDownloadLength];
[request setValue:range forHTTPHeaderField:@"Range"];
// 創(chuàng)建一個(gè)Data任務(wù)
_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) {
// 通過(guò)文件文件路徑初始化字典,第一次取出來(lái)的必為空,因?yàn)榇藭r(shí)還沒(méi)有寫(xiě)進(jìn)沙盒
_fileInfoDictionry = [NSMutableDictionary dictionaryWithContentsOfFile:SPFileInfoPath];
if (_fileInfoDictionry == nil) {
_fileInfoDictionry = [NSMutableDictionary dictionary];
}
}
return _fileInfoDictionry;
}
#pragma mark - <NSURLSessionDataDelegate>
/**
* 1.接收到響應(yīng)
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSHTTPURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// 打開(kāi)流
[self.stream open];
// 獲得服務(wù)器這次請(qǐng)求 返回?cái)?shù)據(jù)的總長(zhǎng)度
self.totalLength = [response.allHeaderFields[@"Content-Length"] integerValue] + SPDownloadLength;
// 存儲(chǔ)總長(zhǎng)度
self.fileInfoDictionry[SPFileName] = @(self.totalLength);
[self.fileInfoDictionry writeToFile:SPFileInfoPath atomically:YES];
// 接收這個(gè)請(qǐng)求,允許接收服務(wù)器的數(shù)據(jù)
completionHandler(NSURLSessionResponseAllow);
}
/**
* 2.接收到服務(wù)器返回的數(shù)據(jù)(這個(gè)方法可能會(huì)被調(diào)用N次)
*/
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
// 寫(xiě)入數(shù)據(jù),不需要指定寫(xiě)入到哪個(gè)路徑,因?yàn)閟tream在創(chuàng)建的那一刻就紀(jì)錄好存儲(chǔ)路徑
[self.stream write:data.bytes maxLength:data.length];
// 回調(diào)block
if (self.downloadProgressBlock) {
// 獲取進(jìn)度值
CGFloat progress = 1.0 * SPDownloadLength / self.totalLength;
//NSLog(@"++++++%f",progress);
self.downloadProgressBlock(progress);
self.fileInfoDictionry[@"downloadProgress"] = @(progress); // 進(jìn)度值
self.fileInfoDictionry[@"downloading"] = @(YES); // 正在下載的標(biāo)識(shí)
[self.fileInfoDictionry writeToFile:SPFileInfoPath atomically:YES];
}
}
/**
* 3.請(qǐng)求完畢(成功\失敗)
*/
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
if (self.completionHandler) {
self.completionHandler(task.response,[NSURL fileURLWithPath:SPFileFullPath],error);
// 關(guān)閉流
[self.stream close];
self.stream = nil;
// 清除任務(wù)
self.task = nil;
if (!error) {
self.fileInfoDictionry[@"downloadCompleted"] = @(YES); // 下載完成的標(biāo)識(shí)
self.fileInfoDictionry[@"downloading"] = @(NO); // 正在下載的標(biāo)識(shí)
[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,是因?yàn)樗鼛缀鯚o(wú)法做到殺死app后繼續(xù)下載,盡管有個(gè)resumeData,也只能實(shí)現(xiàn)后臺(tái)或者暫停后繼續(xù)下載。
github網(wǎng)址:https://github.com/SPStore/SPHTTPSessionManager