IOS网络访问主要建立在http协议上
IOS提供了几个重要的对象完成http请求响应
NSURLRequest:代表一个请求,通过NSURLRequest可以设置请求的URL地址以及缓存策略
NSMutableURLRequest:NSURLRequest的子类,可以方便地设置请求头的各种信息以及请求方式
NSURLConnection:网络访问对象,可以通过同步或者异步的方式发送请求
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //访问的URL NSString *str = @"http://www.baidu.com/"; //对URL进行编码 str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; //生成NSURLRequest,timeoutInterval表示超时时间,默认60秒 NSURLRequest *request = [[NSURLRequest alloc] initWithURL:[NSURL URLWithString:str] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60]; NSURLResponse *resp = [[NSURLResponse alloc] init]; NSError *error = [[NSError alloc] init]; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&resp error:&error]; NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); }
上面代码访问了百度首页,如果URL中包含中文字符必须先经过编码
其中NSURLRequest初始化方法中的cachePolicy表示缓存策略,它可以设置成以下的值
NSURLRequestUseProtocolCachePolicy 根据response中的Cache-Control字段判断缓存是否有效,如果缓存有效则使用缓存数据否则重新从服务器请求
NSURLRequestReloadIgnoringLocalCacheData 不使用缓存,直接从服务器请求数据
NSURLRequestReloadIgnoringCacheData = NSURLRequestReloadIgnoringLocalCacheData 以上一个值意义相同
NSURLRequestReturnCacheDataElseLoad 如果缓存有效则使用缓存否则从服务器请求数据
NSURLRequestReturnCacheDataDontLoad 如果缓存有效则使用缓存否则请求失败
异步请求
sendSynchronousRequest使用的是同步方法访问网络,会阻塞主线程,一般我们常用异步方式访问网络,可以通过多种方式实现异步请求
首先我们写个简短的服务器代码,接收请求参数然后输出
//iostest.php <?php $value1 = $_POST["name"]; $value2 = $_POST["gender"]; echo $value1.'|'.$value2; ?>
方式一:我们可以使用多线程,将上面同步请求的代码放在多线程环境下,这样就不会阻塞主线程
- (void)viewDidLoad { [super viewDidLoad]; [self performSelectorInBackground:@selector(connection) withObject:nil]; } -(void)connection { //访问的URL NSString *str = @"http://localhost/iostest.php"; //对URL进行编码 str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; //设置url request.URL = [NSURL URLWithString:str]; //缓存策略 request.cachePolicy = 0; //设置请求方式 request.HTTPMethod = @"POST"; //设置请求体 request.HTTPBody = [@"name=zanglitao&gender=male" dataUsingEncoding:NSUTF8StringEncoding]; NSURLResponse *resp = [[NSURLResponse alloc] init]; NSError *error = [[NSError alloc] init]; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&resp error:&error]; NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); }
输出:zanglitao|male
方式二:使用NSURLConnection的类方法sendAsynchronousRequest
- (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //访问的URL NSString *str = @"http://localhost/iostest.php"; //对URL进行编码 str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init]; //设置url request.URL = [NSURL URLWithString:str]; //缓存策略 request.cachePolicy = 0; //设置请求方式 request.HTTPMethod = @"POST"; //设置请求体 request.HTTPBody = [@"name=zanglitao&gender=male" dataUsingEncoding:NSUTF8StringEncoding]; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); }]; }
方式三:通过NSURLConnectionDataDelegate代理实现,这种方式较为繁琐,优点是可以获得请求进度
@interface ZLTViewController () { NSMutableData *_data; int _length; } @end @implementation ZLTViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. //访问的URL NSString *str = @"http://g.hiphotos.baidu.com/image/h%3D800%3Bcrop%3D0%2C0%2C1280%2C800/sign=eaef18bb830a19d8d403890503c1e1f9/bd315c6034a85edfabe1bf294a540923dd54754e.jpg"; //对URL进行编码 str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:str]]; //初始化NSURLConnection时设置代理对象 NSURLConnection *conn = [[NSURLConnection alloc] initWithRequest:request delegate:self]; //开始请求 [conn start]; } //获得请求 - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { _data = [[NSMutableData alloc] init]; NSHTTPURLResponse *resp = (NSHTTPURLResponse *)response; //取得响应头 NSDictionary *headFields = [resp allHeaderFields];//取得响应数据长度 _length = [headFields[@"Content-Length"] integerValue]; NSLog(@"请求数据大小:%ld",_length); } - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data { [_data appendData:data]; float percent = (float)[_data length]/_length * 100; NSLog(@"当前进度:%f",percent); } - (void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"请求结束"); } - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error { NSLog(@"请求失败"); }
以上代码访问了互联网的一张图片,当获得响应时didReceiveResponse会被触发,我们可以在这里通过Content-Length字段获取响应数据的大小,然后接收数据时didReceiveData会不断被调用
输出:
请求数据大小:6542
当前进度:59.202080
当前进度:79.807404
当前进度:100.000000
请求结束
下载文件
当文件很大的时候我们使用NSURLConnection直接下载是不现实的,常用方法是每次请求一小段数据,一次一次的下载
请求头中有个参数Range可以用来控制访问的数据的范围,比如100个字节的数据,我们可以通过设定Range:Bytes=20-80 下载20到80的字节(使用Range我们也可以实现多线程下载)
除了GET和POST,NSURLRequest还提供了HEAD的请求方式,可以仅请求头信息来获取数据的长度
注意:NSURLRequest不能使用缓存,因为我们会多次请求相同的url,如果使用了缓存每次都会请求第一次请求的数据
@interface ZLTViewController () { //储存请求来的数据 NSMutableData *_data; //数据总长度 int _length; } @end @implementation ZLTViewController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. _data = [NSMutableData data]; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self download]; }); } //下载 -(void)download { long long length = [self getFileTotalLength]; long long start = 0; long long end = 0; while (start < length) { end = start + 1024 - 1; if (end >= length) { end = length - 1; } [self downFilefrom:start end:end]; NSLog(@"%lld,%lld,%lld",length,start,end); start += 1024; } __weak UIView *weakView = self.view; dispatch_async(dispatch_get_main_queue(), ^{ UIImage *image = [UIImage imageWithData:_data]; UIImageView *imageview = [[UIImageView alloc] initWithImage:image]; [weakView addSubview:imageview]; }); } //获得文件url -(NSURL *)getURL { NSString *urlStr = @"http://g.hiphotos.baidu.com/image/pic/item/8d5494eef01f3a290bed8dc49a25bc315c607c96.jpg"; urlStr = [urlStr stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]; return [NSURL URLWithString:urlStr]; } //获取文件大小 - (long long)getFileTotalLength { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[self getURL]]; request.HTTPMethod = @"HEAD"; NSURLResponse *response = [[NSURLResponse alloc] init]; NSError *error = [[NSError alloc] init]; [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; return response.expectedContentLength; } //下载指定区域的文件 - (void)downFilefrom:(long long)start end:(long long)end { NSString *range = [NSString stringWithFormat:@"Bytes=%lld-%lld",start,end]; //缓存策略需要使用NSURLRequestReloadIgnoringCacheData,否则每次都会请求第一次请求的数据(0-1023) NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[self getURL] cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:5.0f]; [request setValue:range forHTTPHeaderField:@"Range"]; NSURLResponse *response = [[NSURLResponse alloc] init]; NSError *error = [[NSError alloc] init]; NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; //数据写入文件 [self appendDataToFile:data]; //数据写入NSData [_data appendData:data]; } -(void)appendDataToFile:(NSData *)data { NSString *url = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject]; url = [url stringByAppendingPathComponent:@"1.jpg"]; NSFileHandle *handle = [NSFileHandle fileHandleForWritingAtPath:url]; if (handle) { [handle seekToEndOfFile]; [handle writeData:data]; [handle closeFile]; } else { [data writeToFile:url atomically:YES]; } } @end
上传文件
首先我们看看使用浏览器上传文件时请求头
这里有两个关键的字段:Content-Length和Content-Type
Content-Length表示请求体字节长度,Content-Type前面是固定格式multipart/form-data; boundary=---------------------------,后面那串数字由浏览器随机生成最为boundary,我们也可以指定为任意的字符串值
请求体:
请求体开始是--加上请求头中boundary=后面的内容,然后跟着一个 换行
第二行中name="file"表示表单中文件使用了file这个name(所以服务器需要通过file接收数据) filename指定了上传文件的文件名,然后跟着一个 换行
第三行表示上传文件的MIMETYPE,最后跟着两个 (如果漏了一个 会上传失败)
第五行开始是文件的内容,文件结束后跟着一个 (最容易遗忘的一个 )
最后是-- + 请求头中boundary=后面的内容 + --,不要忘了后面还有两个
所以我们写代码时主要注意点在两处,一个是设置请求头的两个参数,一个是请求体的拼接,具体代码如下
//自定义一个boundary #define boundary @"zanglitao" @interface ZViewController() @end @implementation ZViewController -(void)viewDidLoad { [super viewDidLoad]; [self upload]; } -(void)upload { NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:@"http://localhost/iostest.php"]]; //必须使用POST request.HTTPMethod = @"POST"; NSData *data = [self getDataBody]; //设置请求头 [request setValue:[NSString stringWithFormat:@"%d",data.length] forHTTPHeaderField:@"Content-Length"]; [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",boundary] forHTTPHeaderField:@"Content-Type"]; //设置请求体 request.HTTPBody = data; [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (connectionError) { NSLog(@"%@",[connectionError localizedDescription]); } else { NSLog(@"%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]); } }]; } //获取请求体内容 -(NSData *)getDataBody { NSMutableData *data = [NSMutableData data]; NSString *top = [NSString stringWithFormat:@"--%@ Content-Disposition: form-data; name="file"; filename="1.png" Content-Type: image/png ",boundary]; NSString *bottom = [NSString stringWithFormat:@" --%@-- ",boundary]; NSData *content = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"1" ofType:@"png"]]; [data appendData:[top dataUsingEncoding:NSUTF8StringEncoding]]; [data appendData:content]; [data appendData:[bottom dataUsingEncoding:NSUTF8StringEncoding]]; return data; } @end