你好,歡迎來到IOS教程網

 Ios教程網 >> IOS編程開發 >> IOS開發綜合 >> ios:NSURLSessionDataTask做文件斷點下載

ios:NSURLSessionDataTask做文件斷點下載

編輯:IOS開發綜合

之前用afn2.x的AFHttpOperation結合sqlite數據庫管理做了文件的斷點下載功能,之後蘋果宣布要開始限制ipv4,不過AFN的東西時給予high-level的APIs的,因此不需要修改,但是國外的開發者建議使用AFN3.0版本。

閒來無事就想重新集成一下,遷移AFN3.0的時候因為沒有了HTTPOperation,所以在修改代碼的時候全部用NSURLSessionDowonloadTask代替,不過由於之前的數據庫邏輯已經定型,且多處使用,修改起來比較復雜,DownloadTask是先下載臨時文件,下載完成後再遷移到指定文件夾,並不能通過range來指定下載位置的起始,如果用戶直接殺死App,又需要記住resumeData來重新下載,這樣在多線程同時下載多個的時候集成出了問題,可能是我邏輯沒有屢通,總覺的這樣修改起來比較費力。

最後我完全摒棄了AFN,改而實用系統提供的URLSession和URLSessionDataTask及它的代理方法來實現,這樣不需要修改現存的數據庫邏輯,只需要修改下載暫停繼續這部分的控制。

感謝大神提供:https://github.com/HHuiHao/HSDownloadManager。

首先用一個單例類來管理下載,單例類並不存儲下載內容的數據,數據只在函數之間傳遞。

/**

 *  單例

 *

 *  @return 返回單例對象

 */

+ (instancetype)sharedInstance;



/**

 *  開啟任務下載資源

 *

 *  @param model         下載參數

 *  @param progressBlock 回調下載進度

 *  @param stateBlock    下載狀態

 */

- (void)download:(DownloadModel *)model progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progressBlock state:(void(^)(DownloadState state))stateBlock;

model包含下載文件的參數,對應創建的sqlite數據庫的表,可以在內重組文件名,從而保證拿到已下載大小的range,重新創建task繼續下載,我使用的方法也是居於上面的git連接修改的,因為要適用自己的項目,其他大概都差不多,大家可以先看看git項目,相信可以滿足大家的大部分需求。

 

在作者的session類中,我也添加了doanloadModel的引用,從而方便在代理方法中拿到文件路徑相關的參數做比較及存值。

@property (nonatomic, copy) NSString *fileName;


@property (nonatomic,strong)DownloadModel *model;

 

文筆拙劣,其實寫個博客也是想為了給自己留個筆記,方便以後有用的話不需要在翻來翻去。直接貼代碼吧,還惦記著NBA總決賽呢偷笑,大家可以參考我上面給的git項目地址,作者寫的很好。

 

TTDownloadManager

 

//
//  TTDownloadManager.h
//  DownloadDemo
//
//  Created by qihb on 16/6/3.
//  Copyright © 2016年 Qihb. All rights reserved.
//

#import 
#import "TTSessionModel.h"
#import "DownloadModel.h"

@interface TTDownloadManager : NSObject

/**
 *  單例
 *
 *  @return 返回單例對象
 */
+ (instancetype)sharedInstance;

/**
 *  開啟任務下載資源
 *
 *  @param model         下載參數
 *  @param progressBlock 回調下載進度
 *  @param stateBlock    下載狀態
 */
- (void)download:(DownloadModel *)model progress:(void(^)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress))progressBlock state:(void(^)(DownloadState state))stateBlock;

/**
 *  查詢該資源的下載進度值
 *
 *  @param url 下載地址
 *
 *  @return 返回下載進度值
 */
- (CGFloat)progress:(NSString *)fileName;

/**
 *  獲取該資源總大小
 *
 *  @param url 下載地址
 *
 *  @return 資源總大小
 */
- (NSInteger)fileTotalLength:(NSString *)url;

/**
 *  判斷該資源是否下載完成
 *
 *  @param url 下載地址
 *
 *  @return YES: 完成
 */
- (BOOL)isCompletion:(NSString *)url;

/**
 *  刪除該資源
 *
 *  @param url 下載地址
 */
- (void)deleteFile:(NSString *)url;

/**
 *  清空所有下載資源
 */
- (void)deleteAllFile;




/**
 *  暫停所有下載
 */
-(void)pauseAllTask;


/**
 *  取消下載
 *
 */
-(void)cancelTaskWithModel:(DownloadModel *)model;



@end

.m

 

 

//
//  TTDownloadManager.m
//  DownloadDemo
//
//  Created by qihb on 16/6/3.
//  Copyright © 2016年 Qihb. All rights reserved.
//

#import "TTDownloadManager.h"
#import "NSString+Hash.h"
// 緩存主目錄
#define TTCachesDirectory [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"TTCache"]

// 保存文件名
#define TTFileKey(url) url.md5String


// 文件的存放路徑(caches)
#define TTFileFullpath(url) [TTCachesDirectory stringByAppendingPathComponent:TTFileName(url)]

// 文件的已下載長度
#define TTDownloadLength(name) [[[NSFileManager defaultManager] attributesOfItemAtPath:SAVE_MODEL_PATH(name) error:nil][NSFileSize] integerValue]

// 存儲文件總長度的文件路徑(caches)
#define TTTotalLengthFullpath [TTCachesDirectory stringByAppendingPathComponent:@"totalLength.plist"]





@interface TTDownloadManager ()

/** 保存所有任務(注:用md5後作為key) */
@property (nonatomic, strong) NSMutableDictionary *tasks;
/** 保存所有下載相關信息 */
@property (nonatomic, strong) NSMutableDictionary *sessionModels;

@end




@implementation TTDownloadManager

- (NSMutableDictionary *)tasks
{
    if (!_tasks) {
        _tasks = [NSMutableDictionary dictionary];
    }
    return _tasks;
}

- (NSMutableDictionary *)sessionModels
{
    if (!_sessionModels) {
        _sessionModels = [NSMutableDictionary dictionary];
    }
    return _sessionModels;
    
}


static TTDownloadManager *_downloadManager;

+ (instancetype)allocWithZone:(struct _NSZone *)zone
{
    static dispatch_once_t onceToken;
    
    dispatch_once(&onceToken, ^{
        _downloadManager = [super allocWithZone:zone];
    });
    
    return _downloadManager;
}

- (nonnull id)copyWithZone:(nullable NSZone *)zone
{
    return _downloadManager;
}

+ (instancetype)sharedInstance
{
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _downloadManager = [[self alloc] init];
    });
    
    return _downloadManager;
}

/**
 *  創建緩存目錄文件
 */
- (void)createCacheDirectory
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if (![fileManager fileExistsAtPath:TTCachesDirectory]) {
        [fileManager createDirectoryAtPath:TTCachesDirectory withIntermediateDirectories:YES attributes:nil error:NULL];
    }
}

/**
 *  開啟任務下載資源
 */
- (void)download:(DownloadModel *)model progress:(void (^)(NSInteger, NSInteger, CGFloat))progressBlock state:(void (^)(DownloadState))stateBlock
{
    NSString *model_url = model.model_url;
    NSString *fileName = [NSString stringWithFormat:@"%@.%@",model.model_md5_str,model.model_format];
    
    if (!model_url) return;
    if ([self isCompletion:fileName]) {
        stateBlock(DownloadStateCompleted);
        NSLog(@"----該資源已下載完成");
        return;
    }
    
    // 暫停
    if ([self.tasks valueForKey:fileName]) {
        [self handle:fileName];
        
        return;
    }
    
    // 創建緩存目錄文件
    [self createCacheDirectory];
    
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[[NSOperationQueue alloc] init]];
    
    NSString *toPath = SAVE_MODEL_PATH(fileName);
    
    // 創建流
    NSOutputStream *stream = [NSOutputStream outputStreamToFileAtPath:toPath append:YES];
    
    // 創建請求
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:model_url]];
    
    // 設置請求頭
    NSString *range = [NSString stringWithFormat:@"bytes=%zd-", TTDownloadLength(fileName)];
    [request setValue:range forHTTPHeaderField:@"Range"];
    
    // 創建一個Data任務
    NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
    NSTimeInterval time = [[NSDate date] timeIntervalSince1970];
    NSUInteger taskIdentifier = (unsigned long)time;
    [task setValue:@(taskIdentifier) forKeyPath:@"taskIdentifier"];
    
    // 保存任務
    [self.tasks setValue:task forKey:fileName];
    
    TTSessionModel *sessionModel = [[TTSessionModel alloc] init];
    sessionModel.url = model_url;
    sessionModel.fileName = fileName;
    sessionModel.model = model;
    sessionModel.progressBlock = progressBlock;
    sessionModel.stateBlock = stateBlock;
    sessionModel.stream = stream;
    [self.sessionModels setValue:sessionModel forKey:@(task.taskIdentifier).stringValue];
    
    [self start:fileName];
}


- (void)handle:(NSString *)key
{
    NSURLSessionDataTask *task = [self getTask:key];
    if (task.state == NSURLSessionTaskStateRunning) {
        [self pause:key];
    } else {
        [self start:key];
    }
}

/**
 *  開始下載
 */
- (void)start:(NSString *)key
{
    NSURLSessionDataTask *task = [self getTask:key];
    [task resume];
    
    [self getSessionModel:task.taskIdentifier].stateBlock(DownloadStateStart);
}

/**
 *  暫停下載
 */
- (void)pause:(NSString *)key
{
    NSURLSessionDataTask *task = [self getTask:key];
    [task suspend];
    
    [self getSessionModel:task.taskIdentifier].stateBlock(DownloadStateSuspended);
}

/**
 *  根據url獲得對應的下載任務
 */
- (NSURLSessionDataTask *)getTask:(NSString *)key
{
    return (NSURLSessionDataTask *)[self.tasks valueForKey:key];
}

/**
 *  根據url獲取對應的下載信息模型
 */
- (TTSessionModel *)getSessionModel:(NSUInteger)taskIdentifier
{
    return (TTSessionModel *)[self.sessionModels valueForKey:@(taskIdentifier).stringValue];
}

/**
 *  判斷該文件是否下載完成
 */
- (BOOL)isCompletion:(NSString *)fileName
{
    if ([self fileTotalLength:fileName] && TTDownloadLength(fileName) == [self fileTotalLength:fileName]) {
        return YES;
    }
    return NO;
}

/**
 *  查詢該資源的下載進度值
 */
- (CGFloat)progress:(NSString *)fileName
{
    return [self fileTotalLength:fileName] == 0 ? 0.0 : 1.0 * TTDownloadLength(fileName) /  [self fileTotalLength:fileName];
}

/**
 *  獲取該資源總大小
 */
- (NSInteger)fileTotalLength:(NSString *)fileName
{
    return [[NSDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath][fileName] integerValue];
}

#pragma mark - 刪除
/**
 *  刪除該資源
 */
- (void)deleteFile:(NSString *)fileName
{
    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:SAVE_MODEL_PATH(fileName)]) {
        
        // 刪除沙盒中的資源
        [fileManager removeItemAtPath:SAVE_MODEL_PATH(fileName) error:nil];
        // 刪除任務
        [self.tasks removeObjectForKey:fileName];
        [self.sessionModels removeObjectForKey:@([self getTask:fileName].taskIdentifier).stringValue];
        // 刪除資源總長度
        if ([fileManager fileExistsAtPath:TTTotalLengthFullpath]) {
            
            NSMutableDictionary *dict = [NSMutableDictionary dictionaryWithContentsOfFile:TTTotalLengthFullpath];
            [dict removeObjectForKey:fileName];
            [dict writeToFile:TTTotalLengthFullpath atomically:YES];
            
        }
    }
}



/**
 *  暫停所有下載
 */
-(void)pauseAllTask{
    NSArray *allKeys = [self.tasks allKeys];
    if (allKeys&&allKeys.count>0) {
        for (int i=0; i

 

TTSessionModel

//
//  TTSessionModel.h
//  DownloadDemo
//
//  Created by qihb on 16/6/3.
//  Copyright © 2016年 Qihb. All rights reserved.
//

#import 
#import 
#import "DownloadModel.h"


typedef enum {
    /** 未下載 */
    DownloadStateUnBegin = 0,
    /** 下載中 */
    DownloadStateStart,
    /** 下載暫停 */
    DownloadStateSuspended,
    /** 下載完成 */
    DownloadStateCompleted,
}DownloadState;

//0未下載  1正在下載  2暫停下載 3已完成下載


@interface TTSessionModel : NSObject

/** 流 */
@property (nonatomic, strong) NSOutputStream *stream;

/** 下載地址 */
@property (nonatomic, copy) NSString *url;


@property (nonatomic, copy) NSString *fileName;


@property (nonatomic,strong)DownloadModel *model;


/** 獲得服務器這次請求 返回數據的總長度 */
@property (nonatomic, assign) NSInteger totalLength;

/** 下載進度 */
@property (nonatomic, copy) void(^progressBlock)(NSInteger receivedSize, NSInteger expectedSize, CGFloat progress);

/** 下載狀態 */
@property (nonatomic, copy) void(^stateBlock)(DownloadState state);

@end

DownloadModel


 

//
//  DownloadModelList.h
//  Up Studio
//
//  Created by qihb on 16/5/12.
//  Copyright © 2016年 gjh. All rights reserved.
//

#import "DBBaseModel.h"

#define dbModelKeyWord @"model_md5_str"

@interface DownloadModel : DBBaseModel

@property(nonatomic,copy)NSString *model_md5_str;           //md5串
@property(nonatomic,copy)NSString *model_image_url;         //圖片遠程下載地址
@property(nonatomic,copy)NSString *model_url;               //模型遠程下載地址
@property(nonatomic,copy)NSString *model_size;              //文件大小
@property(nonatomic,copy)NSString *model_name;              //名字
@property(nonatomic,copy)NSString *model_format;            //格式 up3/stl
@property(nonatomic,copy)NSString *model_type;              //分類
@property(nonatomic,copy)NSString *model_image_path;        //圖片本地路徑
@property(nonatomic,copy)NSString *model_path;              //模型本地路徑
@property(nonatomic,copy)NSString *model_subModel_num;      //子模型數量
@property(nonatomic,copy)NSString *model_payyed_flag;       //付款標識
@property(nonatomic,copy)NSString *model_price;             //價格
@property(nonatomic,copy)NSString *model_copyright;         //版權
@property(nonatomic,copy)NSString *model_from_source;       //來源 0預設 1下載 2本地保存
@property(nonatomic,copy)NSString *model_download_flag;     //0未下載  1正在下載  2暫停下載 3已完成下載
@property(nonatomic,copy)NSString *model_download_percentage;//下載百分比
@property(nonatomic,copy)NSString *recent_time;             //最近一次使用時間

//獲取所有下載完成的模型數據
+(NSArray *)findAllDownloadFinished;


@end
  1. 上一頁:
  2. 下一頁:
蘋果刷機越獄教程| IOS教程問題解答| IOS技巧綜合| IOS7技巧| IOS8教程
Copyright © Ios教程網 All Rights Reserved