iOS小技能:文件上传和下载(断点下载、断点续传)

2,625 阅读5分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情

引言

小文件下载:直接用NSData的+ (id)dataWithContentsOfURL:(NSURL *)url;利用NSURLConnection发送一个HTTP请求去下载。

如果是下载图片,还可以利用SDWebImage框架

I HTTP Range(指定每次从网路下载数据包的大小)

通过设置请求头Range可以指定每次从网路下载数据包的大小,可以用于断点下载


 [request setValue:[NSString stringWithFormat:@"bytes=%lld-",self.currentDataLength] forHTTPHeaderField:@"Range"];

1.1 Range示例

bytes=0-499  从0到499的头500个字节
 bytes=500-999  从500到999的第二个500字节
bytes=500-  从500字节以后的所有字节
bytes=-500  最后500个字节
bytes=500-599,800-899  同时指定几个范围 

1.2 Range小结

  • 用于分隔 前面的数字表示起始字节数 后面的数组表示截止字节数,没有表示到末尾 用于分组,可以一次指定多个Range,不过很少用

  • 第三方解压缩框架——SSZipArchive


Adding to your project

Add SSZipArchive.h, SSZipArchive.m, and minizip to your project.
Add the libz library to your target 需要引入libz.dylib框架
  • Usage

// Unzipping
NSString *zipPath = @"path_to_your_zip_file";
NSString *destinationPath = @"path_to_the_folder_where_you_want_it_unzipped";
[SSZipArchive unzipFileAtPath:zipPath toDestination:destinationPath];
 
// Zipping
NSString *zippedPath = @"path_where_you_want_the_file_created";
NSArray *inputPaths = [NSArray arrayWithObjects:
                       [[NSBundle mainBundle] pathForResource:@"photo1" ofType:@"jpg"],
                       [[NSBundle mainBundle] pathForResource:@"photo2" ofType:@"jpg"]
                       nil];
[SSZipArchive createZipFileAtPath:zippedPath withFilesAtPaths:inputPaths];

1.3 NSURLConnection断点下载


  • 断点下载

#pragma mark - 断点下载
- (IBAction)pause:(UIButton *)sender {
    [sender setSelected:!sender.selected];
    if (sender.selected) {//继续下载
        NSURL *url = [NSURL URLWithString:@"http://127.0.0.1:8080/MJServer/resources/videos/iPhoneSimulator6.1.sdk.zip"];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
        //设置Range
        [request setValue:[NSString stringWithFormat:@"bytes=%lld-",self.currentDataLength] forHTTPHeaderField:@"Range"];
        NSLog(@"%@",[NSString stringWithFormat:@"bytes=%lld-",self.currentDataLength]);
        self.connection = [NSURLConnection connectionWithRequest:request delegate:self];
 
    }else{//暂停下载
        [self.connection cancel];
        self.connection = nil;
    }
}
#pragma mark - NSURLConnectionDataDelegate
 
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
    self.response = (NSHTTPURLResponse*)response;
    if (self.currentDataLength) {//断点之后的新请求,无需执行以下代码
        return;
    }
    //创建一个和预期下载文件一样大小的文件到沙盒--以便多线程断点下载,的线程分工;----单线程断点下载只需创建一个空文件
    NSString *filePath = HSCachePath([connection.currentRequest.URL lastPathComponent]);
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager createFileAtPath:filePath contents:nil attributes:nil];
    //创建文件handle
    self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:filePath];
    self.totalDataLength = response.expectedContentLength;
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
    self.currentDataLength += data.length;
//    NSDictionary *dict = [self.response allHeaderFields];
//    NSString *length = dict[@"Content-Length"];//获取预期长度
//    NSString *length = self.response.expectedContentLength;
        [self.circularProgressView setProgress:self.currentDataLength*1.0/self.totalDataLength];
    [self.fileHandle seekToEndOfFile];
    [self.fileHandle writeData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
    [self.fileHandle closeFile];
    self.currentDataLength =0;
 
}

II 文件上传( multipart/form-data)


  • 设置Post的请求头(key value)
/*Content-Type   multipart/form-data; boundary=本次上传标示字符串(不能中文)*/
[request setValue:@"multipart/form-data;boundary=hisun" forHTTPHeaderField:@"Content-Type"];

The MIME type of the specified data. (For example, the MIME type for a JPEG image is image/jpeg.) For a list of valid MIME types

2.1 文件上传的请求体


这里写图片描述

  • 文件参数 --本次上传标示字符串(边界比请求头的boundary 增加--开头) Content-Disposition: form-data; name="参数名"; filename="文件名"
    Content-Type: MIMEType (文件类型) 文件具体数据

  • 非文件参数 --本次上传标示字符串(--边界) Content-Disposition: form-data; name="参数名称";
    参数值 --边界–(结尾标记 )

结尾标记(--边界--)

  • zip 压缩文件夹
NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
   NSString *filePath = [cachePath stringByAppendingPathComponent:@"images"];
   NSString *zipPath = [cachePath stringByAppendingPathComponent:@"images.zip"];
   NSLog(@"%@,%@",filePath,zipPath);
   [SSZipArchive createZipFileAtPath:zipPath withContentsOfDirectory:filePath];
  • 根据URL 获取mimeType
- (NSString *) mimeTypeWithUrl:(NSURL *)url{
    NSURLResponse *response;
    [NSURLConnection sendSynchronousRequest:[NSURLRequest requestWithURL:url] returningResponse:&response error:nil];
    return response.MIMEType;
}
 
//    NSString *mimetype = [self mimeTypeWithUrl:[NSURL fileURLWithPath:@"/Users/devzkn/Desktop/minion_02.png"]];

2.2 使用NSMutableURLRequest文件上传


#define HSBasicUrl  @"http://127.0.0.1:8080/KNServer/"
 
#define HSUrl(name) [NSURL URLWithString:[NSString stringWithFormat:@"%@%@",HSBasicUrl,name]]
 
#define HSFileBoundary @"hisun"
 
#define HSFileEndedBounary [NSString stringWithFormat:@"--%@--",HSFileBoundary]
 
#define HSFileStartBoundary [NSString stringWithFormat:@"--%@",HSFileBoundary]
 
#define HSNewLine @"\r\n"
 
#define HSStringEncoding(str) [str dataUsingEncoding:NSUTF8StringEncoding]
#pragma mark - 文件上传
 
- (void)upload:(NSString*)fileName mimeType:(NSString *)mimeType fileData:(NSData*)fileData params:(NSDictionary*)params{
 
    //设置请求体
 
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:HSUrl(@"upload")];
 
    //准备请求体数据
 
    NSMutableData *data =[NSMutableData data];
 
    /*
 
     请求体:
 
     1)文件参数
 
     --本次上传标示字符串(边界)
 
     Content-Disposition: form-data;  name="参数名"; filename="文件名"
 
     Content-Type: MIMEType  (文件类型)
 
     文件具体数据
 
      
 
     2)非文件参数
 
     --本次上传标示字符串(边界)
 
     Content-Disposition: form-data;  name="参数名称"
 
     参数值
 
     3)边界–(结尾标记 )
 
     */
 
    [data appendData:HSStringEncoding(HSFileStartBoundary)];//分界线
 
    [data appendData:HSStringEncoding(HSNewLine)];
 
    NSString *disposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"; filename=\"%@\"",@"file",fileName];
 
    [data appendData:HSStringEncoding(disposition)];//参数名
 
    [data appendData:HSStringEncoding(HSNewLine)];
 
    NSString *mimeTypeStr = [NSString stringWithFormat:@"Content-Type:%@",mimeType];
 
    [data appendData:HSStringEncoding(mimeTypeStr)];
 
    [data appendData:HSStringEncoding(HSNewLine)];
 
     
 
    [data appendData:HSStringEncoding(HSNewLine)];
 
    [data appendData:fileData];
 
    [data appendData:HSStringEncoding(HSNewLine)];
 
    //设置非文件数据
 
    [params enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {
 
        [data appendData:HSStringEncoding(HSFileStartBoundary)];//分界线
 
        [data appendData:HSStringEncoding(HSNewLine)];
 
         
 
        NSString *disposition = [NSString stringWithFormat:@"Content-Disposition: form-data; name=\"%@\"",key];
 
        [data appendData:HSStringEncoding(disposition)];//参数名
 
        [data appendData:HSStringEncoding(HSNewLine)];
 
         
 
         
 
        [data appendData:HSStringEncoding(HSNewLine)];
 
        [data appendData:HSStringEncoding([obj description])];
 
        [data appendData:HSStringEncoding(HSNewLine)];
 
    }];
 
    //结尾
 
    [data appendData:HSStringEncoding(HSFileEndedBounary)];
 
    [data appendData:HSStringEncoding(HSNewLine)];
 
    [request setHTTPBody:data];
 
    [request setHTTPMethod:@"post"];
 
    [request setValue:[NSString stringWithFormat:@"multipart/form-data;boundary=%@",HSFileBoundary] forHTTPHeaderField:@"Content-Type"];
 
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
 
        NSLog(@"%@,%@",response,[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil]);
 
    }];
 
     
}

2.3 NSURLSession-断点续传



- (NSURLSession *)session{
    if (_session == nil) {
        _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue] ];
    }
    return _session;
}
#pragma mark - 断点下载
- (IBAction)pause:(UIButton *)sender {
    [sender setSelected:!sender.selected];
    if (sender.selected) {
        if (self.task == nil) {
            if (self.resumeData) {
                [self resumeDownLoad];//继续下载
            }else{
                [self startSessionWithConfiguration];//开始下载
            }
        }
    }else{
        [self pauseDownload];//暂停
    }
}
#pragma mark - NSURLSessionDownloadDelegate 查看下载进度
- (void) startSessionWithConfiguration{
    NSLog(@"%s----start ",__func__);
    [self.circularProgressView setProgress:0];
    [self.button setSelected:YES];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:HSURL(@"resources/videos/iPhoneSimulator6.1.sdk.zip")];
    self.task = [self.session downloadTaskWithRequest:request];
    [self.task resume];//执行任务
 
}
- (void)resumeDownLoad{
    if (self.resumeData == nil) {
        return;
    }
    NSLog(@"%s-----继续",__func__);
    self.task = [self.session downloadTaskWithResumeData:self.resumeData];
    [self.task resume];
    self.resumeData = nil;
 
}
- (void)pauseDownload {
    NSLog(@"%s------   暂停",__func__);
    if (self.task == nil) {
        return;
    }
    /*
     A completion handler that is called when the download has been successfully canceled.
     If the download is resumable, the completion handler is provided with a resumeData object. Your app can later pass this object to a session’s downloadTaskWithResumeData: or downloadTaskWithResumeData:completionHandler: method to create a new task that resumes the download where it left off.即包含了创建链接的URL和连接的头信息(Range)
     */
    HSweakself(weakself);
    [self.task  cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        weakself.resumeData = resumeData;
        weakself.task = nil;
    }];
}
 
/* Sent when a download task that has completed a download.  The delegate should
 
 * copy or move the file at the given location to a new location as it will be
 
 * removed when the delegate message returns. URLSession:task:didCompleteWithError: will
 
 * still be called.
 
 */
 
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location{
    NSString *filePath = HSCachePath(downloadTask.response.suggestedFilename);
    NSLog(@"%s----%@ --%@ --%@",__func__,downloadTask.response,filePath,location.path);
    //移动location文件到filePath
    NSFileManager *fileManager = [NSFileManager defaultManager];
    [fileManager moveItemAtPath:location.path toPath:filePath error:nil];
     
 
}
/* Sent periodically to notify the delegate of download progress. */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
      didWriteData:(int64_t)bytesWritten
 totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite{
    NSLog(@"%s----%@,bytesWritten:%lld,------totalBytesWritten:%lld,------totalBytesExpectedToWrite:%lld------%f%%",__func__,downloadTask,bytesWritten,totalBytesWritten,totalBytesExpectedToWrite,(double)totalBytesWritten*100.0/totalBytesExpectedToWrite);
    [self.circularProgressView setProgress:(double)totalBytesWritten*1.0/totalBytesExpectedToWrite];
     
 
}
 
/* Sent when a download has been resumed. If a download failed with an
 * error, the -userInfo dictionary of the error will contain an
 * NSURLSessionDownloadTaskResumeData key, whose value is the resume
 * data.
 */
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
 didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes{
    NSLog(@"%s-----fileOffset:%lld,---expectedTotalBytes:%lld",__func__,fileOffset,expectedTotalBytes);
}

see also

🍅 联系作者: iOS逆向(公号:iosrev)


🍅 作者简介:CSDN 博客专家认证🏆丨全站 Top 50、华为云云享专家认证🏆、iOS逆向公号号主


🍅 简历模板、技术互助。关注我,都给你。