多线程网络06

1 获取文件的MIMEType

1.1 部分文件的MIMEType

类型 文件拓展名 MIMEType
图片 png image/png
图片 bmp\dib image/bmp
图片 jpe\jpeg.jpg image/jpeg
图片 gif image/gif
多媒体 mp3 audio/mpeg
多媒体 mp4\mpg4\m4vmp4v video/mp4
文本 js application/javascript
文本 pdf application/pdf
文本 text\txt text/plain
文本 xml text/xml

1.2 获取文件MIMEType方式

#import "ViewController.h"

#import <MobileCoreServices/MobileCoreServices.h>

@interface ViewController ()

@end

@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //1.发送请求,可以响应头(内部有MIMEType)
    //2.百度
    //3.调用C语言的API
    //4.application/octet-stream 任意的二进制数据类型
    
    //[self getMimeType];
   NSString *mimeType= [self mimeTypeForFileAtPath:@"/Users/xiaomage/Desktop/123.h"];
    NSLog(@"%@",mimeType);
}

-(void)getMimeType
{
    
    //1.url
    NSURL *url = [NSURL fileURLWithPath:@"/Users/xiaomage/Desktop/123.h"];
    
    //2.创建请求对象
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //3.发送异步请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
       
        //4.获得文件的类型
        NSLog(@"%@",response.MIMEType);
    }];
    
}

- (NSString *)mimeTypeForFileAtPath:(NSString *)path
{
    if (![[[NSFileManager alloc] init] fileExistsAtPath:path]) {
        return nil;
    }
    
    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
    CFRelease(UTI);
    if (!MIMEType) {
        return @"application/octet-stream";
    }
    return (__bridge NSString *)(MIMEType);
}
2 多线程下载文件思路

开启3个线程,每个线程下载一部分数据,使用文件句柄设置偏移保证在写入数据到本地沙盒的时候写入到指定位置。

3 文件的压缩和解压缩

第三方解压缩框架--ZipArchive

#import "ViewController.h"
#import "SSZipArchive.h"

@interface ViewController ()

@end

@implementation ViewController
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self unzip];
}

-(void)zip
{
    NSArray *arrayM = @[
                        @"/Users/xiaomage/Desktop/Snip20160226_2.png",
                        @"/Users/xiaomage/Desktop/Snip20160226_6.png"
                        ];
    /*
     第一个参数:压缩文件的存放位置
     第二个参数:要压缩哪些文件(路径)
     */
    //[SSZipArchive createZipFileAtPath:@"/Users/xiaomage/Desktop/Test.zip" withFilesAtPaths:arrayM];
    [SSZipArchive createZipFileAtPath:@"/Users/xiaomage/Desktop/Test.zip" withFilesAtPaths:arrayM withPassword:@"123456"];
}

-(void)zip2
{
    /*
     第一个参数:压缩文件存放位置
     第二个参数:要压缩的文件夹(目录)
     */
    [SSZipArchive createZipFileAtPath:@"/Users/xiaomage/Desktop/demo.zip" withContentsOfDirectory:@"/Users/xiaomage/Desktop/demo"];
}

-(void)unzip
{
    /*
     第一个参数:要解压的文件在哪里
     第二个参数:文件应该解压到什么地方
     */
    //[SSZipArchive unzipFileAtPath:@"/Users/xiaomage/Desktop/demo.zip" toDestination:@"/Users/xiaomage/Desktop/xx"];
    
    [SSZipArchive unzipFileAtPath:@"/Users/xiaomage/Desktop/demo.zip" toDestination:@"/Users/xiaomage/Desktop/xx" progressHandler:^(NSString *entry, unz_file_info zipInfo, long entryNumber, long total) {
        NSLog(@"%zd---%zd",entryNumber,total);
        
    } completionHandler:^(NSString *path, BOOL succeeded, NSError *error) {
        
        NSLog(@"%@",path);
    }];
}
4 NSURLConnection和RunLoop补充
#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDataDelegate>

@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self newThreadDelegate2];
}

-(void)delegate1
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];
    
    //设置代理
    //代理方法:默认是在主线程中调用的
    NSURLConnection *connect = [NSURLConnection connectionWithRequest:request delegate:self];
    
    
    //设置代理方法在哪个线程中调用
    //[NSOperationQueue alloc]init]]    开子线程
    [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
    //[NSOperationQueue mainQueue]  不能这样设置
    //[connect setDelegateQueue:[NSOperationQueue mainQueue]];
    
    NSLog(@"-------");
}

-(void)delegate2
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];
    
    //设置代理
    //代理方法:默认是在主线程中调用的
    NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];

    
    [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
    
    //开始发送请求
    [connect start];
    NSLog(@"-------");
}

-(void)newThreadDelegate1
{
   dispatch_async(dispatch_get_global_queue(0, 0), ^{
      
       NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];
       
       //设置代理
       //代理方法:默认是在主线程中调用的
       //该方法内部其实会将connect对象作为一个source添加到当前的runloop中,指定运行模式为默认
       NSURLConnection *connect = [NSURLConnection connectionWithRequest:request delegate:self];
       
       //设置代理方法在哪个线程中调用
       [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
       
       //[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1000]];
       [[NSRunLoop currentRunLoop]run];
       
         NSLog(@"---%@----",[NSThread currentThread]);
   });
  
}

-(void)newThreadDelegate2
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];
        
        //设置代理
        //代理方法:默认是在主线程中调用的
        NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];
        
        [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
        
        //开始发送请求
        //如如果connect对象没有添加到runloop中,那么该方法内部会自动的添加到runloop
        //注意:如果当前的runloop没有开启,那么该方法内部会自动获得当前线程对应的runloop对象并且开启
        [connect start];
        NSLog(@"---%@----",[NSThread currentThread]);
    });
}

#pragma mark ----------------------
#pragma mark NSURLConnectionDataDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSLog(@"didReceiveResponse---%@",[NSThread currentThread]);
}
5 NSURLSession的基本使用

5.1 NSURLSession使用步骤

  • 使用NSURLSession对象创建Task,然后执行Task
  • Task的类型


    Snip20191216_1.png

5.2 获得NSURLSession

  • 获取共享的Session
    + (NSURLSession *)shareSession;
  • 自定义Session
    + (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id)delegateQueue:(NSOperationQueue *)queue

5.3 get和post请求

- (void)get1 {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg"];
    //2 创建请求对象
    NSURLRequest *requet = [NSURLRequest requestWithURL:url];
    //3 创建session(会话)对象
    NSURLSession *session = [NSURLSession sharedSession];
    //4 创建task
    /*
     参数1:请求对象
     参数2:请求回调
        data:响应体
        response:响应头
        error:错误信息
     */
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requet completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"error=%@",error);
        } else {
            UIImage *image = [UIImage imageWithData:data];
            NSLog(@"%@",image);
        }
    }];
    //5 执行task
    [dataTask resume];
}

- (void)get2 {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg"];
    //2 创建session(会话)对象
    NSURLSession *session = [NSURLSession sharedSession];
    //3 创建task
    /*
     参数1:请求地址
     参数2:请求回调
        data:响应体
        response:响应头
        error:错误信息
     注:这个方法内部会以url作为参数创建一个请求对象
     */
    NSURLSessionDataTask *dataTask = [session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        if (error) {
            NSLog(@"error=%@",error);
        } else {
            UIImage *image = [UIImage imageWithData:data];
            NSLog(@"%@",image);
        }
    }];
    //4 执行task
    [dataTask resume];
}

- (void)post {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"https://service.agent.guxiansheng.cn?c=adplace&a=getinfoad&v=App&site=marketing"];
    //2 创建请求对象
    NSMutableURLRequest *requet = [NSMutableURLRequest requestWithURL:url];
    //2.1 设置请求方法
    requet.HTTPMethod = @"POST";
    //2.2 设置请求体
    requet.HTTPBody = [@"id=1&appcode=5c6bb51a113c8szji5nb6cur" dataUsingEncoding:NSUTF8StringEncoding];
    //3 创建session(会话)对象
    NSURLSession *session = [NSURLSession sharedSession];
    //4 创建task
    /*
     参数1:请求对象
     参数2:请求回调
        data:响应体
        response:响应头
        error:错误信息
     */
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requet completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        // 注:回调的线程在子线程,如果处理UI需要回到主线程
        NSLog(@"%@",[NSThread currentThread]);
        if (error) {
            NSLog(@"error=%@",error);
        } else {
            NSString *dataStr = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
            NSLog(@"%@",dataStr);
        }
    }];
    //5 执行task
    [dataTask resume];
}
6 NSURLSession代理方法
- (void)delegate {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg"];
    //2 创建请求对象
    NSURLRequest *requet = [NSURLRequest requestWithURL:url];
    //3 创建session(会话)对象
    /*
     参数1:会话配置
     参数2:代理
     参数3:设置代理在哪个线程执行
     */
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    //4 创建task
    NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:requet];
    //5 执行任务
    [dataTask resume];
}

#pragma mark - NSURLSessionDataDelegate

/// 接收到服务器的响应:该f方法默认会取消请求
/// @param session 会话对象
/// @param dataTask 请求任务
/// @param response 响应头信息
/// @param completionHandler 回调 回传给服务器告诉服务器做什么
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    /*
     NSURLSessionResponseCancel = 0, 取消任务 默认
     NSURLSessionResponseAllow = 1, 继续执行任务
     NSURLSessionResponseBecomeDownload = 2, 转换为下载任务
     NSURLSessionResponseBecomeStream 变为流
     */
    completionHandler(NSURLSessionResponseAllow);
    NSLog(@"%s",__func__);
}


/// 接收到服务器返回数据
/// @param session 会话对象
/// @param dataTask 请求任务
/// @param data 接收到的数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    NSLog(@"%s",__func__);
    [self.receiveData appendData:data];
}


/// 请求完成或者请求失败回调
/// @param session 会话对象
/// @param task 请求任务
/// @param error 错误信息
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"%s",__func__);
    NSLog(@"%@",[UIImage imageWithData:self.receiveData]);
}

#pragma mark - getter
- (NSMutableData *)receiveData {
    if (!_receiveData) {
        _receiveData = [NSMutableData data];
    }
    return _receiveData;
}
7 NSURLSessionDownloadTask大文件下载

7.1 block方式下载大文件
该方法实现了边接收数据边保存到本地沙盒的功能,缺点是不能监听进度

// 使用block方式下载大文件
- (void)downloadFile1 {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"http://dmimg.5054399.com/allimg/pkm/pk/22.jpg"];
    //2 创建请求对象
    NSURLRequest *requet = [NSURLRequest requestWithURL:url];
    //3 创建session(会话)对象
    NSURLSession *session = [NSURLSession sharedSession];
    //4 创建task
    //location:文件下载临时保存的沙盒路径
    //response:请求头
    //error:错误信息
    //注:该方法实现了边接收数据边保存到本地沙盒的功能,缺点是不能监听进度
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:requet completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        //6 解析数据
        NSLog(@"location=%@",location);
        //获取文件保存沙盒路径
        NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
        NSLog(@"fullPath=%@",fullPath);
        //剪切文件
       [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:&error];
    }];
    //5 执行任务
    [downloadTask resume];
}

7.2 delegate方式下载大文件
该方法实现了边接收数据边保存到本地沙盒的功能,并且这种方式可以监听下载进度

// 使用delegate方式下载大文件
- (void)downloadFile2 {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"http://file02.16sucai.com/d/file/2015/0128/8b0f093a8edea9f7e7458406f19098af.jpg"];
    //2 创建请求对象
    NSURLRequest *requet = [NSURLRequest requestWithURL:url];
    //3 创建session(会话)对象
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    //4 创建下载任务
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:requet];
    //5 执行任务
    [downloadTask resume];
}

#pragma mark - NSURLSessionDownloadDelegate
/// 写入数据
/// @param session 会话对象
/// @param downloadTask 下载任务
/// @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 {
    // 获取文件下载路径
    NSLog(@"%f",totalBytesWritten * 1.0 / totalBytesExpectedToWrite);
}

/// 当回复下载的时候调用该方法
/// @param session 会话对象
/// @param downloadTask 下载任务
/// @param fileOffset 从什么地方开始下载
/// @param expectedTotalBytes 本次下载文件内容大小
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"%s",__func__);
}


/// 下载完成时调用
/// @param session 会话对象
/// @param downloadTask 下载任务
/// @param location 文件临时存储路径
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"%s",__func__);
    //获取文件保存沙盒路径
    NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    NSLog(@"%@",fullPath);
    //剪切文件
    NSError *error;
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:&error];
    if (error) {
        NSLog(@"error=%@",error);
    }
}


/// 请求结束
/// @param session 会话对象
/// @param task 下载任务
/// @param error 错误信息
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"%s",__func__);
}

7.3 NSURLSessionDownloadTask大文件下载-断点下载
该方法局限性

  • 如果用户点击暂停之后退出程序,那么需要把恢复下载的数据写一份到沙盒,代码复杂度较高
  • 如果用户在下载中未保存恢复下载数据即退出程序,则不具备可操作性
// 开始下载
- (IBAction)startDownload:(id)sender {
    NSLog(@"+++++++++++++++start");
    [self downloadFile2];
}
// 暂停下载
- (IBAction)suspendDownload:(id)sender {
    NSLog(@"+++++++++++++++suspend");
    [self.downloadTask suspend];
}
// 恢复下载
- (IBAction)resumeDownload:(id)sender {
    NSLog(@"+++++++++++++++resume");
    if (self.resumeData) { // 如果之前取消过,则使用这种方式重新恢复下载任务,并且恢复之前的下载
        self.downloadTask = [self.session downloadTaskWithResumeData:self.resumeData];
    }
    [self.downloadTask resume];
}
// 取消下载
- (IBAction)cancelDownload:(id)sender {
    NSLog(@"+++++++++++++++cancel");
    // cancel方式不能恢复
    //[self.downloadTask cancel];
    // 这种方式可以恢复下载
    // resumeData != 下载的文件,记录的是之前下载的文件大小,位置等信息
    [self.downloadTask cancelByProducingResumeData:^(NSData * _Nullable resumeData) {
        self.resumeData = resumeData;
    }];
}

// 使用delegate方式下载大文件
- (void)downloadFile2 {
    //1 创建url
    NSURL *url = [NSURL URLWithString:@"http://www.hitow.net/data/attachment/album/201612/10/205628nppuzll3137siny2.jpg"];
    //2 创建请求对象
    NSURLRequest *requet = [NSURLRequest requestWithURL:url];
    //3 创建session(会话)对象
    NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    self.session = session;
    //4 创建下载任务
    NSURLSessionDownloadTask *downloadTask = [session downloadTaskWithRequest:requet];
    //5 执行任务
    [downloadTask resume];
    self.downloadTask = downloadTask;
}

#pragma mark - NSURLSessionDownloadDelegate
/// 写入数据
/// @param session 会话对象
/// @param downloadTask 下载任务
/// @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 {
    // 获取文件下载路径
    NSLog(@"%f",totalBytesWritten * 1.0 / totalBytesExpectedToWrite);
}

/// 当回复下载的时候调用该方法
/// @param session 会话对象
/// @param downloadTask 下载任务
/// @param fileOffset 从什么地方开始下载
/// @param expectedTotalBytes 本次下载文件内容大小
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes {
    NSLog(@"%s",__func__);
}


/// 下载完成时调用
/// @param session 会话对象
/// @param downloadTask 下载任务
/// @param location 文件临时存储路径
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location {
    NSLog(@"%s",__func__);
    //获取文件保存沙盒路径
    NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:downloadTask.response.suggestedFilename];
    NSLog(@"%@",fullPath);
    //剪切文件
    NSError *error;
    [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:fullPath] error:&error];
    if (error) {
        NSLog(@"error=%@",error);
    }
}

/// 请求结束
/// @param session 会话对象
/// @param task 下载任务
/// @param error 错误信息
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"%s",__func__);
}

7.4 NSURLSesion离线断点下载
注意:session的释放问题
The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you don’t invalidate the session, your app leaks memory until it exits.

#import "ViewController.h"

#define FileName @"download.jpg"

@interface ViewController ()<NSURLSessionDataDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;
@property (strong, nonatomic) NSFileHandle *fileHandle;
@property (assign, nonatomic) NSInteger totalSize;
@property (assign, nonatomic) NSInteger currentSize;
@property (strong, nonatomic) NSString *fullPath;
@property (strong, nonatomic) NSURLSessionDataTask *dataTask;
@end

@implementation ViewController
- (IBAction)startDownload:(id)sender {
    NSLog(@"++++++++++++++++++++++start");
    // 执行任务
    [self.dataTask resume];
}

- (IBAction)suspendDownload:(id)sender {
    NSLog(@"++++++++++++++++++++++suspend");
    [self.dataTask suspend];
}

- (IBAction)resumeDownload:(id)sender {
    NSLog(@"++++++++++++++++++++++resume");
    [self.dataTask resume];
}

- (IBAction)cancelDownload:(id)sender {
    NSLog(@"++++++++++++++++++++++cancel");
    [self.dataTask cancel];
    self.dataTask = nil;
}


#pragma mark - NSURLSessionDataDelegate
/// 接收到服务器的响应:该f方法默认会取消请求
/// @param session 会话对象
/// @param dataTask 请求任务
/// @param response 响应头信息
/// @param completionHandler 回调 回传给服务器告诉服务器做什么
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
    self.totalSize = response.expectedContentLength + self.currentSize;
    
    
    // 第一次下载才创建空文件,避免恢复下载覆盖问题
    if (self.currentSize == 0) {
        // 创建空的文件
        [[NSFileManager defaultManager] createFileAtPath:self.fullPath contents:nil attributes:nil];
    }
    // 设置文件句柄 这里要写在括号外部,因为如果取消后,文件句柄被置空了
    self.fileHandle = [NSFileHandle fileHandleForWritingAtPath:self.fullPath];
    [self.fileHandle seekToEndOfFile];
    /*
     NSURLSessionResponseCancel = 0, 取消任务 默认
     NSURLSessionResponseAllow = 1, 继续执行任务
     NSURLSessionResponseBecomeDownload = 2, 转换为下载任务
     NSURLSessionResponseBecomeStream 变为流
     */
    completionHandler(NSURLSessionResponseAllow);
    NSLog(@"%s",__func__);
}


/// 接收到服务器返回数据
/// @param session 会话对象
/// @param dataTask 请求任务
/// @param data 接收到的数据
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    NSLog(@"%s",__func__);
    [self.fileHandle writeData:data];
    
    self.currentSize += data.length;
    NSLog(@"%f",1.0 * self.currentSize / self.totalSize);
    self.progressView.progress = 1.0 * self.currentSize / self.totalSize;
}


/// 请求完成或者请求失败回调
/// @param session 会话对象
/// @param task 请求任务
/// @param error 错误信息
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    NSLog(@"%s",__func__);
    [self.fileHandle closeFile];
    self.fileHandle = nil;
    NSLog(@"%@",self.fullPath);
}

#pragma mark - getter
- (NSURLSessionDataTask *)dataTask {
    if (!_dataTask) {
        //1 创建url
        NSURL *url = [NSURL URLWithString:@"http://www.hitow.net/data/attachment/album/201612/10/205628nppuzll3137siny2.jpg"];
        //2 创建请求对象
        NSMutableURLRequest *requet = [NSMutableURLRequest requestWithURL:url];
        // 设置请求头信息,告诉服务端请求哪一部分数据
        // 获得指定文件路径对应的文件大小
        NSDictionary *fileInfoDict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.fullPath error:nil];
        self.currentSize = [fileInfoDict[@"NSFileSize"] integerValue];
        NSString *range = [NSString stringWithFormat:@"bytes=%zd-",self.currentSize];
        [requet setValue:range forHTTPHeaderField:@"Range"];
        //3 创建session(会话)对象
        /*
         参数1:会话配置
         参数2:代理
         参数3:设置代理在哪个线程执行
         */
        NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
        //4 创建task
        _dataTask = [session dataTaskWithRequest:requet];
    }
    return _dataTask;
}


- (NSString *)fullPath {
    if (!_fullPath) {
        // 获取文件全路径
        NSString *fullPath = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:FileName];
        _fullPath = fullPath;
    }
    return _fullPath;
}

- (void)dealloc {
    // 清理工作 两个方法任选一个 不论是发送post、get 或者请求都需要在请求结束后将session释放掉
    //finishTasksAndInvalidate
    [self.session invalidateAndCancel];
}
@end

7.5 NSURLSession实现文件上传

#import "ViewController.h"
//                  ----WebKitFormBoundaryvMI3CAV0sGUtL8tr
#define Kboundary @"----WebKitFormBoundaryjv0UfA04ED44AhWx"

#define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]

@interface ViewController ()<NSURLSessionDataDelegate>
/** 注释 */
@property (nonatomic, strong) NSURLSession *session;
@end

@implementation ViewController

-(NSURLSession *)session
{
    if (_session == nil) {
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
        
        //是否运行蜂窝访问
        config.allowsCellularAccess = YES;
        config.timeoutIntervalForRequest = 15;
        
        _session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:[NSOperationQueue mainQueue]];
    }
    return _session;
}

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self upload2];
}

-(void)upload
{
    //1.url
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];
    
    //2.创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    //2.1 设置请求方法
    request.HTTPMethod = @"POST";

    //2.2 设请求头信息
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"];
    
    //3.创建会话对象
//    NSURLSession *session = [NSURLSession sharedSession];
    
    //4.创建上传TASK
    /*
     第一个参数:请求对象
     第二个参数:传递是要上传的数据(请求体)
     第三个参数:
     */
   NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromData:[self getBodyData] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
       
       //6.解析
       NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
    }];
    
    //5.执行Task
    [uploadTask resume];
}

-(void)upload2
{
    //1.url
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];
    
    //2.创建请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    
    //2.1 设置请求方法
    request.HTTPMethod = @"POST";
    
    //2.2 设请求头信息
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"];
    
    //3.创建会话对象
    
    //4.创建上传TASK
    /*
     第一个参数:请求对象
     第二个参数:传递是要上传的数据(请求体)
     */
    NSURLSessionUploadTask *uploadTask = [self.session uploadTaskWithRequest:request fromData:[self getBodyData] completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
        
        //6.解析
        NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
    }];
    
    //5.执行Task
    [uploadTask resume];
}

-(NSData *)getBodyData
{
    NSMutableData *fileData = [NSMutableData data];
    //5.1 文件参数
    /*
     --分隔符
     Content-Disposition: form-data; name="file"; filename="Snip20160225_341.png"
     Content-Type: image/png(MIMEType:大类型/小类型)
     空行
     文件参数
     */
    [fileData appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    
    //name:file 服务器规定的参数
    //filename:Snip20160225_341.png 文件保存到服务器上面的名称
    //Content-Type:文件的类型
    [fileData appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"Sss.png\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:[@"Content-Type: image/png" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:KNewLine];
    
    UIImage *image = [UIImage imageNamed:@"Snip20160226_90"];
    //UIImage --->NSData
    NSData *imageData = UIImagePNGRepresentation(image);
    [fileData appendData:imageData];
    [fileData appendData:KNewLine];
    
    //5.2 非文件参数
    /*
     --分隔符
     Content-Disposition: form-data; name="username"
     空行
     123456
     */
    [fileData appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:KNewLine];
    [fileData appendData:[@"123456" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    
    //5.3 结尾标识
    /*
     --分隔符--
     */
    [fileData appendData:[[NSString stringWithFormat:@"--%@--",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    return fileData;
}

#pragma mark ----------------------
#pragma mark NSURLSessionDataDelegate
/*
 *  @param bytesSent                本次发送的数据
 *  @param totalBytesSent           上传完成的数据大小
 *  @param totalBytesExpectedToSend 文件的总大小
 */
-(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    NSLog(@"%f",1.0 *totalBytesSent / totalBytesExpectedToSend);
}
@end
8 NSURLSession配置说明

NSURLSessionConfiguration

NSURLSessionConfiguration对象用于初始化NSURLSession对象。

展开请求级别中与NSMutableURLRequest相关的可供选择的方案,我们可以看到NSURLSessionConfiguration对于会话如何产生请求,提供了相当多的控制和灵活性。从网络访问性能,到cookie,安全性,缓存策略,自定义协议,启动事件设置,以及用于移动设备优化的几个新属性,你会发现你一直在寻找的,正是NSURLSessionConfiguration。

会话在初始化时复制它们的配置,NSURLSession有一个只读的配置属性,使得该配置对象上的变化对这个会话的政策无效。配置在初始化时被读取一次,之后都是不会变化的。

-构造方法

NSURLSessionConfiguration有三个类构造函数,这很好地说明了NSURLSession是为不同的用例而设计的。

  • "defaultSessionConfiguration"返回标准配置,这实际上与NSURLConnection的网络协议栈是一样的,具有相同的共享NSHTTPCookieStorage,共享NSURLCache和共享NSURLCredentialStorage。
  • "ephemeralSessionConfiguration"返回一个预设配置,没有持久性存储的缓存,Cookie或证书。这对于实现像"秘密浏览"功能的功能来说,是很理想的。
  • "backgroundSessionConfiguration":独特之处在于,它会创建一个后台会话。后台会话不同于常规的,普通的会话,它甚至可以在应用程序挂起,退出,崩溃的情况下运行上传和下载任务。初始化时指定的标识符,被用于向任何可能在进程外恢复后台传输的守护进程提供上下文。

想要查看更多关于后台会话的信息,可以查看WWDC Session 204: “What’s New with Multitasking”

-NSURLSessionConfiguration的属性

NSURLSessionConfiguration拥有20个属性。熟练掌握这些属性的用处,将使应用程序充分利用其网络环境。

最重要的属性:
替代 request 中的 forHTTPHeaderField 告诉服务器有关客户端的附加信息
"HTTPAdditionalHeaders"指定了一组默认的可以设置出站请求的数据头。这对于跨会话共享信息,如内容类型,语言,用户代理,身份认证,是很有用的。

WebDav的身份验证
NSString *userPasswordString = [NSString stringWithFormat:@"%@:%@", user, password];
NSData * userPasswordData = [userPasswordString dataUsingEncoding:NSUTF8StringEncoding];
NSString *base64EncodedCredential = [userPasswordData base64EncodedStringWithOptions:0];
NSString *authString = [NSString stringWithFormat:@"Basic: %@", base64EncodedCredential];

设置客户端类型
NSString *userAgentString = @"iPhone AppleWebKit";

configuration.HTTPAdditionalHeaders = @{@"Accept": @"application/json",
@"Accept-Language": @"en",
@"Authorization": authString,
@"User-Agent": userAgentString};

"networkServiceType(网络服务类型)"对标准的网络流量,网络电话,语音,视频,以及由一个后台进程使用的流量进行了区分。大多数应用程序都不需要设置这个

"allowsCellularAccess(允许蜂窝访问)"和"discretionary(自行决定)"被用于节省通过蜂窝连接的带宽。建议在使用后台传输的时候,使用discretionary属性,而不是allowsCellularAccess属性,因为它会把WiFi和电源可用性考虑在内

"timeoutIntervalForRequest"和"timeoutIntervalForResource"指定了请求以及该资源的超时时间间隔。许多开发人员试图使用timeoutInterval去限制发送请求的总时间,但这误会了timeoutInterval的意思:报文之间的时间。timeoutIntervalForResource实际上提供了整体超时的特性,这应该只用于后台传输,而不是用户实际上可能想要等待的任何东西

"HTTPMaximumConnectionsPerHost"是 Foundation 框架中URL加载系统的一个新的配置选项。它曾经被用于NSURLConnection管理私人连接池。现在有了NSURLSession,开发者可以在需要时限制连接到特定主机的数量

"HTTPShouldUsePipelining"也出现在NSMutableURLRequest,它可以被用于开启HTTP管道,这可以显着降低请求的加载时间,但是由于没有被服务器广泛支持,默认是禁用的

"sessionSendsLaunchEvents" 是另一个新的属性,该属性指定该会话是否应该从后台启动

"connectionProxyDictionary"指定了会话连接中的代理服务器。同样地,大多数面向消费者的应用程序都不需要代理,所以基本上不需要配置这个属性
关于连接代理的更多信息可以在 CFProxySupport Reference 找到。

"Cookie Policies"
-"HTTPCookieStorage" 是被会话使用的cookie存储。默认情况下,NSHTTPCookieShorage的 + sharedHTTPCookieStorage会被使用,这与NSURLConnection是相同的
-"HTTPCookieAcceptPolicy" 决定了该会话应该接受从服务器发出的cookie的条件
-"HTTPShouldSetCookies" 指定了请求是否应该使用会话HTTPCookieStorage的cookie

"Security Policies"
  URLCredentialStorage 是会话使用的证书存储。默认情况下,NSURLCredentialStorage 的+ sharedCredentialStorage 会被使用使用,这与NSURLConnection是相同的

"TLSMaximumSupportedProtocol" 和 "TLSMinimumSupportedProtocol" 确定是否支持SSLProtocol版本的会话

"Caching Policies"
URLCache 是会话使用的缓存。默认情况下,NSURLCache 的 + sharedURLCache 会被使用,这与NSURLConnection是相同的
requestCachePolicy 指定了一个请求的缓存响应应该在什么时候返回。这相当于NSURLRequest 的-cachePolicy方法

"Custom Protocols"
protocolClasses是注册NSURLProtocol类的特定会话数组

9 UIWebView介绍

9.1 什么是UIWebView

  • UIWebView是iOS内置的浏览器控件

  • 系统自带的safari浏览器就是通过UIWebView实现的

  • UIWebView不但能加载远程的网页资源,还能加载绝大部分的常见文件
    1)html\htm
    2)pdf、doc、ppt、txt
    3)mp4
    ……

  • UIWebView常用的加载资源的方法
    - (void)loadRequest:(NSURLRequet *)request

9.2 小案例

#import "ViewController.h"

@interface ViewController ()<UIWebViewDelegate>
@property (weak, nonatomic) IBOutlet UIWebView *webView;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *goBack;
@property (weak, nonatomic) IBOutlet UIBarButtonItem *goForward;

@end

@implementation ViewController

#pragma mark ----------------------
#pragma mark Life Cycle
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    NSURL *url = [NSURL URLWithString:@"http://www.baidu.com"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    
    //加载网页
    [self.webView loadRequest:request];
    
    //设置代理
    self.webView.delegate = self;
}

#pragma mark ----------------------
#pragma mark Events
- (IBAction)goBackBtnClick:(id)sender
{
    
    [self.webView goBack];
}
- (IBAction)goForwardBtnClick:(id)sender
{
    [self.webView goForward];
    
}
- (IBAction)reloadBtnClick:(id)sender
{
    [self.webView reload];
}

#pragma mark ----------------------
#pragma mark UIWebViewDelegate

//即将加载某个请求的时候调用
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
    NSLog(@"%@",request.URL.absoluteString);
    //简单的请求拦截处理
    NSString *strM = request.URL.absoluteString;
    if ([strM containsString:@"360"]) {
        return NO;
    }
    return YES;
}

//1.开始加载网页的时候调用
-(void)webViewDidStartLoad:(UIWebView *)webView
{
    NSLog(@"webViewDidStartLoad");
}

//2.加载完成的时候调用
-(void)webViewDidFinishLoad:(UIWebView *)webView
{
    NSLog(@"webViewDidFinishLoad");
    
    self.goBack.enabled = self.webView.canGoBack;
    self.goForward.enabled = self.webView.canGoForward;
}

//3.加载失败的时候调用
-(void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
    NSLog(@"didFailLoadWithError");
}

@end
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,137评论 6 511
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,824评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,465评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,131评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,140评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,895评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,535评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,435评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,952评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,081评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,210评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,896评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,552评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,089评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,198评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,531评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,209评论 2 357