前言

  • 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. Linux学习笔记 -- Shell 数组

    定义 在Shell的世界里,我们只能定义一维数组. 定义数组的时候不需要指定长度,数组的下标从0开始; Shell 数组用括号来表示,元素用"空格"符号分割开,语法格式如下: sh ...

  2. node.js的了解

    在node环境上面运行js代码,js相当于php,node相当于apache环境 第一步装 node 环境1.从官网下载 dmg 文件安装 2.通过命令行安装 需要用到 homebrew(mac上专门 ...

  3. Keepalived使用小结

    编译安装 1.安装环境 CentOS release 6.4 Based on Linux 2.6.32,安装1.2.9,没问题 在Red Hat Enterprise Linux Server re ...

  4. spring cloud微服务搭建(一)

    martin fowler大神提出微服务的概念后,各种微服务的技术满天飞,现在用的比较多的是spring cloud和阿里的dubbo,由于dubbo 在16年10月份就停止更新了,不过好像前些天又更 ...

  5. 使用jq.lazyload.js,解决设置loading图片的问题

    最近在使用lazyload的时候,遇上一个问题.当对img做宽100%时,就是placeholder的loading图片也会100%宽,这样一般来说loading图片就会变得很大.实在是不能应用到项目 ...

  6. 求输出和为n的所有连续自然数序列

    这是编程之美中的一道题.编程之美中的题目是这样的: 1+2=3 4+5=9 2+3+4=9 等式的左边都是两个或者两个以上的连续自然数相加,那么是不是所有的整数都可以写成这样的形式? 问题1:写个程序 ...

  7. Unity ios、android、pc一键打包(一)

    http://www.cnblogs.com/miaoshujiang/p/5289223.html http://blog.csdn.net/ynnmnm/article/details/36774 ...

  8. Using native JSON

    文介绍了兼容ECMAScript 5 标准的原生JSON对象. 在不支持原生JSON对象的旧版本Firefox中,该如何处理JSON数据.请查看 JSON. 原生JSON对象包含有两个关键方法.JSO ...

  9. 关于recv的返回值

    通常recv有几种返回值 1.==0 表示收到FIN包, 因为FIN包,是状态为标记为FIN的空包,没有携带数据,所以recv的长度为0 2.>0 表示收到了数据, 但是有没有收完,是不知道的 ...

  10. 单机配置tomcat 8 集群

    如何能在集群中的多个节点之间保持数据的一致性,会话(Session)信息是这些数据中最重要的一块. 本文当采用tomcat默认集群配置(<Cluster className="org. ...