iOS网络篇AFNetworking/NSURLSession/
目录
一、NSData
二、NSURLConnection ios9开始弃用
三、NSURLConnection 代理断点下载
四、NSURLSession ios9+
五、NSURLSession 代理断点下载,队列管理多任务下载
六、NSURLSession 上传
七、AFNetworking
八、HTTP 请求方法
九、OSI七层模型与TCP/IP五层模型
一、NSData原始方式,目前基本不再使用
// 使用NSData下载图片文件,并显示再imageView上
// 在子线程中发送请求
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 创建下载路径
NSURL *url = [NSURL URLWithString:PicUrlLink];
// NSData的dataWithContentsOfURL:方法下载
NSData *data = [NSData dataWithContentsOfURL:url];
// 回到主线程,刷新UI
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = [UIImage imageWithData:data];
});
});
二、NSURLConnection,ios9开始弃用
1 异步方式
2 同步方式
3 代理方式
NSString *FileUrlLink = @"https://dldir1.qq.com/weixin/mac/WeChatMac.dmg";
// 1 异步方式,需要operaionQueue,下列两种方式都可以
NSOperationQueue *operaionQueue = [[NSOperationQueue alloc] init];
operaionQueue = [NSOperationQueue mainQueue];
[NSURLConnection sendAsynchronousRequest:urlRequest queue:operaionQueue completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
NSLog(@" 异步 response : %@, data : %@, connectionError : %@", response, data, connectionError);
}];
// 2 同步方式,需要定义网络响应体,错误
NSError *connectionError;
NSURLResponse *response = [[NSURLResponse alloc] init];
[NSURLConnection sendSynchronousRequest:urlRequest returningResponse:&response error:&connectionError];
NSLog(@" 同步 response : %@, data : %@, connectionError : %@", response, nil, connectionError);
// 3 代理方式,需要实现相关代理方法,在代理方法里获取相关对象和数据
[[NSURLConnection alloc] initWithRequest:urlRequest delegate:self];
[NSURLConnection connectionWithRequest:urlRequest delegate:self];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:YES];
NSURLConnection *urlConnection = [[NSURLConnection alloc] initWithRequest:urlRequest delegate:self startImmediately:NO];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[urlConnection scheduleInRunLoop:runLoop forMode:NSDefaultRunLoopMode];
[urlConnection start];
三、NSURLConnection 代理断点下载
复杂数据类型或大文件下载使用代理方式方便处理,以大文件断点下载说明
重点使用文件句柄NSFileHandle,来定点保存数据。每次保存数据时,首先将句柄移动到文件的最后面,然后将数据写入沙盒。
self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];
[self.writeHandle seekToEndOfFile];
[self.writeHandle writeData:data];
// 协议
<NSURLConnectionDelegate,NSURLConnectionDataDelegate>
@property (assign, nonatomic) long long fileLength;
@property (assign, nonatomic) NSUInteger currentLength;
@property (strong, nonatomic) NSFileHandle *fileHandle;
// 开始下载
- (void) download {
// 如果有正在进行中的,需要停止先
if (self.urlConnection) {
[self cancel];
}
// 1.URL
NSURL *url = [NSURL URLWithString:FileUrlLink];
// 2.请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 重点:设置请求头,上次已经下载的长度
NSString *range = [NSString stringWithFormat:@"bytes=%lld", self.currentLength];
[request setValue:range forHTTPHeaderField:@"Range"];
// 3.下载(创建完conn对象后,会自动发起一个异步请求)
self.urlConnection = [NSURLConnection connectionWithRequest:request delegate:self];
}
// 取消下载
- (void) cancel {
[self.urlConnection cancel];
self.urlConnection = nil;
}
#pragma mark - NSURLConnectionDataDelegate
//开始接收到服务器的响应时调用
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(@"didReceiveResponse: %lld",response.expectedContentLength);
if (self.currentLength) return; // 如果已经下载过了,就直接跳过
// 文件路径
NSString *caches = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *filepath = [caches stringByAppendingPathComponent:@"WeChatMac001.dmg"];
// 创建一个空的文件 到 沙盒中
NSFileManager *mgr = [NSFileManager defaultManager];
[mgr createFileAtPath:filepath contents:nil attributes:nil];
// 创建一个用来写数据的文件句柄
self.writeHandle = [NSFileHandle fileHandleForWritingAtPath:filepath];
// 获得文件的总大小
self.totalLength = response.expectedContentLength;
}
// 接收到服务器返回的数据时调用(服务器返回的数据比较大时会调用多次)
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSLog(@"didReceiveData: %lu",(unsigned long)data.length);
// 移动到文件的最后面
[self.writeHandle seekToEndOfFile];
// 将数据写入沙盒
[self.writeHandle writeData:data];
// 累计文件的长度
self.currentLength += data.length;
}
// 服务器返回的数据完全接收完毕后调用
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"connectionDidFinishLoading");
self.currentLength = 0;
self.totalLength = 0;
// 关闭文件
[self.writeHandle closeFile];
self.writeHandle = nil;
}
// 请求出错时调用(比如请求超时)
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"didFailWithError: %@",error.description);
}
SessionTaskArch.png四、NSURLSession ios9+
GET NSURLRequest默认设置 请求参数直接?key=value拼接在接口后面
POST NSURLRequest设置请求方法HTTPMethod 请求参数放在请求体中
4.1 GET
-(void) getByDataTaskWithURL {
NSString *GetUrlLik = @"协议://ip:port/接口路径名称?参数key=参数value";
NSURL *url = [NSURL URLWithString:GetUrlLik];
// 会话对象
NSURLSession *session = [NSURLSession sharedSession];
// 根据会话对象创建一个Task
// 该方法内部会自动将请求路径url包装成一个请求对象,该请求对象默认包含了请求头信息和请求方法GET
// 如果要发送的是POST请求,则不能使用该方法
NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 解析数据
}];
// 执行任务
[dataTask resume];
}
-(void) getByDataTaskWithRequest {
NSURL *url = [NSURL URLWithString:GetUrlLik];
// 请求对象内部默认已经包含了请求头和请求方法(GET)
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 处理数据
}];
[dataTask resume];
}
-(void) getWithDelegate {
NSURL *url = [NSURL URLWithString:GetUrlLik];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
/*
第一个参数:会话对象的配置信息defaultSessionConfiguration 表示默认配置
第二个参数:谁成为代理,此处为控制器本身即self
第三个参数:队列,该队列决定代理方法在哪个线程中调用,可以传主队列|非主队列
[NSOperationQueue mainQueue] 主队列: 代理方法在主线程中调用
[[NSOperationQueue alloc]init] 非主队列: 代理方法在子线程中调用
*/
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
// 根据会话对象创建一个Task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request];
[dataTask resume];
}
#pragma mark - NSURLSessionDataDelegate
// 接收到服务器响应的时候调用该方法
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
// 注意:需要使用completionHandler回调告诉系统应该如何处理服务器返回的数据
//默认是取消的
/*
NSURLSessionResponseCancel = 0, 默认的处理方式,取消
NSURLSessionResponseAllow = 1, 接收服务器返回的数据
NSURLSessionResponseBecomeDownload = 2,变成一个下载请求
NSURLSessionResponseBecomeStream 变成一个流
*/
completionHandler(NSURLSessionResponseAllow);
}
//2.接收到服务器返回数据的时候会调用该方法,如果数据较大那么该方法可能会调用多次
-(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
NSLog(@"didReceiveData--%@",[NSThread currentThread]);
}
//3.当请求完成(成功|失败)的时候会调用该方法
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
}
4.2 POST
-(void)postByDataTaskWithRequest {
NSString *PostUrlLik = @"协议://ip:port/接口路径名称";
NSURL *url = [NSURL URLWithString:PostUrlLik];
NSURLSession *session = [NSURLSession sharedSession];
//创建可变的请求对象
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 修改请求方法为POST
request.HTTPMethod = @"POST";
// 设置请求体
request.HTTPBody = [@"参数key=参数value" dataUsingEncoding:NSUTF8StringEncoding];
// 根据会话对象创建一个Task
NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
[dataTask resume];
}
五、NSURLSession 代理断点下载,队列管理多任务下载
- [self.downloadTask cancel] 同步完全取消,不可原点重启
- [self.downloadTask cancelByProducingResumeData:] 异步取消,可以重启
- self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];根据resumeData获取suspend时的Task,然后resume重启Task。
5.1 异步下载
- (NSURLSessionDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithURL:(NSURL *)url completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionDownloadTask *)downloadTaskWithResumeData:(NSData *)resumeData completionHandler:(void (^)(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
5.2 代理断点下载
@property(nonatomic,strong)NSData *resumeData;
@property(nonatomic,strong)NSURLSession *urlSession;
@property(nonatomic,strong)NSURLSessionDownloadTask *downloadTask;
#pragma mark - 断点续传
// 开始下载
- (void) download {
NSURL *url = [NSURL URLWithString:@"https://dldir1.qq.com/weixin/mac/WeChatMac.dmg"];
if ( self.downloadTask ) {
// 完全取消,不可原点重启
// [self.downloadTask cancel];
[self suspendCallBack:^{
// 完全取消后再继续下载
[self download];
}];
return;
}
if (self.resumeData) { // 之前已经下载过了
self.downloadTask = [self.urlSession downloadTaskWithResumeData:self.resumeData];
} else {
self.urlSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
self.downloadTask = [self.urlSession downloadTaskWithRequest:request];
[self.urlSession downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
}];
}
[self.downloadTask resume];
}
// 恢复
- (void) reset {
[self suspendCallBack:^{
self.downloadTask = nil;
self.urlSession = nil;
self.resumeData = nil;
}];
self.lblProgress.text = [NSString stringWithFormat:@"下载进度:%.2f%%",100.0 * 0];
CGRect frame = CGRectMake(0, 0, kScreanWidth * 0.6 * 0, 20);
self.progressView.frame = frame;
}
// 取消下载
- (void) suspend {
[self suspendCallBack:nil];
}
- (void) suspendCallBack:(void(^)(void)) callBack {
if (self.downloadTask) {
__weak typeof (self)weakSelf = self;
[self.downloadTask cancelByProducingResumeData:^(NSData *resumeData) {
NSLog(@"resumeData:%@",resumeData);
weakSelf.resumeData = resumeData;
weakSelf.downloadTask = nil;
if ( callBack ) {
callBack();
}
}];
}
}
#pragma mark - NSURLSessionDownloadDelegate
/**
* 写入临时文件时调用
* @param bytesWritten 本次写入大小
* @param totalBytesWritten 已写入文件大小
* @param totalBytesExpectedToWrite 请求的总文件的大小
*/
- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
//可以监听下载的进度
CGFloat progress = 1.0 * totalBytesWritten / totalBytesExpectedToWrite;
dispatch_async(dispatch_get_main_queue(), ^{
self.lblProgress.text = [NSString stringWithFormat:@"下载进度:%.2f%%",100.0 * progress];
CGRect frame = CGRectMake(0, 0, kScreanWidth * 0.6 * progress, 20);
self.progressView.frame = frame;
});
}
// 下载完成调用
- (void) URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
// location 还是一个临时路径,需要自己挪到需要的路径(caches 文件夹)
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
NSLog(@"downloadTask 移动文件路径 - %@", filePath);
}
队列管理多任务下载
- 自定义MAOperation继承自NSOperation,定义属性NSURLSessionTask *dataTask,关联后受队列控制;
- MAOperation重写start等必要方法,在start内部[dataTask resume],开启任务;
- NSOperation中finished/executing/cancelled等为readonly只读属性,因此通过KVO手动绑定set方法,来设置值;使用到以下两个重要方法:
- MATaskManager,管理NSURLSession,NSOperationQueue,通过MAOperation将Task任务与队列关联,
- 设置maxConcurrentOperationCount,队列的最大并发操作数;当某个操作完成时,必须设置MAOperation.finished = YES来触发下一个操作任务开启。
- NSURLSessionDownloadDelegate代理方法中,保存下载内容到目录(如缓存在磁盘里),以便后续使用,如下;或直接保存到相册;
- 该小节通过下载MP4格式视频,缓存在磁盘中,通过AVPlayerLayer来加载播放;
- SDWebImage使用了类似的方式,而AFNetworking是在创建task时直接resume。
// 3
- (void)willChangeValueForKey:(NSString *)key;
- (void)didChangeValueForKey:(NSString *)key;
// 6
// 创建文件目录
NSString *filePath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
// location 是一个临时路径,需要移动到创建好的路径(caches 文件夹)中,缓存在磁盘里
[[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:filePath] error:nil];
六、NSURLSession 上传
注意 确定好request请求相关设置
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromFile:(NSURL *)fileURL completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (NSURLSessionUploadTask *)uploadTaskWithRequest:(NSURLRequest *)request fromData:(nullable NSData *)bodyData completionHandler:(void (^)(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error))completionHandler;
- (void) sessionUploadTask {
NSURL *url = [NSURL URLWithString:UploadUrlLink];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[request setHTTPMethod:@"POST"];
// 注意,以下列设置为例:
// .1设置请求体
[request setValue:[NSString stringWithFormat: @"multipart/form-data;%@", @"cs"] forHTTPHeaderField:@"Content-type"];
// .2上传数据
NSData *data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"uploadPic" ofType:@"png"]];
// .3拼接body,可扩展
NSMutableData *body = [NSMutableData data];
[myRequestData appendData:data];//将image的data加入
NSURLSession *session = [NSURLSession sharedSession];
NSURLSessionUploadTask *uploadTask = [session uploadTaskWithRequest:request fromData:body completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
}];
[uploadTask resume];
}
七、AFNetworking
-
AFURLSessionManager在初始化时通过get方法如下
_session = [NSURLSession sessionWithConfiguration:self.sessionConfiguration delegate:self delegateQueue:self.operationQueue];设置了代理,队列。 -
AFHTTPSessionManager 继承自AFURLSessionManager,其在AFURLSessionManager的基础上,封装了GET/POST/HEAD等方法。而GET/POST/HEAD等方法最后都通过如:
[-addDelegateForDownloadTask: progress: destination: completionHandler:],
与AFURLSessionManagerTaskDelegate关联上,同时为task添加了suspend、resume观察者。TaskDelegate保存在AFURLSessionManager的mutableTaskDelegatesKeyedByTaskIdentifier字典中,同时将uploadProgress、uploadProgress保存在TaskDelegate中。 -
在AFURLSessionManager中实现的部分代理方法中,通过task在mutableTaskDelegatesKeyedByTaskIdentifier字典中获取对应的TaskDelegate,将uploadProgress、uploadProgress中转到TaskDelegate中实现的代理方法里处理。
-
_AFURLSessionTaskSwizzling 通过RunTime替换task的方法suspend、resume,在替换后的方法中状态通知。
@property(nonatomic,strong) UIView *progressView;
@property(nonatomic,strong) UILabel *lblProgress;
@property(nonatomic,strong) NSURLSessionTask *sessionTask;
- (void) getHandler {
[self setProgressValue:0];
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObject:@"application/pdf"];
__weak __typeof__(self) weakSelf = self;
//临时配置,需要自己根据接口地址改动
NSString *urlStr = @"https://images.apple.com/legal/education/apple-school-manager/ASM-HK-ZH.pdf";
self.sessionTask = [manager GET:urlStr parameters:@{} headers:@{} progress:^(NSProgress * _Nonnull downloadProgress) {
__strong __typeof__(weakSelf) strongSelf = weakSelf;
// 下载进度
CGFloat progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
dispatch_async(dispatch_get_main_queue(), ^{
[strongSelf setProgressValue:progress];
});
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
__strong __typeof__(weakSelf) strongSelf = weakSelf;
NSString *filePath = @"";
[responseObject writeToFile:filePath atomically:YES];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
NSLog(@"%@",error.userInfo);
}];
}
- (void)downloadTaskWithRequest {
[self setProgressValue:0];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// 2. 创建下载路径和请求对象
NSURL *URL = [NSURL URLWithString:DownloadUrlLink];
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 3.创建下载任务
__weak __typeof__(self) weakSelf = self;
NSURLSessionDownloadTask *downloadTask = [manager downloadTaskWithRequest:request progress:^(NSProgress *downloadProgress) {
__strong __typeof__(weakSelf) strongSelf = weakSelf;
// 获取主线程,不然无法正确显示进度。
NSOperationQueue* mainQueue = [NSOperationQueue mainQueue];
[mainQueue addOperationWithBlock:^{
// 下载进度
CGFloat progress = 1.0 * downloadProgress.completedUnitCount / downloadProgress.totalUnitCount;
[strongSelf setProgressValue:progress];
}];
} destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
NSURL *path = [[NSFileManager defaultManager] URLForDirectory:NSDocumentDirectory inDomain:NSUserDomainMask appropriateForURL:nil create:NO error:nil];
return [path URLByAppendingPathComponent:@"education.pdf];
} completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
NSLog(@"File downloaded to: %@", filePath);
}];
self.sessionTask = downloadTask;
[downloadTask resume];
}
八、HTTP 请求方法
HTTP请求方法:GET、HEAD、POST、PUT、DELETE、CONNECT、OPTIONS、TRACE 说明及HTTP版本说明
OSI说明九、OSI七层模型与TCP/IP五层模型
OSI与TCP:IP对应协议
OSI与TCP:IP对应设备
OSI(Open System Interconnection)开放系统互连参考七层模型