原创:知识点总结性文章
创作不易,请珍惜,之后会持续更新,不断完善
个人比较喜欢做笔记和写总结,毕竟好记性不如烂笔头哈哈,这些文章记录了我的IOS成长历程,希望能与大家一起进步
温馨提示:由于简书不支持目录跳转,大家可通过command + F 输入目录标题后迅速寻找到你所需要的内容
目录
- 一、了解NSURLSession
- 二、实现 GET 请求
- 三、实现POST请求
- 四、下载数据
- 五、AFNetworking 框架
一、了解NSURLSession
优点
-
NSURLSession
可以为每个用户会话 (session
) 配置缓存 - 支持安全证书策略
- 支持
FTP
、HTTP
和HTTPS
等网络通信协议 - 支持实现后台下载和上传,以及断点续传功能
- 还可以取消、恢复、挂起网络请求
设置HTTP请求白名单
访问http
网页需要在Info.plist
中添加NSAppTransportSecurity
,并在其下添加NSAllowsArbitraryLoads
,值设为YES
。
会话类型
-
简单会话:不可配置会话,只能执行基本的网络请求,通过
shared
静态单例获得该对象 - 默认会话 (default session) :可以配置
- 短暂会话 (ephemeral session):不存储任何数据在磁盘中,所有的数据都是缓存的,当应用结束会话时,它们被自动释放
-
后台会话 (background session):可以在后台进行上传下载操作,需要通过唯一的
identity
标示
任务类型
系统一共提供了5种任务类,继承关系如下图所示。其中NSURLSessionTask
为抽象类,不能实现网络访问。NSURLSessionStreanTask
以流的方式进行网络访问,使用的比较少,使用的多的是dataTask
、downloadTask
、uploadTask
,基本满足了网络访问的基本需求:获取数据(通常是JSON
、XML
等)、文件上传、文件下载,这三个类都是NSURLSessionTask
这个抽象类的子类。NSURLSessionTask
支持任务的暂停、取消和恢复,并且默认任务运行在子线程中。
代理类型
根据图中代理协议的名字不难发现,每一个任务类都有相对应的代理协议,只有NSURLSessionUploadTask
没有对应的代理协议,因为NSURLSessionUploadTask
继承自NSURLSessionDataTask
,因此NSURLSessionDataDelegate
即为NSURLSessionUploadTask
对应的代理协议。
二、实现 GET 请求
使用简单会话实现的网络请求不能进行任何配置,简单会话是在子队列中执行的,当遇到表视图刷新这种更新 UI界面的操作时,要切换回主队列执行。可以使用默认会话,然后进行配置为mainQueue
,由于会话任务是在主线程中执行,不需要再使用并发队列方法dispatch_async
回到主队列刷新UI。
1、构建网络请求任务
指定请求的 URL
- 请求的参数全部暴露在
URL
后面,这是GET
请求方法的典型特征 - 协议:不同的协议代表着不同的资源查找方式、资源传输方式
-
主机地址:存放资源的主机的
IP
地址(域名) - 路径:资源在主机中的具体位置
-
参数:参数可有可无,也可以多个。如果带参数的话,在
?
号后面接参数,多个参数的话之间用&
隔开
NSURL *url = [NSURL URLWithString:@"协议://主机地址/路径?参数&参数"];
// 指定请求的 URL
NSString *strURL = [[NSString alloc] initWithFormat:@"http://www.51work6.com/service/mynotes/WebServic.php?email=%@&type%@&action=%@",@"<你的51work6.com用户邮箱>","JSON",@"query"];
// URL字符串允许的字符集
strURL = [strURL stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
// 将字符串转换为 URL字符串
NSURL *url = [NSURL URLWithString:strURL];
构造网络请求对象 NSURLRequest
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url];
创建默认配置对象
NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
//设置缓存策略
defaultConfig.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
//设置网络服务类型,决定了网络请求的形式
defaultConfig.networkServiceType = NSURLNetworkServiceTypeDefault;
//设置请求超时时间
defaultConfig.timeoutIntervalForRequest = 15;
//设置请求头
defaultConfig.HTTPAdditionalHeaders
//设置网络属性,是否使用移动流量
defaultConfig.allowsCellularAccess = YES;
创建默认会话对象
本例网络请求任务之后回调的是代码块,而非委托对象的方法,delegate
参数被赋值为nil
。delegateQueue
参数是设置会话任务执行所在的操作队列。由于会话任务是在主线程中执行不需要再使用并发队列方法dispatch_async
回到主队列刷新UI。
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfig delegate:nil delegateQueue:[NSOperationQueue mainQueue]];
创建网络请求任务
- 第一个参数是
NSURLRequest
请求对象 - 第二个参数
completionHandler
是请求完成回调的代码块 -
data
参数是从服务器返回的数据 -
response
是从服务器返回的应答对象 -
error
是错误对象,如error
为nil
, 则说明请求过程没有错误发生
NSURLSessionDataTask *task = [defaultSession dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"请求完成...");
...
}];
开启网络请求任务
在会话任务对象上调用 resume
方法开始执行任务,新创建的任务默认情况下是暂停的。
[task resume];
2、请求完成使用返回数据更新UI
获取返回数据
将响应对象转化为NSHTTPURLResponse
对象:
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
获取网络连接的状态码,200成功 404网页未找到:
NSLog(@"状态码:%li", httpResponse.statusCode);
if (httpResponse.statusCode == 200)
{
NSLog(@"请求成功");
}
获取响应头:
NSLog(@"响应头:%@", httpResponse.allHeaderFields);
获取响应体。调用 reloadView:
方法是在 GCD主队列中执行的,简单会话是在非主队列中执行的,当遇到表视图刷新这种更新 UI界面的操作时,要切换回主队列执行。在请求完成时调用 reloadView:
方法,该方法用于重新加载表视图中的数据。
if (!error)
{
NSDictionary *resDict = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:nil];
dispatch_async(dispatch_get_main_queue(), ^{
[self reloadView:resDict];
});
}
else
{
NSLog(@"error: %@", error.localizedDescription);
}
重新加载表视图
从服务器返回的JSON
格式数据有两种情况,成功返回或者失败,ResultCode
数据项用于说明该结果。当ResultCode
大于等于0时,说明服务器端操作成功,取得从服务端返回的数据。为了减少网络传输,只传递消息代码,不传递消息内容,根据结果编码获得结果消息,显示错误消息。
- (void)reloadView:(NSDictionary *)res
{
NSNumber *resultCode = res[@"ResultCode"];
if ([resultCode integerValue] >= 0)
{
self.listData = res[@"Record"];
[self.tableView reloadData];
}
else
{
NSString *errorStr = [resultCode errorMessage];
UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"错误信息" message:errorStr preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
[alertController addAction:okAction];
[self presentViewController:alertController animated:true completion:nil];
}
}
3、实现网络请求代理方法
已经接收到响应头
- NSURLSessionResponseCancel:取消接受
- NSURLSessionResponseAllow:继续接受
- NSURLSessionResponseBecomeDownload:将当前任务转化为一个下载任务
- NSURLSessionResponseBecomeStream:将当前任务转化为流任务
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler
{
//通过状态码来判断石是否成功
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode == 200)
{
NSLog(@"请求成功");
NSLog(@"%@", httpResponse.allHeaderFields);
//初始化接受数据的NSData变量
_data = [[NSMutableData alloc] init];
//执行completionHandler Block回调来继续接收响应体数据
completionHandler(NSURLSessionResponseAllow);
}
else
{
NSLog(@"请求失败");
}
}
接受到数据包时调用的代理方法
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data
{
NSLog(@"收到了一个数据包");
//拼接完整数据
[_data appendData:data];
NSLog(@"接受到了%li字节的数据", data.length);
}
数据接收完毕时调用的代理方法
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
NSLog(@"数据接收完成");
if (error)
{
NSLog(@"数据接收出错!");
//清空出错的数据
_data = nil;
}
else
{
//数据传输成功无误,JSON解析数据
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:_data options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"%@", dic);
}
}
三、实现POST请求
❶ 在这个URL
字符串后面没有参数(即没有?号后面的内容)
NSString *strURL = @"http://www.51work6.com/service/mynotes/WebService.php";
NSURL *url = [NSURL URLWithString:strURL];
❷ 参数字符串:请求参数放到请求体中。postData
就是请求参数。将参数字符串转换成NSData
类型,编码一定要采用UTF-8
。
NSString *post = [NSString stringWithFormat:@"email=%@&type=%@&action=%@", @"<用户邮箱>", @"JSON", @"query"];
NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding];
❸ 创建可变的请求对象NSMutableURLRequest
,所以可以通过属性设置其内容。HTTPMethod
属性用于设置HTTP
请求方法为POST
。HTTPBody
属性用于设置请求数据。
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:postData];
❹ 创建请求会话
NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration: defaultConfig delegate: nil delegateQueue: [NSOperationQueue mainQueue]];
❺ 创建并开启请求任务,将获取到的数据进行JSON
解析。
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSDictionary *dic = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
NSLog(@"%@", dic);
}];
[dataTask resume];
四、下载数据
1、创建下载任务
声明委托和变量
#define kResumeDataPath [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/resumeData.plist"]
@interface NSURLSessionViewController ()<NSURLSessionDownloadDelegate>
@property(nonatomic, strong) UIProgressView *progressView;
@end
@implementation NSURLSessionViewController
{
NSURLSession *_session;
NSURLSessionDownloadTask *_downLoadTask;
}
创建会话,delegate为self
NSURLSessionConfiguration *defaultConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:defaultConfig delegate:self delegateQueue:[NSOperationQueue mainQueue]];
开始下载
- (void)startDownLoad
{
//设置文件下载地址
NSString *strURL = [[NSString alloc] initWithFormat:@"http://218.76.27.57:8080/海贼王.jpg"];
NSURL *url = [NSURL URLWithString:strURL];
//创建下载任务
_downLoadTask = [_session downloadTaskWithURL:url];
//开始执行任务
[_downLoadTask resume];
}
暂停下载
判断当前的下载状态,正在下载就取消当前任务,并将任务的信息保存的文件中,即将数据写入到文件,方便下次读取。数据信息包括下载链接,已下载的数据大小,已下载的临时文件文件名。
- (void)pauseDownLoad
{
if (_downLoadTask && _downLoadTask.state == NSURLSessionTaskStateRunning)
{
[_downLoadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
[resumeData writeToFile:kResumeDataPath atomically:YES];
NSLog(@"将数据写入到文件:%@", kResumeDataPath);
}];
_downLoadTask = nil;
}
}
继续下载
获取已经保存的数据,再重建下载任务并继续执行任务,最后移除文件。
- (void)resumeDownLoad
{
NSData *data = [NSData dataWithContentsOfFile:kResumeDataPath];
if (data)
{
_downLoadTask = [_session downloadTaskWithResumeData:data];
[_downLoadTask resume];
[[NSFileManager defaultManager] removeItemAtPath:kResumeDataPath error:nil];
}
}
2、实现委托协议
文件下载完成
- session:网络会话
- downloadTask:下载任务
- location:下载完成的文件在本地磁盘中的位置,是个保存数据的本地临时文件
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
NSLog(@"下载完成");
NSLog(@"临时文件: %@\n", location);
}
❶ 拼接文件的目标路径
NSString *downloadsDir = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, TRUE) objectAtIndex:0];
NSString *downloadStrPath = [downloadsDir stringByAppendingPathComponent:@"歌曲.mp3"];
NSURL *downloadURLPath = [NSURL fileURLWithPath:downloadStrPath];
❷ 将临时文件,移动到沙盒路径中。首先需要判断在沙箱 Documents
目录下是否存在 海贼王.jpg 文件。如果存在就删除海贼王.jpg文件,这可以防止最新下载的文件不能覆盖之前遗留海贼王.jpg文件。
NSFileManager *fileManager = [NSFileManager defaultManager];
NSError *error = nil;
if ([fileManager fileExistsAtPath:downloadStrPath])
{
[fileManager removeItemAtPath:downloadStrPath error:&error];
if (error)
{
NSLog(@"删除文件失败: %@", error.localizedDescription);
}
}
❸ 将下载时保存数据的本地临时文件移动到沙箱 Documents
目录下,并命名为 海贼王.jpg文件。沙箱Documents
目录下不能有海贼王.jpg名字的文件,否则无法完成移动操作。文件下载并移动成功后,构建UIImage
对象,然后再把UIImage
对象赋值给图片视图。在界面中我 们就可以看到下载的图片了。
if ([fileManager moveItemAtURL:location toURL:downloadURLPath error:&error])
{
NSLog(@"文件保存地址: %@", downloadStrPath);
UIImage *image = [UIImage imageWithContentsOfFile:downloadStrPath];
self.imageView.image = image;
}
else
{
NSLog(@"保存文件失败: %@", error.localizedDescription);
}
接受到一部分数据后调用的方法
- session:网络会话对象
- downloadTask:下载任务对象
- bytesWritten:当前数据包写入的数据字节数
- totalBytesWritten:当前总共写入的数据字节数
- totalBytesExpectedToWrite:完整的文章总大小字节数
计算当前下载任务进度。更新进度条的进度,属于更新UI操作,需要在主队列(主线程所在队列)中执行。由于配置会话时设置的是主队列(主线程所在队列),所以更新UI的操作不必放在dispatch_async
中执行。
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(@"获取到一个数据包:%lli", bytesWritten);
NSLog(@"已经写入的数据包大小:%lli", totalBytesWritten);
NSLog(@"总文件大小:%lli", totalBytesExpectedToWrite);
CGFloat progress = (CGFloat)totalBytesWritten / totalBytesExpectedToWrite;
_progressView.progress = progress;
_progressLabel.text = [NSString stringWithFormat:@"%.2f%%", progress * 100];
}
五、AFNetworking 框架
由于上传数据时需要模拟HTML
表单上传数据,数据采用multipart/form-data
格式, 即将数据分割成小段进行上传,具体实现非常复杂 。 NSURLSessionUploadTask
任务没有屏蔽这些复杂性,不推荐使用 NSURLSessionUploadTask
任务,而推荐采用AFNetworking
网络请求框架。
NSURLSession
仍然不是很理想,易用性不够。AFNetworking
框架是Alamofire
基金会支持的项目,因此能够获得比较稳定的技术支持。AFNetworking
框架支持断点续传、图片缓存到内存、后台下载、获取下载进度和上传进度等。
实现GET请求
NSString *urlString = @"http://piao.163.com/m/cinema/list.html?app_id=1&mobileType=iPhone&ver=2.6&channel=appstore&deviceId=9E89CB6D-A62F-438C-8010-19278D46A8A6&apiVer=6&city=110000";
❶ 创建manager
AFHTTPSessionManager *manger = [AFHTTPSessionManager manager];
❷ 设置发送和接收的数据类型
-
AFHTTPRequestSerializer:使用
key1=value1&key2=value2
的形式来上传数据 -
AFJSONRequestSerializer:使用
json
格式上传数据
[AFJSONRequestSerializer serializerWithWritingOptions:NSJSONWritingPrettyPrinted];
manger.requestSerializer = [AFHTTPRequestSerializer serializer];
❸ 接受数据格式
- AFHTTPResponseSerializer:不做解析操作
-
AFJSONResponseSerializer:自动进行
json
解析 -
AFXMLParserResponseSerializer:接受
XML
数据
manger.responseSerializer = [AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingMutableLeaves];
❹ 进行网络请求
responseObject
是从服务器返回的JSON
对象,可以是字典或数组类型。
[manger GET:urlString parameters:nil headers:nil progress:^(NSProgress * _Nonnull downloadProgress) {
NSLog(@"%lli/%lli", downloadProgress.completedUnitCount, downloadProgress.totalUnitCount);
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功");
NSLog(@"responseObject%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败");
NSLog(@"error : %@", error.localizedDescription);
}];
实现 POST请求
❶ 创建manager
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
❷ 请求参数
NSString *urlString = @"http://piao.163.com/m/cinema/schedule.html?app_id=1&mobileType=iPhone&ver=2.6&channel=appstore&deviceId=9E89CB6D-A62F-438C-8010-19278D46A8A6&apiVer=6&city=110000";
NSDictionary *parameters = @{@"cinema_id" : @"1533"};
❸ 发起POST请求
[manager POST:urlString parameters:parameters headers:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
NSLog(@"请求成功:%@", responseObject);
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"请求失败");
}];
下载数据
下载地址
NSString *urlString = @"http://218.76.27.57:8080/chinaschool_rs02/135275/153903/160861/160867/1370744550357.mp3";
创建manager
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
创建请求对象
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:urlString]];
创建下载任务。创建一个沙盒路径下的子路径,设定保存的文件夹位置。
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress * _Nonnull downloadProgress) {
NSLog(@"%lli/%lli", downloadProgress.completedUnitCount, downloadProgress.totalUnitCount);
} destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) {
NSLog(@"状态码:%li", ((NSHTTPURLResponse *)response).statusCode);
//指定的保存路径
NSString *filePath = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents/歌曲.mp3"];
NSLog(@"%@", filePath);
return [NSURL fileURLWithPath:filePath];
} completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nullable filePath, NSError * _Nullable error) {
NSLog(@"下载完成");
}];
[downloadTask resume];
上传数据
❶ 构造参数字典
设置请求网址
NSString *urlString = @"https://api.weibo.com/2/statuses/upload.json";
NSString *token = @"2.00hd363CtKpsnBedca9b3f35tBYiP";
获取输入的文字和图片
UIImage *image = _imageView.image;
NSString *text = _textField.text;
if (image == nil || text.length == 0)
{
return;
}
构造参数字典
NSDictionary *dic = @{@"access_token" : token, @"status" : text};
❷ 创建HTTP请求序列化对象
封装了HTTP
请求参数(放在 URL
问号之后的部分)和表单数据
AFHTTPRequestSerializer *serializer = [AFHTTPRequestSerializer serializer];
❸ 创建请求对象
- 该方法可以发送
multipart/form-data
格式的表单数据 -
method
参数的请求方法一般是POST
-
URLString
参数是上传时的服务器地址 -
parameters
是请求参数,它是字典结构 -
constructingBodyWithBlock
是请求体代码块 -
error
参数是错误对象
NSMutableURLRequest *request = [serializer multipartFormRequestWithMethod:@"POST" URLString:urlString parameters:dic constructingBodyWithBlock:^(id<AFMultipartFormData> _Nonnull formData) {
...
} error:nil];
❹ 图片数据的拼接
- 添加
multipart/form-data
格式的表单数据,这种格式的数据被分割成小段进行上传 - 该方法的第一个参数是要上传的文件数据
- 第二个参数
name
是与数据相关的名称,一般是file
- 第三个参数
fileName
是文件名,是放在请求头中的文件名,不能为空 - 第四个参数
mimeType
是数据相关的MIME
类型
NSData *imageData = UIImageJPEGRepresentation(image, 1);
[formData appendPartWithFileData:imageData name:@"pic" fileName:@"image.png" mimeType:@"image/png"];
❺ 创建manager
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
❻ 用于创建NSURLSessionUploadTask上传会话任务
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithStreamedRequest:request progress:^(NSProgress * _Nonnull uploadProgress) {
//回到主线程中 刷新界面
[_progressView performSelectorOnMainThread:@selector(setProgress:) withObject:@(uploadProgress.fractionCompleted) waitUntilDone:YES];
dispatch_async(dispatch_get_main_queue(), ^{
//上传进度
[_progressView setProgress:uploadProgress.fractionCompleted];
});
} completionHandler:^(NSURLResponse * _Nonnull response, id _Nullable responseObject, NSError * _Nullable error) {
NSLog(@"上传结束,状态码:%li", ((NSHTTPURLResponse *)response).statusCode);
}];
[uploadTask resume];
Demo
Demo在我的Github上,欢迎下载。
BasicsDemo