iOS网络开发学习笔记

来源:互联网 发布:linux 修改home目录 编辑:程序博客网 时间:2024/05/03 17:15

HTTP概述

超文本运输协议(Hypertext Transfer Protocol,HTTP),使用TCP作为传输层协议,是一个无状态的协议。基于请求-响应机制,由服务器端和客户端实现,定义了客户端如何向服务器端请求页面与服务器端如何将页面传送给客户端。在[RFC 1945]和[RFC 2616]中进行了定义。
1989年互联网之父 Tim Berners-Lee采用超文本技术开发出世界上第一个Web服务器和客户端浏览编辑程序,并将应用命名为World Wide Web(万维网)。1991年5月登上互联网大舞台并迅速流传。至此,互联网不再是一个需要一系列复杂步骤且看到的只是一些枯燥的内容,赋予了互联网全新的血液和强大的生命力。

The world of networking is complex. Users can connect to the Internet using a wide range of technologies—cable modems, DSL, Wi-Fi, cellular connections, satellite uplinks, Ethernet, and even traditional acoustic modems. Each of these connections has distinct characteristics, including differences in bandwidth, latency, packet loss, and reliability. –from apple

网络还是相对比较复杂的,用户可以通过多种方式连接网络,比如猫,电话线拨号上网(这种以电话线为传输介质的上网方式现在应该比较少了吧),Wi-Fi,使用卫星上网(覆盖范围大且速度比传统的猫快数十到上百倍),蜂窝上网等方式。每种连接方式都有不同的特性,包括带宽方面,延迟性方面,丢包情况,可靠性等。

As a developer of network-based software, your code must be able to adapt to changing network conditions, including performance, availability, and reliability.

Networks are inherently unreliable—cellular networks doubly so. As a result, good networking code tends to be somewhat complex. Among other things, your software should:
1. Transfer only as much data as required to accomplish a task.
2. Avoid timeouts whenever possible.
3. Design user interfaces that allow the user to easily cancel transactions that are taking too long to complete.
4. Handle failures gracefully.
5. Degrade gracefully when network performance is slow.
6. Choose APIs that are appropriate for the task.
7. Design your software carefully to minimize security risks.

开发网络应用时,代码必须能适应不断变化的网络条件,包括性能,可用性,可靠性。网络本质上是非常不可靠的。好的网络代码还是比较复杂的,需要:
1. 传输只完成工作所需的所有数据。发送的数据越少,电池的寿命就会越长,并且用户花费的流量也会越少。
2. 尽可能避免超时。实际在连接网络的时候,如果太久,这个时候最好能提供方式让用户可以取消该操作。又比如,实时聊天的时候,如果出现丢包,那就直接丢包而不要重传,尽量避免超时。
3. 如果是需要耗费比较久才能完成的事务,比如像下载比较的文件。最好提供相应的界面让用户可以取消事务,或者继续执行事务。
4. 优雅的处理连接失败情况。
5. 网络性能比较慢的时候可以开启省流量模式。
6. 选择合适的API吧,尽量使用抽象层度更高的API,如果抽象层度高的API可以实现应用要求,就使用抽象层度高的API。
7. 减少安全风险。从iOS9添加了ATS特性后,WWDC2016也继续收紧了对网络的控制,全应用HTTPS的时代即将到来。

NSURLConnection

NSURLConnection.h:
@protocol:
NSURLConnectionDelegate,NSURLConnectionDataDelegate,NSURLConnectionDownloadDelegate。

1. 通过静态方法获取数据[推荐]

这里写图片描述

NSURLRequest

NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0f];
NSURLRequestCachePolicy(缓存策略):

typedef NS_ENUM(NSUInteger, NSURLRequestCachePolicy){    NSURLRequestUseProtocolCachePolicy = 0,// 在协议实现中定义缓存逻辑    NSURLRequestReloadIgnoringLocalCacheData = 1,// 再次发送请求时不使用本地缓存而是直接从网络加载    NSURLRequestReloadIgnoringLocalAndRemoteCacheData = 4, // Unimplemented    NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData,    NSURLRequestReturnCacheDataElseLoad = 2,// 返回缓存,无缓存则从网络中加载    NSURLRequestReturnCacheDataDontLoad = 3,// 返回缓存,没有缓冲也不从网络加载    NSURLRequestReloadRevalidatingCacheData = 5, // Unimplemented};

Demo

- (void)sendRequest {    // 1. 获取url    NSString *urlStr = [NSString stringWithFormat:@"%@", kURL];    // 对url中的中文就行编码。其中url解码使用stringByRemovingPercentEncoding    urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];    NSURL *url = [NSURL URLWithString:urlStr];    //  2. 创建可变请求    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:5.0f];    [request setHTTPMethod:@"POST"];    // 3. 设置POST参数    NSString *bodyDataStr = [NSString stringWithFormat:@"username=%@&password=%@", _userName, _password];    NSData *bodyData = [bodyDataStr dataUsingEncoding:NSUTF8StringEncoding];    [request setHTTPBody:bodyData];    // 4. 发送异步请求    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {        if (!connectionError) {            [self loadData: data];        } else {            NSLog(@"error :%@", connectionError.localizedDescription);        }    }];}

2. 通过代理获取数据

NSURLConnectionDataDelegate

这里写图片描述
常用于响应数据比较大,如文件这类的。在didReceiveResponse获取文件长度,在didReceiveData中获取data对数据进行组装(appendData),其中可以对已接收到的数据量提供进度条显示。当接收完毕后会发送connectionDidFinishLoading消息,可在其中将接收到的data保存到本地中。

show code:

#pragma mark NSURLConnectionDataDelegate- (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 valueForKey:@"Content-Length"] longLongValue];}- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {    [_data appendData: data];    [self updateProgress];}- (void)connectionDidFinishLoading:(NSURLConnection *)connection {    NSLog(@"connectionDidFinishLoading");    NSString *savePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];    savePath = [savePath stringByAppendingPathComponent:textField.text];    [_data writeToFile:savePath atomically:YES];}

NSURLConnectionDelegate

#pragma mark NSURLConnectionDelegate- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {    NSLog(@"didFailWithError:%@", error.localizedDescription);}

ps:实际开发中不推荐使用该方法。在接收比较大的文件时,由于-didReceiveData看似每次接收一部分数据,实际上并不是多次接受数据,数据比较大时则需调用多次代理方法。这里存在一个问题,如果是大文件下载,中途暂停再次请求就会重新下载,不适合打文件断电续传,且在一次响应中接收到全部数据也是不合理的。正常会通过分段请求,也就是将一个大的文件数据包分成多个小文件数据包,每次请求一个小数据包,分多次将大文件下载下来。下文中会详细介绍分段下载

文件分段下载

大文件分段下载的过程中,每次的数据都会依次获取追加直到获取到所有的数据。分段下载,自然要知道文件的大小,这里可以使用HEAD请求,只获取头信息。
下载指定区域的数据

// 1. 创建请求声明下载区域NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld", start,end];NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@""]];[request setValue:range forHTTPHeaderField:@"Range"];// 2. 发送请求获取数据NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];// 3. 追加文件NSFileHandle *fileHandler = [NSFileHandle fileHandleForWritingAtPath:filePath];if (fileHandler) {    [fileHandler seekToEndOfFile];    [fileHandler writeData:data];    [fileHandler closeFile];} else {    [data writeToFile:filePath atomically:YES]; // 不存在创建文件}

NSURLSession

NSURLSession.h结构

相比较NSURLConnection,NSURLSession提供了配置会话缓存、协议、cookie和证书能力,这使得网络架构和应用程序可以独立工作、互不干扰。另外,NSURLSession另一个重要的部分是会话任务,它负责加载数据,在客户端和服务器端进行文件的上传下载。–from iOS开发系列–网络开发

1. 发送请求

- (void)sendRequest {    // 1. 创建url    NSString *urlStr = [NSString stringWithFormat:@"%@", kURL];    urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];    NSURL *url = [NSURL URLWithString:urlStr];    // 2. 创建请求    NSURLRequest *request = [NSURLRequest requestWithURL:url];    // 3. 创建会话    NSURLSession *session = [NSURLSession sharedSession];    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {        if (!error) {            [self loadData:data];        } else {            NSLog(@"error :%@", error.localizedDescription);        }    }];    [dataTask resume];}

2. 上传文件

文件上传时请求类型为multipart/form-data(通过enctype属性设置)。Content-type分为两部分:内容类型和边界boundary。

–boundary
content-Disposition: form-data; name=”表单控件名称”; filename=”上传文件名称”
Content-Type: 文件MIME类型
–boundary

- (NSString *)getMIMETypes:(NSString *)fileName {    return @"image/jpeg";}- (NSData *)getHttpBody:(NSString *)fileName {    NSString *boundary=@"JolieYang";    NSMutableData *dataM = [NSMutableData data];    NSString *strTop = [NSString stringWithFormat:@"--%@\nContent-Disposition: form-data; name=\"field1\"; filename=\"%@\"\nContent-Type:%@\n\n", boundary, fileName, [self getMIMETypes:fileName]];    NSString *strBottom = [NSString stringWithFormat:@"\n--%@--", boundary];    NSString *filePath = [[NSBundle mainBundle] pathForResource:fileName ofType:nil];    NSData *fileData = [NSData dataWithContentsOfFile:filePath];    [dataM appendData: [strTop dataUsingEncoding:NSUTF8StringEncoding]];    [dataM appendData: fileData];    [dataM appendData: [strBottom dataUsingEncoding:NSUTF8StringEncoding]];    return dataM;}- (void)uploadFile: (NSString *)fileName {    NSString *boundary = @"JolieYang";    // 1. 创建请求    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@""] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f];    request.HTTPMethod = @"POST";    // 2. 构建数据    NSData *bodyData = [self getHttpBody:fileName];    [request setValue:[NSString stringWithFormat:@"%lu", bodyData.length] forHTTPHeaderField:@"Content-Length"];    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@", boundary] forHTTPHeaderField:@"Content-Type"];    request.HTTPBody = bodyData;    // 创建会话    NSURLSession *session = [NSURLSession sharedSession];    NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:bodyData completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {        if (!error) {            NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];        } else {            NSLog(@"error:%@", error.localizedDescription);        }    }];    [uploadTask resume];}

NSURLSessionConfiguration

通过NSURLSession上传或下载数据时定义使用的行为和策略。
+ (NSURLSessionConfiguration *)defaultSessionConfiguration; // 硬盘缓存数据
+ (NSURLSessionConfiguration *)ephemeralSessionConfiguration;// 返回的配置中不会永久存储缓存,cookies或者认证信息
+ (NSURLSessionConfiguration )backgroundSessionConfigurationWithIdentifier:(NSString )identifier NS_AVAILABLE(10_10, 8_0);// 后台模式,在默认会话的基础上,应用程序挂起,退出或者崩溃时会开启一个线程进行网络数据处理。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。

@property BOOL allowsCellularAccess;// 是否可以使用蜂窝网络进行连接
@property NSTimeInterval timeoutIntervalForRequest;// 请求超时时间
@property NSTimeInterval timeoutIntervalForResource;// 响应超时时间
@property (nullable, copy) NSDictionary *HTTPAdditionalHeaders;// 设置请求头

- (void)downloadFileBySession {    NSString *urlStr = @"";    urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];    NSURL *url = [NSURL URLWithString:urlStr];    // 1. 创建请求    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    // 2. 创建会话配置    NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];    sessionConfig.timeoutIntervalForRequest = 5.0f;    sessionConfig.allowsCellularAccess = YES;    // 3. 创建会话    NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfig delegate:self delegateQueue:nil];    _downloadTask = [session downloadTaskWithRequest:request];    [_downloadTask resume];}#pragma mark NSURLSessionDownloadDelegate- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {    // 更新进度条}- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTaskdidFinishDownloadingToURL:(NSURL *)location {    NSError *error;    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];    NSString *savePath = [cachePath stringByAppendingPathComponent:@"a.text"];    NSURL *saveURL = [NSURL fileURLWithPath:savePath];    [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveURL error:&error];    if (error) {        NSLog(@"Error :%@", error.localizedDescription);    }}

background

- (void)downloadFileByBackgroundSession {    NSString *urlStr = @"";    urlStr = [urlStr stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];    NSURL *url = [NSURL URLWithString:urlStr];    // 1. 创建请求    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];    _downloadTask = [[self backgroundSession] downloadTaskWithRequest:request];    [_downloadTask resume];}- (NSURLSession *)backgroundSession {    static NSURLSession *session;    static dispatch_once_t token;    dispatch_once(&token, ^{        NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:@"com.jy.URLSession"];        sessionConfig.timeoutIntervalForRequest = 5.0f;        sessionConfig.discretionary = YES; //        sessionConfig.HTTPMaximumConnectionsPerHost = 5; // 限制每次最多连接        session = [NSURLSession sessionWithConfiguration:sessionConfig];    });    return session;}#pragma mark NSURLSessionDelegate- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {}

程序在后台下载完成后,会调用AppDelegate中的- (void)application:(UIApplication )application handleEventsForBackgroundURLSession:(NSString )identifier completionHandler:(void (^)())completionHandler回调。

NSURLSession的优点

iOS7中引入了NSURLSession这套更为优秀的网络解决方案替代NSURLConnection。iOS9中已弃用NSURLConnection。
基于NSURLConnection,阐述下NSURLSession都做了哪些优化,既然苹果弃用,那么NSURLSession一定是继承了NSURLConnection的功能与优点,且成熟到完全取代NSURLConnection,并表现的更为优秀。
1. 相比于NSURLConnection,首先提供了配置会话缓存,协议,Cookie和credential policy(证书策略),且跨应用共享这些数据。
2. 支持后台上传下载
3. 支持暂停和重启网络操作
4. 丰富的代理模式

While certain decisions in the architecture of session tasks are a step backward in terms of composability and extensibility, NSURLSession nonetheless serves as a great foundation for higher-level networking functionality. —Mattt Thompson《From NSURLConnection to NSURLSession》
尽管在这个体系结构中,某些决定对于可组合性和可扩展性而言是一种倒退,但是 NSURLSession 仍然是实现更高级别网络功能的一个强大的基础框架。

AFNetworking

如果你不需要使用 AFNetworking 这些第三方库所提供的加强功能,比如和 UIKit 的深度集成等,那么使用 NSURLSession 是一个最简单快捷的选择。from NSURLSession 网络库 - 原生系统送给我们的礼物

讲到iOS网络开发,AFNetworking必不可少,基本上看到的项目都是基于AFNetworking进行封装。
关于AFNetworking,我专门整理到了这篇文章中 AFNetworking学习笔记,目前还在学习中。

总结

  1. 一个NSURLSession可以创建多个NSURLSessionTask,且task之间的cookie和缓存可以共享。
  2. NSURLSessionConfiguration提供三种类方法,也可自行定制。
  3. NSURLSession,sharedSession获取全局共享的实例,或者根据sessionConfiguration实例化。并且sessionConfiguration实例方法中有两个,其中一个提供代理对象,可以检测下载进度等。也就是使用全局共享实例也就不会进入相应代理,以及task如果使用block也同样无法检测进度。
  4. task创建后是挂起状态,-resume后才会执行。
  5. NSURLConnection下载文件后会自动保存到内存中,然后再写入沙盒,文件过大的话会导致内存暴涨。NSURLSession则是默认下载到沙盒中,相应的在下载完成后会自动删除沙盒中的文件,需在下载完文件的回调中手动保存文件。
  6. 请求发送后,会先访问共享的缓存,存在则返回,不存在则根据请求参数发送请求

参考资料

URL Session Programming Guide
NSURLConnection Class Reference
NSURLSession Class Reference
Networking Overview
iOS开发系列–网络开发
iOS应用架构谈 网络层设计方案
从 NSURLConnection 到 NSURLSession
NSURLSession 网络库 - 原生系统送给我们的礼物
iOS 7系列译文:忘记NSURLConnection,拥抱NSURLSession吧!
NSURLSession与NSURLConnection的区别

0 0
原创粉丝点击