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

一、网络请求

在网络开发中,需要了解一些常用的请求方法:
  • GET请求:get是获取数据的意思,数据以明文在URL中传递,受限于URL长度,所以传输数据量比较小。
  • POST请求:post是向服务器提交数据的意思,提交的数据以实际内容形式存放到消息头中进行传递,无法在浏览器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];
// 告诉服务器数据为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:请求结果响应,连接成功后服务器会返回的响应
  • 该类不用我们创建,连接后服务器返回的,里面包含了一些响应信息
下面我们来发个完整的网络请求:
#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次也就下载完了。那么如何让服务器每次只返回1M的数据呢?
  • 在网络开发中可以在请求的头文件中设置一个range信息,它代表请求数据的大小。
  • 在WEB开发中我们还有另一种请求方法“HEAD”,通过这种请求服务器只会响应头信息,其他数据不会返回给客户端,这样一来整个数据的大小也就可以得到了。
首先我们需要先获取要下载的文件大小:
#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:@"--%@\n",kBOUNDARY_STRING];
    [bodyTop appendFormat:@"Content-Disposition: form-data; name=\"file1\"; filename=\"%@\"\n",fileName];
    [bodyTop appendFormat:@"Content-Type: %@\n\n",type];
    //构建请求体body的底部
    NSString *bodyBottom = [NSString stringWithFormat:@"\n--%@--",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哈!
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,293评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,604评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,958评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,729评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,719评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,630评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,000评论 3 397
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,665评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,909评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,646评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,726评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,400评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,986评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,959评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,996评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,481评论 2 342

推荐阅读更多精彩内容

  • iOS开发系列--网络开发 概览 大部分应用程序都或多或少会牵扯到网络开发,例如说新浪微博、微信等,这些应用本身可...
    lichengjin阅读 3,637评论 2 7
  • iOS网络编程读书笔记 Facade Tester客户端门面模式的实例(被动版本化) 被动版本化,所以硬编码URL...
    melouverrr阅读 1,598评论 3 7
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,594评论 18 139
  • 儒家曾经说过:“不谋全局,不足与谋一隅?”这就是传统文化的核心,全局观! 儒家如此,兵家也一样!“知彼知己者,百...
    汉正阅读 949评论 0 0
  • 经常会想,我是不是在一个梦里迷失。 如果是,我真的害怕会醒过来。 你的人生中,可曾出现过长相完全相同的两个人? 我...
    吴歌菡萏阅读 1,004评论 0 0