• iOS学习笔记12-网络(一)NSURLConnection


    一、网络请求

    在网络开发中。须要了解一些经常使用的请求方法:
    • GET请求:get是获取数据的意思,数据以明文在URL中传递,受限于URL长度,所以数据传输量比較小。
    • POST请求:post是向server提交数据的意思。提交的数据以实际内容形式存放到消息头中进行传递,无法在浏览器url中查看到,大小没有限制。

    • HEAD请求:请求头信息,并不返回请求数据体,而仅仅返回请求头信息,经常使用用于在文件下载中取得文件大小、类型等信息。

    Web请求

    二、NSURLConnection

    NSURLConnection是苹果提供的原生网络訪问类,可是苹果非常快会将其废弃。且由NSURLSession(iOS7以后)来替代。眼下使用最广泛的第三方网络框架AFNetworking最新版本号已弃用了NSURLConnection,那我们学习它还有什么用呢?
    * 首先,苹果弃用它还是须要时间的,最起码到iOS10之后。
    * 如今另一些老项目会使用NSURLConnection。特别是2013年之前的项目,用户量基础还是非常大的;
    * 另外,不得不承认,有些公司还在用相似ASI这些经典的网络框架。所以还是非常有必要学习NSURLConnection的。

    让我们来首先了解几个类:

    1. NSURL:请求地址,定义一个网络资源路径
    NSURL *url = [NSURL URLWithString:@"协议://主机地址/路径?參数&參数"];
    解释例如以下:
    • 协议:不同的协议,代表着不同的资源查找方式、资源传输方式,比方经常使用的httpftp
    • 主机地址:存放资源的主机的IP地址(域名)
    • 路径:资源在主机中的详细位置
    • 參数:參数可有可无。也能够多个。如果带參数的话,用“?”号后面接參数,多个參数的话之间用&隔开
    2.NSURLRequest:请求,依据前面的NSURL建立一个请求
    NSURLRequest *request = [NSURLRequest requestWithURL:url 
                                             cachePolicy:NSURLRequestUseProtocolCachePolicy 
                                         timeoutInterval:15.0];
    參数解释例如以下:
    • url:资源路径
    • cachePolicy:缓存策略(不管使用哪种缓存策略,都会在本地缓存数据),类型为枚举类型,取值例如以下:
    NSURLRequestUseProtocolCachePolicy = 0 //默认的缓存策略,使用协议的缓存策略
    NSURLRequestReloadIgnoringLocalCacheData = 1 //每次都从网络载入
    NSURLRequestReturnCacheDataElseLoad = 2 //返回缓存否则载入。非常少使用
    NSURLRequestReturnCacheDataDontLoad = 3 //仅仅返回缓存。没有也不载入。非常少使用
    • timeoutInterval:超时时长。默认60s
    另外,还能够设置其他一些信息,比方请求头,请求体等等。例如以下:
    NSMutableURLRequest *request = 
              [NSMutableURLRequest requestWithURL:url 
                                      cachePolicy:NSURLRequestUseProtocolCachePolicy 
                                  timeoutInterval:15.0];
    // 告诉server数据为json类型
    [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; 
    // 设置请求体body(json类型的数据)
    NSData *jsonData = [NSJSONSerialization dataWithJSONObject:@{@"userid":@"123456"} 
                                                       options:NSJSONWritingPrettyPrinted 
                                                         error:nil];
    request.HTTPBody = jsonData; 

    注意,上面的request是NSMutableURLRequest,就可以变类型

    3.NSURLResponse:请求结果响应,连接成功后server会返回的响应
    • 该类不用我们创建。连接后server返回的。里面包括了一些响应信息
    以下我们来发个完整的网络请求:
    #pragma mark 发送数据请求
    - (void)sendRequest{
        NSString *urlStr = [NSString stringWithFormat:@"http://192.168.1.208/FileDownload.aspx?file=%@",_textField.text];
        //注意对于url中的中文是无法解析的。须要进行url编码(指定编码类型为utf-8)
        //另外注意url解码使用stringByRemovingPercentEncoding方法
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        //创建url链接,该链接是下载文件
        NSURL *url = [NSURL URLWithString:urlStr];
        //创建请求
        NSURLRequest *request = [NSURLRequest requestWithURL:url 
                                                 cachePolicy:NSURLRequestUseProtocolCachePolicy 
                                             timeoutInterval:15.0f];
        //创建连接,设置请求。以及设置代理
        NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
        //启动连接,用start启动的连接都是异步请求
        [connection start];
    }
    要得到请求的数据,须要实现NSURLConnection的代理方法:
    #pragma mark - 连接代理方法
    #pragma mark 開始响应
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
        _data = [[NSMutableData alloc] init];
        _progressView.progress = 0;
        //通过响应头中的Content-Length取得整个响应的总长度
        NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
        NSDictionary *httpResponseHeaderFields = [httpResponse allHeaderFields];
        _totalLength = [[httpResponseHeaderFields objectForKey:@"Content-Length"] longLongValue];
    }
    #pragma mark 接收响应数据(依据响应内容的大小此方法会被反复调用)
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
        //连续接收数据
        [_data appendData:data];
        //更新进度
        [self updateProgress];
    }
    #pragma mark 数据接收完毕
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection{
        //数据接收完保存文件(注意苹果官方要求:下载数据仅仅能保存在缓存文件夹)
        NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        savePath = [savePath stringByAppendingPathComponent:_textField.text];
        [_data writeToFile:savePath atomically:YES];
    }
    #pragma mark 请求失败
    -(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
        //如果连接超时或者连接地址错误可能就会报错
    }

    网络请求下载
    但这样每次都要去实现这些代理,感觉十分麻烦,不用怕。NSURLConnection有简化方法。这也是我们用的最多得。

    typedef void (^CompletionBlock)(NSURLResponse*, NSData*, NSError*);
    /* 发送一个异步请求 */
    + (void)sendAsynchronousRequest:(NSURLRequest *)request /*请求*/
                              queue:(NSOperationQueue *)queue /* 连接所在线程队列 */
                  completionHandler:(CompletionBlock)completion;/* 请求回调 */
    /* 发送一个同步请求 */
    + (void)sendSynchronousRequest:(NSURLRequest *)request /*请求*/
                              queue:(NSOperationQueue *)queue /* 连接所在线程队列 */
                  completionHandler:(CompletionBlock)completion;/* 请求回调 */
    这样我们就不用实现代理方法了。以下是简化方法的使用实例:
    #pragma mark 发送数据请求
    - (void)sendRequest{
        NSString *urlStr = [NSString stringWithFormat:@"%@",kURL];
        urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        //创建url链接
        NSURL *url = [NSURL URLWithString:urlStr];
        /*创建可变请求*/
        NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:url cachePolicy:0 timeoutInterval:5.0f];
        [requestM setHTTPMethod:@"POST"];//设置位post请求
        //创建post參数
        NSString *bodyDataStr = [NSString stringWithFormat:@"userName=%@&password=%@",_userName,_password];
        NSData *bodyData = [bodyDataStr dataUsingEncoding:NSUTF8StringEncoding];
        [requestM setHTTPBody:bodyData];
        //发送一个异步请求
        [NSURLConnection sendAsynchronousRequest:requestM 
                                           queue:[NSOperationQueue mainQueue] 
                               completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
            if (!error) {
                //载入数据
                [self loadData:data];
                //刷新表格
                [_tableView reloadData];
            }
        }];
    }

    三、进阶-文件分段下载

    实际开发文件下载的时候。不管是通过代理方法还是静态方法运行请求和响应,我们都会分批请求数据,而不是一次性请求数据。

    如果一个文件有1G,那么仅仅要每次请求1M的数据,请求1024次也就下载完了。

    那么怎样让server每次仅仅返回1M的数据呢?

    • 在网络开发中能够在请求的头文件里设置一个range信息。它代表请求数据的大小。

    • 在WEB开发中我们还有另一种请求方法“HEAD”,通过这样的请求server仅仅会响应头信息。其他数据不会返回给client,这样一来整个数据的大小也就能够得到了。
    首先我们须要先获取要下载的文件大小:
    #pragma mark  取得文件大小
    - (long long)getFileTotlaLength:(NSString *)fileName{
        NSURL *url = [self getDownloadUrl:fileName];//获取URL,这种方法我就不列出来了
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url 
                                                cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                            timeoutInterval:5.0f];
        //设置为头信息请求
        [request setHTTPMethod:@"HEAD"];
        NSURLResponse *response;
        NSError *error;
        //注意这里使用了同步请求,直接将文件大小返回
        [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
        //取得内容长度
        return response.expectedContentLength;
    }
    我们还须要封装一个下载指定数据大小的请求:
    #pragma mark 下载指定块大小的数据
    - (void)downloadFile:(NSString *)fileName startByte:(long long)start endByte:(long long)end{
        NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld",start,end];
        NSURL *url = [self getDownloadUrl:fileName];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url 
                                          cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                      timeoutInterval:5.0f];
        //通过请求头设置数据请求范围
        [request setValue:range forHTTPHeaderField:@"Range"];
        NSURLResponse *response = nil;
        NSError *error = nil;
        //注意这里使用同步请求,避免文件块追加顺序错误
        NSData *data = [NSURLConnection sendSynchronousRequest:request 
                                             returningResponse:&response 
                                                         error:&error];
        if(!error){
            [self fileAppend:[self getSavePath:fileName] data:data];
        }
    }
    最后我们来下载整个文件:
    #pragma mark 异步下载文件
    - (void)downloadFileAsync{
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            [self downloadFile];
        });
    }
    #pragma mark 文件下载,拼接每一个下载小文件
    - (void)downloadFile{
        // 获取要下载的文件总大小
        _totalLength = [self getFileTotlaLength:_textField.text];
        _loadedLength = 0;
        long long startSize = 0;
        long long endSize = 0;
        //分段下载
        while(startSize < _totalLength){
            //kFILE_BLOCK_SIZE 宏定义的是分段下载的字节大小
            endSize = startSize + kFILE_BLOCK_SIZE - 1;
            if (endSize > _totalLength) {
                endSize = _totalLength - 1;
            }
            [self downloadFile:_textField.text startByte:startSize endByte:endSize];
            //更新进度
            _loadedLength += (endSize - startSize) + 1;
            [self updateProgress];
            startSize += kFILE_BLOCK_SIZE;
        }
    }

    分段下载

    四、进阶-文件上传

    要实现文件上传,须要採用POST请求,请求数据类型必须为multipart/form-data

    我们常见得请求数据类型有:
    • application/x-www-form-urlencoded:默认值,发送前对全部发送数据进行url编码。支持浏览器訪问,通常文本内容提交经常使用这样的方式。
    • multipart/form-data:多部分表单数据,支持浏览器訪问,不进行不论什么编码,通经常使用于文件传输(此时传递的是二进制数据) 。
    • text/plain:普通文本数据类型。支持浏览器訪问。发送前当中的空格替换为“+”。可是不正确特殊字符编码。
    • application/json:json数据类型 。
    • text/xml:xml数据类型。

    我们须要自己定制上传请求,以下为定制过程:

    1. 上传请求头必须满足例如以下格式:

    上传请求头

    2. 请求体内容要求如以下格式:
    --boundary
    Content-Disposition:form-data;name=”表单控件名称”;filename=”上传文件名”
    Content-Type:文件MIME Types
    
    文件二进制数据;
    
    --boundary--

    上传请求体

    至于上面上传文件的MIME Types类型,我列出几个经常使用的:
    • *.gif文件 - image/gif
    • *.html文件 - text/html
    • *.jpg文件 - image/jpeg
    • *.mov文件 - video/quicktime
    • *.mp3文件 - audio/mpeg
    • *.pdf文件 - application/pdf
    • *.txt文件 - text/plain
    • *.xml文件 - text/xml
    • *.avi文件 - video/x-msvideo
    以下为上传文件详细实例:
    #pragma mark 上传文件
    -(void)uploadFile{
        NSString *fileName = _textField.text;
        NSURL *url = [self getUploadUrl:fileName];
        NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
                                          cachePolicy:NSURLRequestReloadIgnoringCacheData 
                                      timeoutInterval:5.0f];
        request.HTTPMethod = @"POST";
        NSData *data = [self getHttpBody:fileName];
        //通过请求头设置
        NSString *lengthStr = [NSString stringWithFormat:@"%lu",(unsigned long)data.length];
        [request setValue:lengthStr forHTTPHeaderField:@"Content-Length"];
        NSString *typeStr = [NSString stringWithFormat:@"multipart/form-data; boundary=%@",kBOUNDARY_STRING];
        [request setValue:typeStr forHTTPHeaderField:@"Content-Type"];
        //设置数据体
        request.HTTPBody = data;
        //发送异步请求
        [NSURLConnection sendAsynchronousRequest:request 
                                 queue:[[NSOperationQueue alloc]init] 
                     completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
            if(error){
                NSLog(@"error:%@",connectionError.localizedDescription);
            }
        }];
    }
    #pragma mark 取得数据体
    -(NSData *)getHttpBody:(NSString *)fileName{
        NSMutableData *dataM = [NSMutableData data];
        NSString *type = [self getMIMETypes:fileName];
        //构建请求体body的顶部
        NSMutableString *bodyTop = [NSMutableString string];
        //宏kBOUNDARY_STRING就是boundary标示
        [bodyTop appendFormat:@"--%@
    ",kBOUNDARY_STRING];
        [bodyTop appendFormat:@"Content-Disposition: form-data; name="file1"; filename="%@"
    ",fileName];
        [bodyTop appendFormat:@"Content-Type: %@
    
    ",type];
        //构建请求体body的底部
        NSString *bodyBottom = [NSString stringWithFormat:@"
    --%@--",kBOUNDARY_STRING];
        NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];
        //构建请求体body中间的二进制上传数据
        NSData *fileData = [NSData dataWithContentsOfFile:filePath];
        //把顶部、数据、底部组合起来,形成body
        [dataM appendData:[bodyTop dataUsingEncoding:NSUTF8StringEncoding]];
        [dataM appendData:fileData];
        [dataM appendData:[bodyBottom dataUsingEncoding:NSUTF8StringEncoding]];
        return dataM;
    }
    有什么问题随时能够在下方评论提出!O(∩_∩)O哈。
  • 相关阅读:
    因为数据库无法大写循环所有要使用shell
    mysql动态扩容调研
    MySQL扩容
    数据库死锁及解决死锁问题
    SQL数据库常见故障及解决方法
    通过Ajax方式上传文件(input file),使用FormData进行Ajax请求
    Ajax方式上传文件
    高并发解决方案--负载均衡
    对TCP/IP协议的深入浅出总结
    常用的php开发工具有哪些?
  • 原文地址:https://www.cnblogs.com/yxysuanfa/p/7208355.html
Copyright © 2020-2023  润新知