博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
iOS - NSURLConnection 网络请求
阅读量:5115 次
发布时间:2019-06-13

本文共 34013 字,大约阅读时间需要 113 分钟。

前言

@interface NSURLConnection : NSObject    class NSURLConnection : NSObject        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 请求

  • Objective-C

    // 设置网络接口    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];
  • Swift

    // 设置网络接口    let urlStr = "http://192.168.88.200:8080/MJServer/video?type=JSON"    // 设置请求路径    let url = NSURL(string: urlStr!)    let urlRequest = NSURLRequest(URL: url!)    // 创建同步网络请求    let syncNetData = try? NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil)

3、NSURLConnection 同步 POST 请求

  • Objective-C

    // 设置网络接口    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];
  • Swift

    // 设置网络接口    let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video")    // 创建请求对象    let urlRequest = NSMutableURLRequest(URL: url!)    // 设置请求方式,默认为 GET 请求    urlRequest.HTTPMethod = "POST";    // 设置请求体(请求参数)    urlRequest.HTTPBody = "type=JSON".dataUsingEncoding(NSUTF8StringEncoding)    // 创建同步网络请求    let syncNetData = try? NSURLConnection.sendSynchronousRequest(urlRequest, returningResponse: nil)

4、NSURLConnection 异步 GET 请求

  • Objective-C

    • 使用 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) {        }    }];
    • 使用 协议 方式

      // 遵守协议 
      @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 { }
  • Swift

    • 使用 闭包 方式

      // 设置网络接口    let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video?type=XML")    // 创建请求对象    let urlRequest = NSURLRequest(URL: url!)    // 创建异步网络请求    NSURLConnection.sendAsynchronousRequest(urlRequest,                                       queue: NSOperationQueue.mainQueue())                                            { (response:NSURLResponse?, data:NSData?, connectionError:NSError?) -> Void in        if connectionError == nil {            // 服务器的数据加载完毕,处理从服务器下载的数据        }    }
    • 使用 协议 方式

      // 遵守协议 NSURLConnectionDataDelegate    // 设置网络接口    let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video?type=XML")    // 创建请求对象    let urlRequest = NSURLRequest(URL: url!)    // 创建异步网络请求    NSURLConnection(request: urlRequest, delegate: self)    // 协议方法    // 接收到服务器的响应    func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {        // 异步下载数据源初始化        self.asyncNetData = NSMutableData()    }    // 接收到服务器数据    func connection(connection: NSURLConnection, didReceiveData data: NSData) {        // 拼接从服务器下载的数据        self.asyncNetData?.appendData(data)    }    // 服务器的数据加载完毕    func connectionDidFinishLoading(connection: NSURLConnection) {        // 处理从服务器下载的数据        self.textView.text = NSString(data: self.asyncNetData!, encoding: NSUTF8StringEncoding) as! String    }    // 请求错误    func connection(connection: NSURLConnection, didFailWithError error: NSError) {    }

5、NSURLConnection 异步 POST 请求

  • Objective-C

    • 使用 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) {        }    }];
    • 使用 协议 方式

      // 遵守协议 
      // 设置网络接口 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 { }
  • Swift

    • 使用 闭包 回调方式

      // 设置网络接口    let url = NSURL(string: "http://192.168.88.200:8080/MJServer/video")    // 创建请求对象    let urlRequest = NSMutableURLRequest(URL: url!)    // 设置请求方式,默认为 GET 请求    urlRequest.HTTPMethod = "POST"    // 设置请求体(请求参数)    urlRequest.HTTPBody = "type=XML".dataUsingEncoding(NSUTF8StringEncoding)    // 创建异步网络请求    NSURLConnection.sendAsynchronousRequest(urlRequest,                                       queue: NSOperationQueue.mainQueue())                                            { (response:NSURLResponse?, data:NSData?, connectionError:NSError?) in    }
    • 使用 协议 方式

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

6、NSURLConnection 文件下载

6.1 获取文件信息

  • Objective-C

    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 数据请求方式下载,文件句柄存储

  • Objective-C

    // 下载文件的总长度    @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];    // 遵守协议 
    [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 数据请求方式下载,文件输出流存储

  • Objective-C

    // 下载文件的总长度    @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];    // 遵守协议 
    [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 使用 专用下载 方式

  • Objective-C

    NSURL *url = [NSURL URLWithString:@"http://192.168.88.200/download/file/minion_01.mp4"];    NSURLRequest *urlRequest = [NSURLRequest requestWithURL:url];    // 遵守协议 
    [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 断点续传下载

  • Objective-C

    // 下载文件的总长度    @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"];        // 遵守协议 
    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 异步下载

  • Objective-C

    // 下载文件的总长度    @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];        // 遵守协议 
    [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 下载单例封装

  • 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
  • QDownloaderOperation.m

    @interface QDownloaderOperation () 
    /// 下载文件总长度 @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"]; // 遵守协议
    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); } }
  • 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
  • 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;    }
  • 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];

转载于:https://www.cnblogs.com/QianChia/p/5766535.html

你可能感兴趣的文章
java面试题
查看>>
提高码力专题(未完待续)
查看>>
IOS第17天(3,Quartz2D画板和画线)
查看>>
pair的例子
查看>>
前端框架性能对比
查看>>
@property中 retain 详解
查看>>
java8 stream初试,map排序,list去重,统计重复元素个数,获取map的key集合和value集合...
查看>>
Python爬虫个人记录(四)利用Python在豆瓣上写一篇日记
查看>>
jdk8 Function
查看>>
第二次作业
查看>>
迷茫中的自己
查看>>
burp suite 的intruder 四种攻击方式
查看>>
机器学习----人脸对齐的算法-ASM.AAM..CLM.SDM
查看>>
自定义文本选中样式
查看>>
python3 字符串/列表/元组(str/list/tuple)相互转换方法及join()函数的使用
查看>>
uva 387 A Puzzling Problem (回溯)
查看>>
ubuntu16.04+opencv3.1配置
查看>>
Longest Palindromic Substring
查看>>
Linux查看实时网卡流量的几种方式
查看>>
Spring Boot初识(2)- Spring Boot整合Mybaties
查看>>