高层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:方法来避免输入流的复制。

 

Logo

CSDN联合极客时间,共同打造面向开发者的精品内容学习社区,助力成长!

更多推荐