• iOS开发——网络篇——文件下载(NSMutableData、NSFileHandle、NSOutputStream)和上传、压缩和解压(三方框架ZipArchive),请求头和请求体格式,断点续传Range


    一、小文件下载

        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_02.png"];
        
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        
        [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            
            UIImage *image = [UIImage imageWithData:data];
            
        }];

    二、大文件下载

    1、利用NSMutableData,缺点是会导致内存暴涨(以下用fileData,也就是注释部分)
    2、利用NSFileHandle,该类就是专门用于操作文件(handle)

    @interface ViewController ()<NSURLConnectionDataDelegate>
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView;
    
    @property (nonatomic, assign)NSUInteger currentLength; /**< 当前已经接收的长度 */
    @property (nonatomic, assign)NSUInteger totalLength; /**< 需要接收的总长度 */
    
    @property (nonatomic, strong)NSMutableData *fileData; /**< 用于存储下载的文件 */
    
    @property (nonatomic, strong) NSFileHandle *handle; /**< 文件句柄 */
    
    @property (nonatomic, copy) NSString *path; /**< 保存文件的路径 */
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
       
        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [NSURLConnection connectionWithRequest:request delegate:self];
    }
    
    #pragma mark - NSURLConnectionDataDelegate
    // 接收到服务器的响应
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        // 根据文件名称, 拼接保存文件的路径
        self.path = [response.suggestedFilename cacheDir];
        
        // 保存文件的总大小
        self.totalLength = response.expectedContentLength;
        
        // 创建一个空的文件, 用于存储下载的数据
        NSFileManager *manager = [NSFileManager defaultManager];
        if([manager createFileAtPath:self.path contents:nil attributes:nil]){
            NSLog(@"创建空文件成功");
        }
    }
    // 接收到服务器的数据调用(会调用一次或多次)
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        // 将每次下载的内容存储到fileData中
        // 会导致内存暴涨
    //    [self.fileData appendData:data];
        
        // 告诉handle从什么地方开始写入
        // 从没有数据的地方开始写入, 也就是在上一次的后面拼接
        [self.handle seekToEndOfFile];
        // 写入数据
        [self.handle writeData:data];
    
        // 计算进度
        self.currentLength += data.length;
        self.progressView.progress = 1.0 * self.currentLength / self.totalLength;
    }
    // 接收完毕时调用
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
    //    [self.fileData writeToFile:@"/Users/xiaomage/Desktop/abc.mp4" atomically:YES];
        
        // 关闭文件句柄
        [self.handle closeFile];
        self.handle = nil;
    }
    
    // 接收发生错误时调用
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
        
    }
    
    #pragma mark - lazy
    - (NSMutableData *)fileData
    {
        if (!_fileData) {
            _fileData = [NSMutableData data];
        }
        return _fileData;
    }
    
    - (NSFileHandle *)handle
    {
        if (!_handle) {
            // NSFileHandle类就是专门用于操作文件
            _handle = [NSFileHandle fileHandleForWritingAtPath:self.path];
        }
        return _handle;
    }
    
    @end

    3、使用NSOutputStream(输出流)

    @interface ViewController ()<NSURLConnectionDataDelegate>
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView;
    
    @property (nonatomic, assign)NSUInteger currentLength; /**< 当前已经接收的长度 */
    @property (nonatomic, assign)NSUInteger totalLength; /**< 需要接收的总长度 */
    
    @property (nonatomic, copy) NSString *path; /**< 保存文件的路径 */
    
    @property (nonatomic, strong) NSOutputStream *outputStream  ; /**< 输出流 */
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
       
        NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [NSURLConnection connectionWithRequest:request delegate:self];
    }
    
    #pragma mark - NSURLConnectionDataDelegate
    // 接收到服务器的响应
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    {
        // 根据文件名称, 拼接保存文件的路径
        self.path = [response.suggestedFilename cacheDir];
        NSLog(@"%@", self.path);
        
        // 保存文件的总大小
        self.totalLength = response.expectedContentLength;
        NSLog(@"%zd", response.expectedContentLength);
    
    }
    // 接收到服务器的数据调用(会调用一次或多次)
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        // 直接写入数据
        /*
         第一个参数: 需要写入的数据
         第二个参数: 写入数据的大小
         */
        [self.outputStream write:data.bytes maxLength:data.length];
        
        // 计算进度
        self.currentLength += data.length;
        self.progressView.progress = 1.0 * self.currentLength / self.totalLength;
    }
    // 接收完毕时调用
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    {
        // 关闭输出流
        [self.outputStream close];
        self.outputStream = nil;
    }
    
    // 接收发生错误时调用
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
    {
        
    }
    
    - (NSOutputStream *)outputStream
    {
        if (!_outputStream) {
            /*
             第一个参数: 告诉系统数据流需要输出到哪个文件中
             第二个参数: 如果传入YES, 代表每次都在上一次的后面追加
             如果传入NO, 代表每次都从头开始
             */
            _outputStream = [NSOutputStream outputStreamToFileAtPath:self.path append:YES];
            
            // 注意: 如果想利用输出流写入数据, 一定要打开数据流
            // 如果数据流打开的文件不存在, 那么会自动创建个新的
            [_outputStream open];
        }
        return _outputStream;
    }
    
    @end

    三、文件压缩/解压缩(使用三方框架ZipArchive)

    在ZipArchive框架中有一下几个方法可快速实现文件压缩和解压缩

    // 第一个参数: 压缩后的文件保存到什么地方(zip文件)
    // 第二个参数: 哪些文件需要压缩(传入一个存了文件名的数组)
    + (BOOL)createZipFileAtPath:(NSString *)path
               withFilesAtPaths:(NSArray *)paths;
               
    // 第一个参数: 压缩后的文件保存到什么地方(zip文件)
    // 第二个参数: 哪个文件夹的文件需要压缩(传入你想压缩的文件名)
    + (BOOL)createZipFileAtPath:(NSString *)path
        withContentsOfDirectory:(NSString *)directoryPath;
            
    // 第一个参数:需要解压的文件
    // 第二个参数:解压到什么地方(传入解压缩的目的文件名)
    + (BOOL)unzipFileAtPath:(NSString *)path
              toDestination:(NSString *)destination;

    四、MIMEType(获取文件类型)

    文件的万能类型 application/octet-stream
    1、利用NSURLConnection

    - (NSString *)MIMEType:(NSURL *)url
    {
        // 1.创建一个请求
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        // 2.发送请求(返回响应)
        NSURLResponse *response = nil;
        [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
        // 3.获得MIMEType
        return response.MIMEType;
    }

    2、C语言API

    + (NSString *)mimeTypeForFileAtPath:(NSString *)path
    {
        if (![[NSFileManager alloc] init] fileExistsAtPath:path]) {
            return nil;
        }
    
        CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
        CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
        CFRelease(UTI);
        if (!MIMEType) {
            return @"application/octet-stream";
        }
        return NSMakeCollectable(MIMEType);
    }

    五、文件的上传

    1、如何设置请求头
    [request setValue:@"multipart/form-data; boundary=分割线" forHTTPHeaderField:@"Content-Type"];

    2、如何设置请求体
    + 文件参数
    --分割线
    Content-Disposition: form-data; name="参数名"; filename="文件名"
    Content-Type: 文件的MIMEType

    文件数据

    + 非文件参数
    --分割线
    Content-Disposition: form-data; name="参数名"

    参数值

    + 结束标记
    参数结束的标记
    --分割线--

    - 注意事项
    + `请求体`比`请求头`分割线**前面**`多两个--`
    + `结束标记`比`请求体`**后面**`多两个--`

    3、上传步骤:
    1.创建URL

    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];

    2.根据URL创建NSURLRequest

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

      2.1设置请求头

    request.HTTPMethod = @"POST";
        [request setValue:@"multipart/form-data; boundary=分割线" forHTTPHeaderField:@"Content-Type"];

      2.2设置请求体

    NSMutableData *data = [NSMutableData data];

        2.2.1设置文件参数
        2.2.2设置非文件参数
        2.2.3设置结束符号

    request.HTTPBody = data;

    3.利用NSURLConnetion发送请求

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) {
            
        }];

    六、断点下载(续传)

    NSUrlConnection实现断点续传的关键是自定义http request的头部的range域属性。
     

    Range头域
      Range头域可以请求实体的一个或者多个子范围。例如,
      表示头500个字节:bytes=0-499
      表示第二个500字节:bytes=500-999
      表示最后500个字节:bytes=-500
      表示500字节以后的范围:bytes=500-
      第一个和最后一个字节:bytes=0-0,-1
      同时指定几个范围:bytes=500-600,601-999
      但是服务器可以忽略此请求头,如果无条件GET包含Range请求头,响应会以状态码206(PartialContent)返回而不是以200(OK)。

    NSURL *url1=[NSURL URLWithString:@"下载地址";  
    NSMutableURLRequest* request1=[NSMutableURLRequest requestWithURL:url1];  
    [request1 setValue:@"bytes=20000-" forHTTPHeaderField:@"Range"];   
    [request1 setCachePolicy:NSURLRequestReloadIgnoringLocalCacheData];  
    NSData *returnData1 = [NSURLConnection sendSynchronousRequest:request1 returningResponse:nil error:nil];   
    [self writeToFile:returnData1 fileName:@"SOMEPATH"];  
      
      
      
      
    -(void)writeToFile:(NSData *)data fileName:(NSString *) fileName  
    {  
        NSString *filePath=[NSString stringWithFormat:@"%@",fileName];  
        if([[NSFileManager defaultManager] fileExistsAtPath:filePath] == NO){  
            NSLog(@"file not exist,create it...");  
            [[NSFileManager defaultManager] createFileAtPath:filePath contents:nil attributes:nil];  
        }else {  
        NSLog(@"file exist!!!");  
        }  
      
        FILE *file = fopen([fileName UTF8String], [@"ab+" UTF8String]);  
      
        if(file != NULL){  
            fseek(file, 0, SEEK_END);  
        }  
        int readSize = [data length];  
        fwrite((const void *)[data bytes], readSize, 1, file);  
        fclose(file);  
    }  
    将来的你会感谢今天如此努力的你! 版权声明:本文为博主原创文章,未经博主允许不得转载。
  • 相关阅读:
    Harbor安装 -- 企业级Registry仓库
    https原理
    第十节
    第九节
    第八节
    Spring中用到的部分设计模式
    代理模式及实现
    单例模式的实现
    索引
    第九章 集合
  • 原文地址:https://www.cnblogs.com/chglog/p/4751963.html
Copyright © 2020-2023  润新知