iOS 知识点

iOS网络篇AFNetworking/NSURLSession/

2020-08-10  本文已影响0人  愤斗的小蚂蚁

目录
一、NSData
二、NSURLConnection ios9开始弃用
三、NSURLConnection 代理断点下载
四、NSURLSession ios9+
五、NSURLSession 代理断点下载,队列管理多任务下载
六、NSURLSession 上传
七、AFNetworking
八、HTTP 请求方法
九、OSI七层模型与TCP/IP五层模型

具体实现请直接阅读源码Session/Connection/Data/AFNetworking Demo

一、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);
}

四、NSURLSession ios9+

SessionTaskArch.png
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 代理断点下载,队列管理多任务下载

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);
}

队列管理多任务下载

  1. 自定义MAOperation继承自NSOperation,定义属性NSURLSessionTask *dataTask,关联后受队列控制;
  2. MAOperation重写start等必要方法,在start内部[dataTask resume],开启任务;
  3. NSOperation中finished/executing/cancelled等为readonly只读属性,因此通过KVO手动绑定set方法,来设置值;使用到以下两个重要方法:
  4. MATaskManager,管理NSURLSession,NSOperationQueue,通过MAOperation将Task任务与队列关联,
  5. 设置maxConcurrentOperationCount,队列的最大并发操作数;当某个操作完成时,必须设置MAOperation.finished = YES来触发下一个操作任务开启。
  6. NSURLSessionDownloadDelegate代理方法中,保存下载内容到目录(如缓存在磁盘里),以便后续使用,如下;或直接保存到相册;
  7. 该小节通过下载MP4格式视频,缓存在磁盘中,通过AVPlayerLayer来加载播放;
  8. SDWebImage使用了类似的方式,而AFNetworking是在创建task时直接resume。

具体实现请直接阅读源码Session/Connection/Data/AFNetworking Demo

建议参考阅读SYDownloadManager源码

// 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

@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七层模型与TCP/IP五层模型

OSI说明
OSI与TCP:IP对应协议
OSI与TCP:IP对应设备

OSI参考模型 图文说明

OSI(Open System Interconnection)开放系统互连参考七层模型

以太网根据IEEE 802.3标准来管理和控制数据帧

具体实现请直接阅读源码Session/Connection/Data/AFNetworking Demo

上一篇 下一篇

猜你喜欢

热点阅读