iOS - NSURLConnection 网络请求
前言
@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,所以使用 <NSURLConnectionDataDelegate> 协议。 当接收到服务器的响应(连通了服务器)时会调用:
- (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) { }
}];
使用 协议 方式
// 遵守协议 <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 { }
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) { }
}];
使用 协议 方式
// 遵守协议 <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 { }
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 }
使用 协议 方式
// 遵守协议 <NSURLConnectionDataDelegate> // 设置网络接口
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]; // 遵守协议 <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 数据请求方式下载,文件输出流存储
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]; // 遵守协议 <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 使用 专用下载 方式
Objective-C
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 断点续传下载
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"]; // 遵守协议 <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 异步下载
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]; // 遵守协议 <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 下载单例封装
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 () <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);
}
}
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];
iOS - NSURLConnection 网络请求的更多相关文章
- ios htttp网络请求cookie的读取与写入(NSHTTPCookieStorage)
当你访问一个网站时,NSURLRequest都会帮你主动记录下来你访问的站点设置的Cookie,如果 Cookie 存在的话,会把这些信息放在 NSHTTPCookieStorage 容器中共享,当你 ...
- iOS - AFNetworking 网络请求
前言 在 iOS 开发中,一般情况下,简单的向某个 Web 站点简单的页面提交请求并获取服务器的响应,用 Xcode 自带的 NSURLConnection 是能胜任的.但是,在绝大部分下我们所需要访 ...
- iOS开发网络请求——大文件的多线程断点下载
iOS开发中网络请求技术已经是移动app必备技术,而网络中文件传输就是其中重点了.网络文件传输对移动客户端而言主要分为文件的上传和下载.作为开发者从技术角度会将文件分为小文件和大文件.小文件因为文件大 ...
- iOS - Alamofire 网络请求
前言 Alamofire 是 Swift 语言的 HTTP 网络开发工具包,相当于 Swift 实现 AFNetworking 版本.当然,AFNetworking 非常稳定,在 Mac OSX 与 ...
- iOS - ASIHTTPRequest 网络请求
前言 使用 iOS SDK 中的 HTTP 网络请求 API,相当的复杂,调用很繁琐,ASIHTTPRequest 就是一个对 CFNetwork API 进行了封装,并且使用起来非常简单的一套 AP ...
- iOS - NSURLSession 网络请求
前言 NS_CLASS_AVAILABLE(NSURLSESSION_AVAILABLE, 7_0) @interface NSURLSession : NSObject @available(iOS ...
- iOS之网络请求NSURLSession剖析
2013年的WWDC大会上,苹果推出了NSURLSession,对Foundation URL加载系统进行了彻底的重构,提供了更丰富的API来处理网络请求,如:支持http2.0协议.直接把数据下载到 ...
- NSURLConnection 网络请求
前言 DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement f ...
- iOS 原生网络请求(推荐使用AFNetWorking库)
. 同步GET请求 //第一步,创建URL NSURL *url = [NSURL URLWithString:@"http://api.hudong.com ...
随机推荐
- (转)java线程安全问题之静态变量、实例变量、局部变量
java多线程编程中,存在很多线程安全问题,至于什么是线程安全呢,给出一个通俗易懂的概念还是蛮难的,如同<java并发编程实践>中所说: 写道 给线程安全下定义比较困难.存在很多种定义,如 ...
- 【jQuery UI 1.8 The User Interface Library for jQuery】.学习笔记.8.Datepicker控件
默认datepicker的安装启用 探索它的配置选项 安装启用一个触发按钮 配置一个供选择的动画 dateFormat选项 简单的国际化 多月datepicker 日期范围选择 datepicker的 ...
- TCP/IP协议 HTTP协议
TCP/IP协议 OSI传统的7层参考模型:物理层,数据链路层,网络层,传输层,话路层,表示层和应用层.而TCP/IP协议并不完全符合这7层参考模型,它只采用了其中的应用层,传输层,网络层和数据链路层 ...
- C#: 获取执行程序所在路径和启动资源管理器
一. 获取执行程序所在路径 1.获取和设置当前目录的完全限定路径. string str = System.Environment.CurrentDirectory; //获取的是主程序目录,线程启 ...
- oracle数据库表空间文件收缩实例
Oracle数据文件收缩实例 数据文件的作用 HWM的基本概念 查看数据文件的使用情况 包括内容:数据文件大小,已经used空间,free空间,hwm信息 select /*+ ordered use ...
- MyBatis 判断条件为等于的问题
在用MyBatis操作数据库的时候相信很多人都用到,当在判断null, 大于,大于等于,小于,小于等于,不等于时估计很多都用到,比较容易实现了,这里就省略了,但唯独判断条件为等于时估计蛮多人遇到坑了, ...
- ACM题目————士兵杀敌(三)
[RMQ算法]:用于当数组过于庞大的时候,查询区间的最大(最小)值. 时间复杂度:O(nlogn),主要时间发费在预处理上,查询只要O(1). 描述 南将军统率着N个士兵,士兵分别编号为1~N,南将军 ...
- java 类加载顺序
1.虚拟机在首次加载Java类时,会对静态初始化块.静态成员变量.静态方法进行一次初始化 2.只有在调用new方法时才会创建类的实例 3.类实例创建过程:按照父子继承关系进行初始化,首先执行父类的初始 ...
- 欧拉回路-Door Man 分类: 图论 POJ 2015-08-06 10:07 4人阅读 评论(0) 收藏
Door Man Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 2476 Accepted: 1001 Description ...
- 动态规划(DP),模拟
题目链接:http://poj.org/problem?id=1088 Memory: 252KTime: 16MSLanguage: C++Result: Accepted 解题报告: 1.lm[i ...