The URL Loading System
定义:描述 Foundation 框架类在标准的网络传输协议下,用 URLs 连接因特网并与服务器交互的一整套体系。
支持的传输协议:
- File Transfer Protocol (
ftp://
) - Hypertext Transfer Protocol (
http://
) - Hypertext Transfer Protocol with encryption (
https://
) - Local file URLs (
file:///
) - Data URLs (
data://
)
结构图
网络系统模块
5个模块:代理支持、身份验证和凭据、cookie 存储、配置管理和缓存管理。
Cookie,有时也用其复数形式 Cookies,指某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密)。
NSURLSessionTask
NSURLSessionDelegate 委托协议
Session 会话的概念
Session中任务的行为取决于三个方面:
- Session 的类型(取决于创建的配置对象类型);
- task 任务的类型;
- task 任务被创建时,app 是否处于前台状态?
Session 的类型
- 默认会话(Default session):与其他用于下载URL的 Foundation 方法类似。 使用永久性的基于磁盘的缓存并将凭据存储在用户的钥匙串中。
- 短暂会话(Ephemeral session):不会将任何数据存储到磁盘; 所有缓存,凭证等都保存在 RAM 中并与会话相关联。 因此,当应用程序使会话无效时,会自动清除该会话。
- 后台会话(Background session):类似于默认会话,但是会使用单独的进程处理所有数据传输。 后台会话有一些额外的限制。
⚠️ NSURLSession 使用完成之后需要释放,否则会引起内存泄漏问题。
task 任务类型
NSURLSession 支持三种类型的任务:data tasks, download tasks 和 upload tasks。
- data tasks:使用 NSData 对象发送和接收数据。 处理应用程序与服务器之间的简短的,经常交互的请求。 数据任务可以在每次接收到数据后就返回,或者通过 completion handler 一次性返回所有数据到您的应用程序。
- download tasks:下载任务以文件的形式检索数据,并在应用程序未运行时支持后台下载。
- upload tasks:上传任务以文件的形式发送数据,并在应用程序未运行时支持后台上传。
后台传输注意事项
NSURLSession 类在您的应用程序被暂停时支持后台传输。 后台传输仅由使用后台会话配置对象(调用 backgroundSessionConfiguration :
返回的会话)提供。
- 必须提供委托对象来进行事件传递。 (对于上传和下载任务,代理的行为与在进程内传输相同。)
- 只支持HTTP和HTTPS协议(没有自定义协议)。
- 始终遵循重定向。
- 只支持 file 文件上传(应用程序退出后,data 或 stream 类型的传输将会失败)。
- 如果在应用程序处于后台时启动后台传输,配置对象的 discretionary 属性将被视为true。
网络请求创建流程
graph LR
NSURL-->NSURLRequest
NSURLRequest-->NSURLSessionTask
NSURLSessionConfiguration-->NSURLSession
NSURLSession-->NSURLSessionTask
创建和配置 Session
一、NSURLSessionDataTask 示例
// 1.创建 NSURLSessionConfiguration
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSessionConfiguration *ephemeralConfiguration = [NSURLSessionConfiguration ephemeralSessionConfiguration];
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.myapp.networking.background"];
// 配置默认会话的缓存行为
NSString *cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *cachePath = [cachesDirectory stringByAppendingPathComponent:@"MyCache"];
/* Note:
iOS需要设置相对路径:〜/Library/Caches
OS X 要设置绝对路径。
*/
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:16384
diskCapacity:268435456
diskPath:cachePath];
defaultConfiguration.URLCache = cache;
defaultConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
// 2.创建 NSURLSession
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:operationQueue];
NSURLSession *ephemeralSession = [NSURLSession sessionWithConfiguration:ephemeralConfiguration delegate:self delegateQueue:operationQueue];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:self delegateQueue:operationQueue];
// 3.创建 NSURLSessionDataTask
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
[[defaultSession dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSLog(@"Got response %@ with error %@.\n", response, error);
NSLog(@"DATA:\n%@\nEND DATA\n", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}] resume];
二、NSURLSessionDownloadTask 示例
NSURL *url = [NSURL URLWithString:@"https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/ObjC_classic/FoundationObjC.pdf"];
// 1.创建 NSURLSessionConfiguration
NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier: @"com.myapp.networking.background"];
// 2.创建 NSURLSession
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
NSURLSession *backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:self delegateQueue:operationQueue];
// 3.创建 NSURLSessionDownloadTask
NSURLSessionDownloadTask *downloadTask = [backgroundSession downloadTaskWithURL:url];
[downloadTask resume];
# prama mark - Delegate
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
NSLog(@"Session %@ download task %@ wrote an additional %lld bytes (total %lld bytes) out of an expected %lld bytes.\n", session, downloadTask, bytesWritten, totalBytesWritten, totalBytesExpectedToWrite);
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didResumeAtOffset:(int64_t)fileOffset
expectedTotalBytes:(int64_t)expectedTotalBytes
{
NSLog(@"Session %@ download task %@ resumed at offset %lld bytes out of an expected %lld bytes.\n", session, downloadTask, fileOffset, expectedTotalBytes);
}
- (void)URLSession:(NSURLSession *)session
downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
NSLog(@"Session %@ download task %@ finished downloading to URL %@\n", session, downloadTask, location);
// Perform the completion handler for the current session
self.completionHandlers[session.configuration.identifier]();
// Open the downloaded file for reading
NSError *readError = nil;
NSFileHandle *fileHandle = [NSFileHandle fileHandleForReadingFromURL:location error:readError];
// ...
// Move the file to a new URL
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *cacheDirectory = [[fileManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask] firstObject];
NSError *moveError = nil;
if ([fileManager moveItemAtURL:location toURL:cacheDirectory error:moveError]) {
// ...
}
}
三、Uploading Body Content
// 1.创建 NSURLSessionConfiguration
NSURLSessionConfiguration *defaultConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
// 配置默认会话的缓存行为
NSString *cachesDirectory = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES).firstObject;
NSString *cachePath = [cachesDirectory stringByAppendingPathComponent:@"MyCache"];
/* Note:
iOS需要设置相对路径:〜/Library/Caches
OS X 要设置绝对路径。
*/
NSURLCache *cache = [[NSURLCache alloc] initWithMemoryCapacity:16384
diskCapacity:268435456
diskPath:cachePath];
defaultConfiguration.URLCache = cache;
defaultConfiguration.requestCachePolicy = NSURLRequestUseProtocolCachePolicy;
// 2.创建 NSURLSession
NSOperationQueue *operationQueue = [NSOperationQueue mainQueue];
NSURLSession *defaultSession = [NSURLSession sessionWithConfiguration:defaultConfiguration delegate:self delegateQueue:operationQueue];
// ***************************************************************
// 3.1.上传 Data
NSURL *textFileURL = [NSURL fileURLWithPath:@"/path/to/file.txt"];
NSData *data = [NSData dataWithContentsOfURL:textFileURL];
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url];
mutableRequest.HTTPMethod = @"POST";
[mutableRequest setValue:[NSString stringWithFormat:@"%lld", data.length] forHTTPHeaderField:@"Content-Length"];
[mutableRequest setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
NSURLSessionUploadTask *uploadTask = [defaultSession uploadTaskWithRequest:mutableRequest fromData:data];
[uploadTask resume];
// ***************************************************************
// 3.2.上传 File
NSURL *textFileURL = [NSURL fileURLWithPath:@"/path/to/file.txt"];
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url];
mutableRequest.HTTPMethod = @"POST";
NSURLSessionUploadTask *uploadTask = [defaultSession uploadTaskWithRequest:mutableRequest fromFile:textFileURL];
[uploadTask resume];
// ***************************************************************
// 3.3.上传 Stream
NSURL *textFileURL = [NSURL fileURLWithPath:@"/path/to/file.txt"];
NSURL *url = [NSURL URLWithString:@"https://www.example.com/"];
NSMutableURLRequest *mutableRequest = [NSMutableURLRequest requestWithURL:url];
mutableRequest.HTTPMethod = @"POST";
mutableRequest.HTTPBodyStream = [NSInputStream inputStreamWithFileAtPath:textFileURL.path];
[mutableRequest setValue:@"text/plain" forHTTPHeaderField:@"Content-Type"];
[mutableRequest setValue:[NSString stringWithFormat:@"%lld", data.length] forHTTPHeaderField:@"Content-Length"];
NSURLSessionUploadTask *uploadTask = [defaultSession uploadTaskWithStreamedRequest:mutableRequest];
[uploadTask resume];
四、NSURLSessionDataTask 发送 GET 请求
- (void)p_NSURLSessionDataTask_GET {
// 请求路径
NSURL *url = [NSURL URLWithString:@"https://op.juhe.cn/shanghai/hospital?dtype=&key=123"];
// 1.创建 NSURLSession,使用共享 Session
NSURLSession *session = [NSURLSession sharedSession];
// 2.创建 NSURLSessionDataTask, 默认 GET 请求
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
NSLog(@"%@",error.localizedDescription);
}else {
NSLog(@"%@",data);
}
}];
// 3.执行 Task
[dataTask resume];
}
五、NSURLSessionDataTask 发送 POST 请求
- (void)p_NSURLSessionDataTask_POST {
// 请求路径
NSURL *url = [NSURL URLWithString:@"https://op.juhe.cn/shanghai/hospital"];
// 创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法
request.HTTPMethod = @"POST";
// 设置请求体
NSString *stringBody = @"dtype=&key=123";
request.HTTPBody = [stringBody dataUsingEncoding:NSUTF8StringEncoding];
// 1.创建 NSURLSession,使用共享 Session
NSURLSession *session = [NSURLSession sharedSession];
// 2.创建 NSURLSessionDataTask
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
if (error) {
// error
}else {
// data
}
}];
// 3.执行 Task
[dataTask resume];
}
六、NSURLSessionDataTask 设置代理发送请求
- (void)p_NSURLSessionDataTask_Delegate {
// 请求路径
NSURL *url = [NSURL URLWithString:@"https://op.juhe.cn/shanghai/hospital"];
// 创建请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 设置请求方法
request.HTTPMethod = @"POST";
// 设置请求体
NSString *stringBody = @"dtype=&key=5718abc3837ecb471c5d5b1ef1e35130";
request.HTTPBody = [stringBody dataUsingEncoding:NSUTF8StringEncoding];
// 1.创建 NSURLSessionConfiguration
NSURLSessionConfiguration *configuration =
[NSURLSessionConfiguration defaultSessionConfiguration];
// 2.创建 NSURLSession
NSURLSession *session =
[NSURLSession sessionWithConfiguration:configuration
delegate:self
delegateQueue:nil];
// 3.创建 NSURLSessionDataTask
NSURLSessionDataTask *dataTask =
[session dataTaskWithRequest:request];
// 4.执行 Task
[dataTask resume];
}
#pragma mark - NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session didBecomeInvalidWithError:(nullable NSError *)error {
// 请求失败调用。
}
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
// 处理身份验证和凭据。
}
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session {
// 后台任务下载完成后调用
}
#pragma mark - NSURLSessionDataDelegate
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveResponse:(NSURLResponse *)response
completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler {
// 接收到服务器响应的时候调用
// 默认情况下不接收数据,必须告诉系统是否接收服务器返回的数据
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didCompleteWithError:(nullable NSError *)error {
// 请求失败调用
}
- (void)URLSession:(NSURLSession *)session
dataTask:(NSURLSessionDataTask *)dataTask
didReceiveData:(NSData *)data {
// 接受到服务器返回数据的时候调用,可能被调用多次
}
获取新闻示例代码:
-(void)viewdidload {
//创建NSURLSession对象
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
_session = [NSURLSession sessionWithConfiguration:config
delegate:nil
delegateQueue:nil];
//发起网络请求获取新闻
[self fetchHrssnews];
}
#pragma 获取新闻方法
- (void)fetchHrssnews {
//创建NSURLRequest对象
NSString *requestString = hrssnewsString;
NSURL *url = [NSURL URLWithString:requestString];
//方法参数:统一资源定位符、缓存策略:忽略本地缓存、等待web服务器响应最长时间
NSMutableURLRequest *req = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:60.0f];
//设置请求方式为POST
[req setHTTPMethod: @"POST"];
//设置请求体
NSString *dataString = @"ksym=0&jsym=15";
NSData *postData = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[req setHTTPBody:postData];
//创建NSURLSessionDataTask对象
NSURLSessionDataTask *dataTask = [self.session dataTaskWithRequest:req completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
//解析JSON数据
NSDictionary *jsonObject = [NSJSONSerialization
JSONObjectWithData:data
options:kNilOptions
error:nil];
self.msgflag = jsonObject[@"msgflag"];
HQLog(@"msgflag:%@",self.msgflag);
self.msg = jsonObject[@"msg"];
HQLog(@"msg:%@",self.msg);
//判断是否成功获取服务器端数据
if ([self.msgflag isEqualToString:@"0"]) {
HQLog(@"msgflag:%@",self.msgflag);
}else{
HQLog(@"msgflag:-1,msg:For input string: ");
}
//使用dispatch_asynch函数让reloadData方法在主线程中运行
dispatch_async(dispatch_get_main_queue(), ^{
//重新加载UITableView对象的数据
[self.tableView reloadData];});
//停止刷新
[self.tableView.mj_header endRefreshing];
}];
//NSURLSessionDataTask在刚创建的时候默认处于挂起状态,需要手动调用恢复。
[dataTask resume];
}
参考
- Apple: URL Session Programming Guide
- Apple: NSURLSession
- 天天品尝iOS7甜点 :: Day 1 :: NSURLSession
- iOS网络2——NSURLSession使用详解
- NSURLSessionConfiguration API详解
- iOS 通俗易懂的HTTP网络:URL、报文、Swift
- 开发只懂 AFN ?搞定 NSURLSession 才是硬道理
- NSURLSession与NSURLConnection区别
- iOS网络请求在Controller退出后是否应该被取消?@MrPeak
- 深度优化iOS网络模块 @MrPeak
- NSURL /NSURLComponents