前言

  • DEPRECATED: The NSURLConnection class should no longer be used.
  • NSURLSession is the replacement for NSURLConnection
  • 从 iOS 9 开始 NSURLConnection 的大部分方法被废弃。

1、NSURLConnection

  • NSURLConnection 提供了两种方式来实现连接,一种是同步的另一种是异步的,异步的连接将会创建一个新的线程,
  • 这个线程将会来负责下载的动作。而对于同步连接,在下载连接和处理通讯时,则会阻塞当前调用线程。
  • 许多开发者都会认为同步的连接将会堵塞主线程,其实这种观点是错误的。一个同步的连接是会阻塞调用它的线程。
  • 如果你在主线程中创建一个同步连接,没错,主线程会阻塞。但是如果你并不是从主线程开启的一个同步的连接,它将会类似异步的连接一样。
  • 因此这种情况并不会堵塞你的主线程。事实上,同步和异步的主要区别就是运行 runtime 为会异步连接创建一个线程,而同步连接则不会。
  • 1.1 NSURLConnection 的常用类

    • 1、NSURL:请求地址;
    • 2、NSURLRequest:封装一个请求,保存发给服务器的全部数据,包括一个 NSURL 对象,请求方法、请求头、请求体 ....;
    • 3、NSMutableURLRequest:NSURLRequest 的子类
    • 4、NSURLConnection:负责发送请求,建立客户端和服务器的连接。发送 NSURLRequest 的数据给服务器,并收集来自服务器的响应数据。
  • 1.2 使用 NSURLConnection 发送请求的步骤

    • 1、创建一个 NSURL 对象,设置请求路径(设置请求路径);
    • 2、传入 NSURL 创建一个 NSURLRequest 对象,设置请求头和请求体(创建请求对象);任何 NSURLRequest 默认都是 GET 请求。
    • 3、使用 NSURLConnection 发送 NSURLRequest(发送请求)。
      • 发送同步请求:有返回值。
      • 发送异步请求:没有返回值。
  • 1.3 发送同步请求

    • 使用 NSURLConnection 的 sendSynchronousRequest:returningResponse:error: 类方法,我们可以进行同步请求。
    • 在创建一个同步的网络连接的时候我们需要明白一点,并不是是我们的这个同步连接一定会堵塞我们的主线程,如果这个同步的连接是创建在主线程上的,
    • 那么这种情况下是会堵塞我们的主线程的,其他的情况下是不一定会堵塞我们的主线程的。
    • 例如如果在 GCD 的全局并发队列上初始化了一个同步的连接,其实并不会堵塞我们的主线程的。
  • 1.4 发送异步请求

  • 发送异步请求有两种方式:
    • 1)使用 block 回调:

      NS_AVAILABLE(10_7, 5_0)
      + (void)sendAsynchronousRequest:(NSURLRequest*) request
      queue:(NSOperationQueue*) queue
      completionHandler:(void (^)(NSURLResponse* response, NSData* data, NSError* connectionError)) handler;
      • 创建一个操作,放在 NSOperation 队列中执行,默认是异步执行的。当服务器有返回数据的时候调用会开一条新的线程去发送请求,主线程继续往下走,
      • 当拿到服务器的返回数据的数据的时候再回调 block,执行 block 代码段。这种情况不会卡住主线程。
      • queue 队列的作用是决定这个 block 操作放在哪个线程执行?刷新 UI 界面的操作应该放在主线程执行,不能放在子线程,在子线程处理 UI 相关操作
      • 会出现一些莫名的问题。使用 [NSOperationQueue mainQueue] 返回一个和主线程相关的队列,即主队列,这个 block 操作会被放在主线程中执行。使用
      • [[NSOperationQueue alloc] init] 返回的不是主队列,这个 block 操作不会被放在主线程中执行。
    • 2)代理:

      - (instancetype)initWithRequest:(NSURLRequest *)request delegate:(id)delegate;
      + (NSURLConnection*)connectionWithRequest:(NSURLRequest *)request delegate:(id)delegate;
      • 要监听服务器返回的 data,所以使用 协议。
      • 当接收到服务器的响应(连通了服务器)时会调用:
      - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
      • 当接收到服务器的数据时会调用(可能会被调用多次,每次只传递部分数据,需要拼接接收到的所有数):
      - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
      • 当服务器的数据加载完毕时就会调用:
      - (void)connectionDidFinishLoading:(NSURLConnection *)connection
      • 请求错误(失败)的时候调用(请求超时\断网\没有网\,一般指客户端错误):
      - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
      • NSURLConnection 的代理方法默认都是在主线程上执行的,会对界面产生卡顿。
      • For the connection to work correctly, the calling thread’s run loop must be operating in the default run loop mode.
      • 为了让连接工作正常,调用线程的运行循环必须在默认的运行循环模式下。
      • 如果要让 NSURLConnection 实现在后台线程回调代理方法,需要在后台线程启动 NSURLConnection,并启动后台线程的运行循环,NSURLConnection
      • 执行完毕后,会自动停止后台线程的运行循环。
      • 启动子线程的运行循环方法:
      CFRunLoopRun();
      [[NSRunLoop currentRunLoop] run]; // NSRunLoop 只能启动,没有提供停止的接口
  • 1.5 开始/停止网络连接

    - (void)start;
    - (void)cancel;
    • 创建网络连接后可以不使用 start,系统会自动开始网络连接。
    • 取消一个请求后,连接不在调用代理方法,如果希望再此连接,需要再次创建一个新的网络连接。
  • 1.6 NSURLConnectionDownloadDelegate

    • NSURLConnectionDownloadDelegate 代理方法是为 Newsstand Kit’s(杂志包) 创建的下载服务的,Newsstand 主要在国外使用比较广泛,国内极少。
    • 如果使用 NSURLConnectionDownloadDelegate 代理方法监听下载进度,能够监听到进度,但是找不到下载的文件。

2、NSURLConnection 同步 GET 请求


// 设置网络接口
NSString *urlStr = @"http://192.168.88.200:8080/MJServer/video?type=JSON"; // 设置请求路径
NSURL *url = [NSURL URLWithString:urlStr]; NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 创建同步网络请求
NSData *syncNetData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:NULL error:NULL];

3、NSURLConnection 同步 POST 请求


// 设置网络接口
NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"]; // 创建请求对象
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url]; // 设置请求方式,默认为 GET 请求
urlRequest.HTTPMethod = @"POST"; // 设置请求体(请求参数)
urlRequest.HTTPBody = [@"type=JSON" dataUsingEncoding:NSUTF8StringEncoding]; // 创建同步网络请求
NSData *syncNetData = [NSURLConnection sendSynchronousRequest:urlRequest returningResponse:NULL error:NULL];

4、NSURLConnection 异步 GET 请求

  • 4.1 使用 block 回调方式

    // 设置网络接口
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=XML"]; // 创建请求对象
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 创建异步网络请求
    [NSURLConnection sendAsynchronousRequest:urlRequest
    queue:[NSOperationQueue mainQueue]
    completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (connectionError == nil && data != nil) { }
    }];
  • 4.2 使用 协议 方式

    // 遵守协议 <NSURLConnectionDataDelegate>
    
    @property(nonatomic, retain)NSMutableData *asyncNetData;
    
    // 设置网络接口
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video?type=XML"]; // 创建请求对象
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 创建异步网络请求
    [NSURLConnection connectionWithRequest:urlRequest delegate:self]; // 协议方法 // 接收到服务器的响应
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { // 异步下载数据源初始化
    self.asyncNetData = [[NSMutableData alloc] init];
    } // 接收到服务器数据
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 拼接从服务器下载的数据
    [self.asyncNetData appendData:data];
    } // 服务器的数据加载完毕
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection { // 处理从服务器下载的数据
    self.textView.text = [[NSString alloc] initWithData:self.asyncNetData encoding:NSUTF8StringEncoding];
    } // 请求错误
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { }

5、NSURLConnection 异步 POST 请求

  • 5.1 使用 block 回调方式

    // 设置网络接口
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"]; // 创建请求对象
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url]; // 设置请求方式,默认为 GET 请求
    urlRequest.HTTPMethod = @"POST"; // 设置请求体(请求参数)
    urlRequest.HTTPBody = [@"type=XML" dataUsingEncoding:NSUTF8StringEncoding]; // 创建异步网络请求
    [NSURLConnection sendAsynchronousRequest:urlRequest queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (connectionError == nil && data != nil) { }
    }];
  • 5.2 使用 协议 方式

    // 遵守协议 <NSURLConnectionDataDelegate>
    
    // 设置网络接口
    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200:8080/MJServer/video"]; // 创建请求对象
    NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url]; // 设置请求方式,默认为 GET 请求
    urlRequest.HTTPMethod = @"POST"; // 设置请求体(请求参数)
    urlRequest.HTTPBody = [@"type=XML" dataUsingEncoding:NSUTF8StringEncoding]; // 创建异步网络请求
    [NSURLConnection connectionWithRequest:urlRequest delegate:self]; // 协议方法 // 已经发送请求体
    - (void)connection:(NSURLConnection *)connection didSendBodyData:(NSInteger)bytesWritten totalBytesWritten:(NSInteger)totalBytesWritten totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite { } // 接收到服务器的响应
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { } // 接收到服务器数据
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { } // 服务器的数据加载完毕
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection { } // 请求错误
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { }

6、NSURLConnection 文件下载

  • 6.1 获取文件信息


    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"]; NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; // 使用 HEAD 请求方式
    request.HTTPMethod = @"HEAD"; NSURLResponse *response = nil;
    NSError *error = nil; // 使用同步请求方式,后续的下载会依赖于此
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error == nil && response != nil) { // 获取文件大小或名称
    NSLog(@"要下载文件的长度 %tu", response.expectedContentLength);
    }
  • 6.2 使用 GET 数据请求方式下载,文件句柄存储


    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength; // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 遵守协议 <NSURLConnectionDataDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self]; // 协议方法 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"321.mp4"]; // 如果文件不存在,方法不会出错
    [[NSFileManager defaultManager] removeItemAtPath:documentsPath error:NULL]; // 获取数据总大小
    self.expectedContentLength = response.expectedContentLength; self.recvedfileLength = 0;
    } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 将从服务器下载的数据直接写入文件
    [self writeToFile:data]; // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length; // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { } // 将数据写入文件
    - (void)writeToFile:(NSData *)data { /*
    NSFileManager:文件管理器,文件复制,删除,是否存在...操作,类似于在 Finder 中进行的操作
    NSFileHandle :文件 "句柄(指针)" Handle 操纵杆,凡事看到 Handle 单词,表示对前面一个名词(File)的操作,
    对一个文件进行独立的操作。
    */ NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"321.mp4"]; // 打开文件,如果文件不存在,fp == nil
    NSFileHandle *fp = [NSFileHandle fileHandleForWritingAtPath:documentsPath]; // 如果文件不存在
    if (fp == nil) { // 将数据直接写入文件
    [data writeToFile:documentsPath atomically:YES];
    } else { // 将文件指针移动到文件末尾
    [fp seekToEndOfFile]; // 写入数据
    [fp writeData:data]; // 关闭文件,C 语言中有一个默认的约定,凡事打开文件,都必须关闭
    [fp closeFile];
    }
    }
  • 6.3 使用 GET 数据请求方式下载,文件输出流存储


    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength; // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength; // 输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 遵守协议 <NSURLConnectionDataDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self]; // 协议方法 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"312.mp4"]; // 如果文件不存在,方法不会出错
    [[NSFileManager defaultManager] removeItemAtPath:documentsPath error:NULL]; // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:documentsPath append:YES]; // 打开文件流
    [self.fileStream open]; // 获取数据总大小
    self.expectedContentLength = response.expectedContentLength; self.recvedfileLength = 0;
    } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 将数据的 "字节"一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length]; // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length; // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { // 关闭文件流
    [self.fileStream close];
    } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { // 关闭文件流
    [self.fileStream close];
    }
  • 6.4 使用 专用下载 方式


    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 遵守协议 <NSURLConnectionDownloadDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self]; // 协议方法 - (void)connection:(NSURLConnection *)connection didWriteData:(long long)bytesWritten totalBytesWritten:(long long)totalBytesWritten expectedTotalBytes:(long long)expectedTotalBytes { /*
    下载进度: bytesWritten 本次下载子节数
    totalBytesWritten 已经下载字节数
    expectedTotalBytes 总下载字节数(文件总大小)
    */ float progress = (float)totalBytesWritten / expectedTotalBytes;
    NSLog(@"%f", progress);
    } - (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *) destinationURL { /*
    destinationURL 下载保存的路径,下载完成之后,找不到下载的文件。
    */ NSLog(@"%@", destinationURL);
    }
  • 6.5 断点续传下载


    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength; // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength; // 输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream; // 目标目录
    @property (nonatomic, copy) NSString *targetPath; @property (nonatomic, strong) NSURLConnection *conn; NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"]; // 检查服务器上的文件信息
    [self checkServerFileInfoWithURL:url]; // 检查本地文件信息
    long long fileSize = [self checkLocalFileInfo]; // 文件已经下载到本地
    if (fileSize == self.expectedContentLength) { return;
    } // 根据本地文件的长度,从对应 "偏移" 位置开始下载
    [self downloadWithURL:url offset:fileSize]; // 检查服务器上的文件信息
    - (void)checkServerFileInfoWithURL:(NSURL *)url { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"HEAD"; NSURLResponse *response = nil;
    NSError *error = nil; // 发送同步方法
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error == nil && response != nil) { // 文件大小
    self.expectedContentLength = response.expectedContentLength; // 文件名,保存到临时文件夹
    self.targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
    }
    } // 检查本地文件信息
    - (long long)checkLocalFileInfo { long long fileSize = 0; // 检查本地是否存在文件
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.targetPath]) { NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.targetPath error:NULL]; // 获取文件大小
    fileSize = [dict fileSize];
    } // 判断是否比服务器的文件大
    if (fileSize > self.expectedContentLength) { // 删除文件
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    fileSize = 0;
    } return fileSize;
    } // 从断点处开始下载
    - (void)downloadWithURL:(NSURL *)url offset:(long long)offset { // 记录本地文件大小
    self.recvedfileLength = offset; NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:15]; // 一旦设置了 Range,response 的状态码会变成 206
    [urlRequest setValue:[NSString stringWithFormat:@"bytes=%lld-", offset] forHTTPHeaderField:@"Range"]; // 遵守协议 <NSURLConnectionDataDelegate>
    self.conn = [NSURLConnection connectionWithRequest:urlRequest delegate:self]; [self.conn start];
    } // 暂停下载
    - (void)pause1 { [self.conn cancel];
    } // 协议方法
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:self.targetPath append:YES];
    [self.fileStream open];
    } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 将数据的 "字节"一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length]; // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length; // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { [self.fileStream close];
    } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { // 关闭文件流
    [self.fileStream close];
    }
  • 6.6 异步下载


    // 下载文件的总长度
    @property (nonatomic, assign) long long expectedContentLength; // 当前文件大小
    @property (nonatomic, assign) long long recvedfileLength; // 输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream; dispatch_async(dispatch_get_global_queue(0, 0), ^{ NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_02.mp4"];
    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url]; // 遵守协议 <NSURLConnectionDataDelegate>
    [NSURLConnection connectionWithRequest:urlRequest delegate:self]; // 启动运行循环
    CFRunLoopRun();
    }); // 协议方法
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSString *documentsPath = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0] stringByAppendingPathComponent:@"312.mp4"]; // 如果文件不存在,方法不会出错
    [[NSFileManager defaultManager] removeItemAtPath:documentsPath error:NULL]; // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:documentsPath append:YES]; // 打开文件流
    [self.fileStream open]; // 获取数据总大小
    self.expectedContentLength = response.expectedContentLength; self.recvedfileLength = 0;
    } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 将数据的 "字节"一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length]; // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length; // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength;
    } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { // 关闭文件流
    [self.fileStream close];
    } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { // 关闭文件流
    [self.fileStream close];
    }

7、NSURLConnection 下载单例封装

  • 7.1 QDownloaderOperation.h

    @interface QDownloaderOperation : NSOperation
    
    /// 类方法
    + (instancetype)downloaderWithURL:(NSURL *)url
    progress:(void (^)(float progress))progress
    successed:(void (^)(NSString *targetPath))successed
    failed:(void (^)(NSError *error))failed; /// 暂停当前下载
    - (void)pauseDownload; /// 取消当前下载
    - (void)cancelDownload; @end
  • 7.2 QDownloaderOperation.m

    @interface QDownloaderOperation () <NSURLConnectionDataDelegate>
    
    /// 下载文件总长度
    @property (nonatomic, assign) long long expectedContentLength; /// 已下载文件大小
    @property (nonatomic, assign) long long recvedfileLength; /// 下载目标目录
    @property (nonatomic, copy) NSString *targetPath; /// 下载文件输出数据流
    @property (nonatomic, strong) NSOutputStream *fileStream; /// block 属性
    @property (nonatomic, copy) void (^progressBlock)(float);
    @property (nonatomic, copy) void (^successedBlock)(NSString *);
    @property (nonatomic, copy) void (^failedBlock)(NSError *); /// 网络连接属性
    @property (nonatomic, strong) NSURLConnection *conn;
    @property (nonatomic, strong) NSURL *downloadURL; @end + (instancetype)downloaderWithURL:(NSURL *)url progress:(void (^)(float))progress successed:(void (^)(NSString *))successed failed:(void (^)(NSError *))failed { QDownloaderOperation *downloader = [[self alloc] init]; downloader.progressBlock = progress;
    downloader.successedBlock = successed;
    downloader.failedBlock = failed; downloader.downloadURL = url; return downloader;
    } - (void)main {
    @autoreleasepool { // 检查服务器上的文件信息
    [self checkServerFileInfoWithURL:self.downloadURL]; if (self.isCancelled) return; // 检查本地文件信息
    long long fileSize = [self checkLocalFileInfo]; if (fileSize == self.expectedContentLength) { // 下载完成的回调
    if (self.successedBlock) {
    dispatch_async(dispatch_get_main_queue(), ^{
    self.successedBlock(self.targetPath);
    }); // 下载进度的回调
    if (self.progressBlock) {
    self.progressBlock(1.0);
    }
    }
    return;
    } // 根据本地文件的长度,从对应 "偏移" 位置开始下载
    [self downloadWithURL:self.downloadURL offset:fileSize];
    }
    } - (void)pauseDownload { // 取消一个请求,调用此方法后,连接不在调用代理方法
    [self.conn cancel];
    } - (void)cancelDownload { [self.conn cancel];
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    } /// 检查服务器上的文件信息
    - (void)checkServerFileInfoWithURL:(NSURL *)url { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
    request.HTTPMethod = @"HEAD"; NSURLResponse *response = nil;
    NSError *error = nil; // 发送同步方法
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (error == nil && response != nil) { // 文件大小
    self.expectedContentLength = response.expectedContentLength; // 文件名
    self.targetPath = [NSTemporaryDirectory() stringByAppendingPathComponent:response.suggestedFilename];
    }
    } /// 检查本地文件信息 - (long long)checkLocalFileInfo { long long fileSize = 0; // 检查本地是否存在文件
    if ([[NSFileManager defaultManager] fileExistsAtPath:self.targetPath]) {
    NSDictionary *dict = [[NSFileManager defaultManager] attributesOfItemAtPath:self.targetPath error:NULL]; // 获取文件大小
    fileSize = [dict fileSize];
    } // 判断是否比服务器的文件大
    if (fileSize > self.expectedContentLength) { // 删除文件
    [[NSFileManager defaultManager] removeItemAtPath:self.targetPath error:NULL];
    fileSize = 0;
    } return fileSize;
    } /// 从断点处开始下载
    - (void)downloadWithURL:(NSURL *)url offset:(long long)offset { // 记录本地文件大小
    self.recvedfileLength = offset; NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:url cachePolicy:1 timeoutInterval:15]; // 一旦设置了 Range,response 的状态码会变成 206
    [urlRequest setValue:[NSString stringWithFormat:@"bytes=%lld-", offset] forHTTPHeaderField:@"Range"]; // 遵守协议 <NSURLConnectionDataDelegate>
    self.conn = [NSURLConnection connectionWithRequest:urlRequest delegate:self]; [self.conn start]; // 开启子线程运行循环
    CFRunLoopRun();
    } /// 协议方法 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { // 以拼接的方式实例化文件流
    self.fileStream = [[NSOutputStream alloc] initToFileAtPath:self.targetPath append:YES]; // 打开文件流
    [self.fileStream open];
    } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { // 将数据的 "字节" 一次性写入文件流,并且指定数据长度
    [self.fileStream write:data.bytes maxLength:data.length]; // 计算当前数据下载完成的大小
    self.recvedfileLength += data.length; // 计算下载进度
    float progress = (float)self.recvedfileLength / self.expectedContentLength; // 进度的回调
    if (self.progressBlock) {
    self.progressBlock(progress);
    }
    } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { // 关闭文件流
    [self.fileStream close]; // 完成的回调
    if (self.successedBlock) { // 主线程回调
    dispatch_async(dispatch_get_main_queue(), ^{
    self.successedBlock(self.targetPath);
    });
    }
    } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { // 关闭文件流
    [self.fileStream close]; // 失败的回调
    if (self.failedBlock) {
    self.failedBlock(error);
    }
    }
  • 7.3 QDownloaderManager.h

    @interface QDownloaderManager : NSObject
    
    /// 单例
    + (instancetype)sharedManager; /// 开始下载
    - (void)downloadWithURL:(NSURL *)url progress:(void (^)(float progress))progress successed:(void (^)(NSString *targetPath))successed failed:(void (^)(NSError *error))failed; /// 暂停下载
    - (void)pauseWithURL:(NSURL *)url; /// 取消下载
    - (void)cancelWithURL:(NSURL *)url; @end
  • 7.4 QDownloaderManager.m

    @interface QDownloaderManager ()
    
    /// 下载操作缓冲池
    @property (nonatomic, strong) NSMutableDictionary *downloadCache; /// 下载操作队列
    @property (nonatomic, strong) NSOperationQueue *downloadQueue; @end + (instancetype)sharedManager {
    static id instance; static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    instance = [[self alloc] init];
    }); return instance;
    } - (void)downloadWithURL:(NSURL *)url progress:(void (^)(float))progress successed:(void (^)(NSString *))successed failed:(void (^)(NSError *))failed { // 检查缓冲池中是否有下载,如果有,直接返回
    if (self.downloadCache[url.absoluteString] != nil) {
    NSLog(@"正在在玩命下载中...");
    return;
    } QDownloaderOperation *downloader = [QDownloaderOperation downloaderWithURL:url progress:progress successed:^(NSString *targetPath) { // 删除下载操作
    [self.downloadCache removeObjectForKey:url.absoluteString]; if (successed != nil) {
    successed(targetPath);
    } } failed:^(NSError *error) { [self.downloadCache removeObjectForKey:url.absoluteString]; if (failed != nil) {
    failed(error);
    }
    }]; // 添加到缓冲池
    [self.downloadCache setObject:downloader forKey:url.absoluteString];
    // 添加到队列
    [self.downloadQueue addOperation:downloader];
    } - (void)pauseWithURL:(NSURL *)url { // 判断缓冲池中是否有对应的下载操作
    QDownloaderOperation *downloader = self.downloadCache[url.absoluteString]; if (downloader != nil) { // 暂停 downloader 内部的 NSURLConnection
    [downloader pauseDownload]; // 给操作发送取消消息 NSOperation
    [downloader cancel]; // 从缓冲池中清除
    [self.downloadCache removeObjectForKey:url.absoluteString];
    }
    } - (void)cancelWithURL:(NSURL *)url { // 判断缓冲池中是否有对应的下载操作
    QDownloaderOperation *downloader = self.downloadCache[url.absoluteString]; if (downloader != nil) { // 取消 downloader 内部的 NSURLConnection
    [downloader cancelDownload]; // 给操作发送取消消息 NSOperation
    [downloader cancel]; // 从缓冲池中清除
    [self.downloadCache removeObjectForKey:url.absoluteString];
    }
    } /// 懒加载 - (NSMutableDictionary *)downloadCache {
    if (_downloadCache == nil) {
    _downloadCache = [NSMutableDictionary dictionary];
    }
    return _downloadCache;
    } - (NSOperationQueue *)downloadQueue {
    if (_downloadQueue == nil) {
    _downloadQueue = [[NSOperationQueue alloc] init]; // 设置最大并发数
    _downloadQueue.maxConcurrentOperationCount = 5;
    }
    return _downloadQueue;
    }
  • 7.5 ViewController.m

    // 下载进度
    @property (weak, nonatomic) IBOutlet UIProgressView *progressView; @property (nonatomic, strong) NSURL *url; // 开始下载 NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_01.mp4"];
    self.url = url; [[QDownloaderManager sharedManager] downloadWithURL:url progress:^(float progress) { dispatch_async(dispatch_get_main_queue(), ^{ self.progressView.progress = progress;
    }); } successed:^(NSString *targetPath) { NSLog(@"%@", targetPath); } failed:^(NSError *error) { NSLog(@"%@", error);
    }]; // 暂停下载 [[QDownloaderManager sharedManager] pauseWithURL:self.url]; // 取消下载 [[QDownloaderManager sharedManager] cancelWithURL:self.url];

NSURLConnection 网络请求的更多相关文章

  1. iOS - NSURLConnection 网络请求

    前言 @interface NSURLConnection : NSObject class NSURLConnection : NSObject DEPRECATED: The NSURLConne ...

  2. iOS - AFNetworking 网络请求

    前言 在 iOS 开发中,一般情况下,简单的向某个 Web 站点简单的页面提交请求并获取服务器的响应,用 Xcode 自带的 NSURLConnection 是能胜任的.但是,在绝大部分下我们所需要访 ...

  3. iOSAFNetworking 网络请求

    前言 在 iOS 开发中,一般情况下,简单的向某个 Web 站点简单的页面提交请求并获取服务器的响应,用 Xcode 自带的 NSURLConnection 是能胜任的.但是,在绝大部分下我们所需要访 ...

  4. CHNetRequest网络请求

    Paste JSON as Code • quicktype 软件的使用 iOS开发:官方自带的JSON使用 JSON 数据解析 XML 数据解析 Plist 数据解析 NetRequest 网络数据 ...

  5. iOS开发——网络篇——HTTP/NSURLConnection(请求、响应)、http响应状态码大全

    一.网络基础 1.基本概念> 为什么要学习网络编程在移动互联网时代,移动应用的特征有几乎所有应用都需要用到网络,比如QQ.微博.网易新闻.优酷.百度地图只有通过网络跟外界进行数据交互.数据更新, ...

  6. iOS NSURLConnection和异步网络请求

    在日常应用中,我们往往使用AFNetworking等第三方库来实现网络请求部分.这篇文章会简要地介绍一下如何使用NSURLConnection来进行异步的网络请求. 我们先看一个小demo - (vo ...

  7. 多线程与网络之NSURLConnection发送请求

    *:first-child { margin-top: 0 !important; } body > *:last-child { margin-bottom: 0 !important; } ...

  8. ios htttp网络请求cookie的读取与写入(NSHTTPCookieStorage)

    当你访问一个网站时,NSURLRequest都会帮你主动记录下来你访问的站点设置的Cookie,如果 Cookie 存在的话,会把这些信息放在 NSHTTPCookieStorage 容器中共享,当你 ...

  9. iOS开发——post异步网络请求封装

    IOS中有许多网络请求的函数,同步的,异步的,通过delegate异步回调的. 在做一个项目的时候,上网看了很多别人的例子,发现都没有一个简单的,方便的异步请求的封装例子.我这里要给出的封装代码,是异 ...

随机推荐

  1. kali下启动postgresql

    1.service postgresql start 2.su postgres 3.psql

  2. mongdb与mysql的联系和区别

    与关系型数据库相比,MongoDB的优点:①弱一致性(最终一致),更能保证用户的访问速度举例来说,在传统的关系型数据库中,一个COUNT类型的操作会锁定数据集,这样可以保证得到“当前”情况下的精确值. ...

  3. 第六章 深入分析ClassLoader工作机制

    补充(非书中): Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件).类加载器负责读取Java字节代码,并转换成 java.lan ...

  4. U-boot分析与移植(1)----bootloader分析

    一.Boot Loader 概念 就是在操作系统内核运行之前运行的一段小程序.通过这段小程序,我们可以初始化硬件设备.建立内存空间的映射图,从而将系统的软硬件环境带到一个合适的状态,以便为最终调用操作 ...

  5. web新特性 之 WebSocket

    详情参见:你真的了解WebSocket吗?     WebSocket系列教程   HTML5新特性之WebSocket WebSocket协议是基于TCP的一种新的协议.WebSocket最初在HT ...

  6. linux 动态静态库

    库从本质上来说是一种可执行代码的二进制格式,可以被载入内存中执行.库分静态库和动态库两种.  1  静态库和动态库的区别1.1. 静态函数库    (1)静态函数库的名字一般是lib[name].a( ...

  7. Tornado 高并发源码分析之六---异步编程的几种实现方式

    方式一:通过线程池或者进程池 导入库futures是python3自带的库,如果是python2,需要pip安装future这个库 备注:进程池和线程池写法相同 from concurrent.fut ...

  8. Java占位符替换工具类

    import java.util.HashMap; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFact ...

  9. Python模块及其导入

    一.模块 1.模块的定义: 为了编写可维护的代码,我们把很多函数分组,分别放到不同的文件里,这样,每个文件包含的代码就相对较少, 很多编程语言都采用这种组织代码的方式.在Python中,一个.py文件 ...

  10. 主机不能访问虚拟机中的web服务【解决方案】

    百度了其它一些方法都不行,最后实在没辙,关了windows防火墙和Linux防火墙,居然能够访问了,我服. 总结一下,原来是Red Hat Linux 6.0防火墙没有开启端口80,开启的方法为(老版 ...