高层iOS HTTP API
iOS有3个主要的方法可以执行HTTP请求和接收响应:同步、队列式异步、异步。
所有请求类型共用的对象
所有的URL加载请求方法会共用几个对象:NSURL、NSURLRequest、NSURLConnection、NSURLResponse。
1、NSURL
可以指向文件资源,也可以指向网络资源。
NSURL *url = [NSURL URLWithString:mysteryString];
NSData *data = [NSData dataWithContentsOfURL:url];
if (url.port == nil)
{
NSLog(@"Port is nil");
}
else
{
NSLog(@"Port is not nil");
}
2、NSURLRequest
NSURL *url = [NSURL URLWithString:@"https://www.meijubie.com/"];
if (url == nil)
{
NSLog(@"Invalid URL");
return;
}
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:20.0];
if (request == nil)
{
NSLog(@"Invalid Request");
return;
}
要想修改属性的话,需要使用可变版本,URL加载系统会自动装配请求的Content-Length头。
NSURL *url = [NSURL URLWithString:@"https://www.meijubie.com/"];
if (url == nil)
{
NSLog(@"Invalid URL");
return;
}
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
if (request == nil)
{
NSLog(@"Invalid Request");
return;
}
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[@"Post body" dataUsingEncoding:NSUTF8StringEncoding]];
两种方式可以向NSURLRequest提供HTTP体,在内存中(上面)和通过NSInputStream。如果发送诸如照片或视频等大容量内容,那么使用输入流是最佳选择。
NSURL *url = [NSURL URLWithString:@"https://www.meijubie.com/"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
NSString *srcFilePath = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"flower.wov"];
NSInputStream *inStream = [NSInputStream inputStreamWithFileAtPath:srcFilePath];
[request setHTTPBodyStream:inStream];
[request setHTTPMethod:@"POST"];
3、NSURLConnection
是URL加载系统活动的中心,接口只有:初始化、开启、取消连接。
4、NSURLResponse
同步请求
NSURL *url = [NSURL URLWithString:@"https://www.meijubie.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30.0];
NSHTTPURLResponse *response;
NSError *error = nil;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if (error != nil)
{
NSLog(@"Error on load = %@", [error localizedDescription]);
return;
}
if ([response isKindOfClass:[NSHTTPURLResponse class]])
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode != 200)
{
return;
}
}
NSDictionary *dictionary = [XMLReader dictionaryForXMLData:data error:&error];
NSLog(@"feed = %@", dictionary);
NSArray *entries = [self getEntriesArray:dictionary];
队列式异步请求
NSURL *url = [NSURL URLWithString:@"https://www.meijubie.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30.0];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[NSURLConnection sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError)
{
if (connectionError != nil)
{
NSLog(@"Error on load = %@", [connectionError localizedDescription]);
}
else
{
if ([response isKindOfClass:[NSHTTPURLResponse class]])
{
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if (httpResponse.statusCode != 200)
{
return;
}
}
}
}];
NSDictionary *dictionary = [XMLReader dictionaryForXMLData:data error:&error];
NSLog(@"feed = %@", dictionary);
NSArray *entries = [self getEntriesArray:dictionary];
if ([self.delegate respondsToSelector:@selector(setVideo:)])
{
dispatch_async(dispatch_get_main_queue(), ^
{
[self.delegate setVideo:entries];
});
}
队列式异步请求的最佳实践
- 数据不能超出应用的内存
- 使用单一的队列
- 验证错误和状态码
- 不能进行验证
- 不能提供进度条
- 不能通过流解析器渐进解析响应数据
- 不能取消
异步请求
- (IBAction)doSyncRequest:(id)sender
{
NSURL *url = [NSURL URLWithString:@"https://www.meijubie.com/"];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalAndRemoteCacheData timeoutInterval:30.0];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];
[conn start];
}
#pragma mark -接收到HTTP重定向响应
-(NSURLRequest *)connection:(NSURLConnection *)connection
willSendRequest:(NSURLRequest *)request
redirectResponse:(NSURLResponse *)response
{
return request;
}
#pragma mark -接收到了响应,会被反复调用
-(void)connection:(NSURLConnection *)connection
didReceiveResponse:(nonnull NSURLResponse *)response
{
NSLog(@"Received response from request to url %@", @"https://www.meijubie.com/");
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
NSLog(@"All headers = %@", [httpResponse allHeaderFields]);
if (httpResponse.statusCode != 200)
{
[connection cancel];
return;
}
//下载进度视图会进行更新。
}
#pragma mark - 接收到部分或全部响应体
-(void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
{
totalDownloaded += [data length];
NSLog(@"Received %ld of %ld (%f%%) bytes of data for URL %@", totalDownloaded, downloadSize, ((double)totalDownloaded / (double)downloadSize) * 100.0, @"https://www.meijubie.com/");
[progressView addAmountDownloaded:[data length]];
[_outputHandle writeData:data];
}
/**
当连接失败时
*/
-(void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error
{
NSLog(@"Load failed with error %@", [error localizedDescription]);
NSFileManager *fm = [NSFileManager defaultManager];
if (_tempFile != nil)
{
[_outputHandle closeFile];
NSError *error;
[fm removeItemAtPath:_tempFile error:&error];
}
if (downloadSize != 0L)
{
[progressView addAmountToDownload:-downloadSize];
[progressView addAmountDownloaded:-totalDownloaded];
}
}
/**
当整个请求完成加载并且接收到的所有数据都被传递给委托后。
*/
-(void)connectionDidFinishLoading:(NSURLConnection *)connection
{
[_outputHandle closeFile];
NSFileManager *fm = [NSFileManager defaultManager];
NSError *error;
[fm moveItemAtPath:_tempFile toPath:_targetFile error:&error];
[[NSNotificationCenter defaultCenter] postNotificationName:kDownloadComplete object:nil userInfo:nil];
}
/**
由于出现错误或是认证等原因需要重新传递请求体。
*/
-(NSInputStream *)connection:(NSURLConnection *)connection
needNewBodyStream:(NSURLRequest *)request
{
}
/**
提供了上传进度信息。在不确定的时间间隔内调用。
*/
-(void)connection:(NSURLConnection *)connection
didSendBodyData:(NSInteger)bytesWritten
totalBytesWritten:(NSInteger)totalBytesWritten
totalBytesExpectedToWrite:(NSInteger)totalBytesExpectedToWrite
{
}
/**
检测与修改协议控制器所缓存的响应。
*/
-(NSCachedURLResponse *)connection:(NSURLConnection *)connection
willCacheResponse:(NSCachedURLResponse *)cachedResponse
{
}
异步请求与运行循环
如果在后台线程上发出了异步请求,还需要确定线程是有运行循环还是使用了别的运行循环。
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request
delegate:self
startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop mainRunLoop] forMode:NSDefaultRunLoopMode];
[connection start];
异步请求的最佳实践:
- 大的上传或下载;
- 需要认证;
- 提供进度反馈;
- 在后台线程使用异步请求,需要提供一个运行循环;
- 可以在后台线程的请求队列轻松调度和完成的简单请求,不需要使用异步;
- 如果使用输入流上传数据,请实现connection:newBodyStream:方法来避免输入流的复制。