天天看點

使用NSURLConnection實作大檔案斷點下載下傳

使用NSURLConnection實作大檔案斷點下載下傳

使用NSURLConnection實作大檔案斷點下載下傳

由于是實作大檔案的斷點下載下傳,不是下載下傳一般圖檔什麼的.在設計這個類的時候本身就不會考慮把下載下傳的檔案緩存到記憶體中,而是直接寫到檔案系統.

要實作斷點下載下傳,需要滿足1個條件,那就是,必須要伺服器支援斷點下載下傳.

實作的思路是這樣子的:

1.  第一次會擷取到被下載下傳檔案的總大小(伺服器提供這個值)

  下載下傳檔案總大小 = 期望從伺服器擷取檔案的大小 + 本地已經下載下傳的檔案的大小

2.  設定請求的緩存政策為不會讀取本地中已經緩存的資料(NSURLRequestReloadIgnoringLocalCacheData)

3.  在去伺服器請求資料之前先擷取到本地已經下載下傳好的部分檔案的長度,以這個參數設定進Range中到伺服器去請求剩下的資料

4.  當從網絡擷取到一定的資料的時候,我們直接将資料寫進檔案系統中

YXDownloadNetwork.h

//
//  YXDownloadNetwork.h
//  Download
//
//  http://home.cnblogs.com/u/YouXianMing/
//
//  Copyright (c) 2014年 Y.X. All rights reserved.
//

#import <Foundation/Foundation.h>

// block的相關定義
typedef void (^downloadProgress_t)(long long currentBytes, long long totalBytes);
typedef void (^completion_t)(NSDictionary *headers, NSData *body);

@interface YXDownloadNetwork : NSObject

// 将block定義成屬性
@property (nonatomic, copy) downloadProgress_t       downloadProgress;
@property (nonatomic, copy) completion_t             completion;

// 初始化方法
- (instancetype)initWithUrlString:(NSString *)urlString cacheCapacity:(unsigned long long)capacity;
- (void)start;

@end      

YXDownloadNetwork.m

//
//  YXDownloadNetwork.m
//  Download
//
//  http://home.cnblogs.com/u/YouXianMing/
//
//  Copyright (c) 2014年 Y.X. All rights reserved.
//

#import "YXDownloadNetwork.h"

@interface YXDownloadNetwork ()<NSURLConnectionDelegate, NSURLConnectionDataDelegate>

@property (nonatomic, assign) unsigned long long   totalLength;      // 檔案總大小
@property (nonatomic, assign) unsigned long long   startDataLength;  // 本地存在檔案的大小
@property (nonatomic, assign) unsigned long long   expectedLength;   // 從伺服器期望檔案的大小
@property (nonatomic, assign) unsigned long long   cacheCapacity;    // 緩存檔案容量,以k為機關

@property (nonatomic, strong) NSURLConnection     *dataConncetion;   // 網絡連接配接
@property (nonatomic, strong) NSDictionary        *responseHeaders;  // 網絡連接配接頭部資訊
@property (nonatomic, strong) NSFileHandle        *file;             // 檔案操作句柄
@property (nonatomic, strong) NSMutableData       *cacheData;        // 用于緩存的data資料

@end

@implementation YXDownloadNetwork

- (instancetype)initWithUrlString:(NSString *)urlString cacheCapacity:(unsigned long long)capacity
{
    self = [super init];
    
    if (self)
    {
        // 擷取緩存容量
        if (capacity <= 0)
        {
            _cacheCapacity = 100 * 1024;
        }
        else
        {
            _cacheCapacity = capacity * 1024;
        }
        
        // 擷取用于緩存的資料
        _cacheData = [NSMutableData new];
        
        // 擷取檔案名以及檔案路徑
        NSString *fileName = [urlString lastPathComponent];
        NSString *filePath = \
            fileFromPath([NSString stringWithFormat:@"/Documents/%@", fileName]);
        
        // 記錄檔案起始位置
        if ([[NSFileManager defaultManager] fileExistsAtPath:filePath])
        {
            // 從檔案中讀取出已經下載下傳好的檔案的長度
            _startDataLength = [[NSData dataWithContentsOfFile:filePath] length];
        }
        else
        {
            // 不存在則建立檔案
            _startDataLength = 0;
            [[NSFileManager defaultManager] createFileAtPath:filePath
                                                    contents:nil
                                                  attributes:nil];
        }
        
        // 打開寫檔案流
        _file = [NSFileHandle fileHandleForWritingAtPath:filePath];
        
        // 建立一個網絡請求
        NSMutableURLRequest* request = \
        [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
        
        // 禁止讀取本地緩存
        [request setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];
        
        // 設定斷點續傳(需要伺服器支援)
        [request setValue:[NSString stringWithFormat:@"bytes=%llu-", _startDataLength]
       forHTTPHeaderField:@"Range"];
        
        // 開始建立連接配接
        self.dataConncetion = \
        [[NSURLConnection alloc] initWithRequest:request
                                        delegate:self
                                startImmediately:NO];
    }
    
    return self;
}

- (void)start
{
    [self.dataConncetion start];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    if([response isKindOfClass:[NSHTTPURLResponse class]])
    {
        NSHTTPURLResponse *r = (NSHTTPURLResponse *)response;
        
        // 如果能擷取到期望的資料長度就執行括号中的方法
        if ([r expectedContentLength] != NSURLResponseUnknownLength)
        {
            // 擷取剩餘要下載下傳的
            _expectedLength  = [r expectedContentLength];
            
            // 計算出總共需要下載下傳的
            _totalLength = _expectedLength + _startDataLength;

            // 擷取頭檔案
            _responseHeaders = [r allHeaderFields];
        }
        else
        {
            NSLog(@"不支援斷點下載下傳");
        }
    }
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)theData
{
    // 追加緩存資料
    [_cacheData appendData:theData];
    
    // 如果該緩存資料的大小超過了指定的緩存大小
    if ([_cacheData length] >= _cacheCapacity)
    {
        // 移動到檔案結尾
        [_file seekToEndOfFile];
        
        // 在檔案末尾處追加資料
        [_file writeData:_cacheData];
        
        // 清空緩存資料
        [_cacheData setLength:0];
    }
    
    // 目前已經下載下傳的所有資料的總量
    _startDataLength += [theData length];
    
    // 如果指定了block
    if (_downloadProgress)
    {
        _downloadProgress(_startDataLength, _totalLength);
    }
}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // 移動到檔案結尾
    [_file seekToEndOfFile];
    
    // 在檔案末尾處追加最後的一點緩存資料
    [_file writeData:_cacheData];
    
    // 清空緩存
    [_cacheData setLength:0];
    
    NSLog(@"下載下傳完成哦");
}

NS_INLINE NSString * fileFromPath(NSString *filePath)
{
    return [NSHomeDirectory() stringByAppendingString:filePath];
}

@end      

測試代碼如下:

使用NSURLConnection實作大檔案斷點下載下傳

繼續閱讀