NSURLConnection

先了解一些iOS开发中网络开发相关的常用类

NSURL

请求地址

NSURLRequest

一个NSURLRequest对象就代表一个请求,它包含的信息有:

  • 一个NSURL对象
  • 请求方法、请求头、请求体
  • 请求超时
  • ...

NSMutableURLRequest

NSURLRequest的子类,内容可变

NSURLConnection(目前已弃用)

  • 负责发送请求,建立客户端和服务器的连接
  • 发送数据给服务器,并收集来自服务器的响应数据

使用NSURLConnection发送请求的步骤很简单

(1)创建一个NSURL对象,设置请求路径(设置请求路径)

(2)传入NSURL创建一个NSURLRequest对象,设置请求头和请求体(创建请求对象)

(3)使用NSURLConnection发送NSURLRequest(发送请求)

请求实例

请求:请求头(NSURLRequest默认包含) + 请求体(Get没有请求体)
响应:相应头(真实类型->NSHTTPURLResponse)+ 响应体(要解析的数据)

发送同步Get请求

- (void)httpGetSynchronous
{
    //GET 没有请求体
    //确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];

    //创建请求对象(无需设置请求头,会使用默认的请求头)
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //发送请求
    //NSURLResponse *response = nil;
    //真实类型其实是:
    NSHTTPURLResponse *response = nil;

    /* 同步Http请求,使用这个方法会阻塞当前线程
     * 请求服务器返回相应体信息 NSData
     * 参数1:请求对象
     * 参数2:相应头信息,用来接收服务器返回信息,传的是地址
     * 参数3:错误信息
     */
    NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];

    NSLog(@"服务器返回的信息为:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    NSLog(@"服务器返回的状态为:%ld",response.statusCode);

    /*
     2016-12-09 16:04:31.538219 NSURLConnectionTest[6174:2208758] 服务器返回的信息为:{"success":"登录成功"}
     2016-12-09 16:04:31.538430 NSURLConnectionTest[6174:2208758] 服务器返回的状态为:200
    */
}

发送异步Get请求

- (void)httpGetAsynchronous
{
    //GET 没有请求体
    //确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];

    //创建请求对象(无需设置请求头,会使用默认的请求头)
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    /* 异步Http请求,使用这个方法会在子线程中执行
     * 参数1:请求对象
     * 参数2:队列,决定代码块调用线程
     * 参数3:请求完成(成功、失败都算完成)后的回调
     *  response 相应头
     *  data 相应体
     *  connectionError 错误信息
     */
    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {

        NSLog(@"服务器返回的信息为:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

        NSHTTPURLResponse *httpRespose = (NSHTTPURLResponse*)response;
        NSLog(@"服务器返回的状态为:%ld",httpRespose.statusCode);

    }];

    //NSLog(@"服务器返回的信息为:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

    //NSLog(@"服务器返回的状态为:%ld",response.statusCode);

    /*
     2016-12-09 16:04:31.538219 NSURLConnectionTest[6174:2208758] 服务器返回的信息为:{"success":"登录成功"}
     2016-12-09 16:04:31.538430 NSURLConnectionTest[6174:2208758] 服务器返回的状态为:200
     */
}

通过代理发送Get请求

@interface ViewController () <NSURLConnectionDataDelegate>//遵守NSURLConnectionDataDelegate协议,注意,不是NSURLConnectionDelegate

@property(strong,nonatomic)NSMutableData *reciveData;

@end

@implementation ViewController

- (NSMutableData *)reciveData
{
    if(_reciveData == nil)
    {
        _reciveData = [NSMutableData data];
    }
    return _reciveData;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self httpGetWithDelegate];
}

- (void)httpGetWithDelegate
{
    //GET 没有请求体
    //确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login?username=520it&pwd=520it&type=JSON"];

    //创建请求对象(无需设置请求头,会使用默认的请求头)
    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    //设置代理的方法有多种:
    //1.类方法,设置代理并发送请求
    //[NSURLConnection connectionWithRequest:request delegate:self];

    //2.初始化方法,设置代理并发送请求
    //[[NSURLConnection alloc]initWithRequest:request delegate:self];

    //3.初始化方法,设置代理,只有当startImmediately为YES才会发送请求
    [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:YES];

    //如果startImmediately设置为NO,要发送请求还需调用:
    //[connection start];

    //如果要取消请求调用:
    //[connection cancel];
}

//响应头
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSLog(@"%s",__func__);

    NSHTTPURLResponse *httpRespose = (NSHTTPURLResponse*)response;
    NSLog(@"服务器返回的状态为:%ld",httpRespose.statusCode);
}

//响应体
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    NSLog(@"%s",__func__);

    [self.reciveData appendData:data];//拼接数据,如果返回的数据过大,这个方法会调用多次,所以需要一个变量来拼接数据
}

//错误信息
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
    NSLog(@"%s",__func__);
}

//请求完成
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"%s",__func__);

    NSLog(@"服务器返回的信息为:%@",[[NSString alloc] initWithData:self.reciveData encoding:NSUTF8StringEncoding]);
}

@end

以上代码执行的结果:

2016-12-09 17:40:30.571966 NSURLConnectionTest[6215:2229156] -[ViewController connection:didReceiveResponse:]
2016-12-09 17:40:30.572058 NSURLConnectionTest[6215:2229156] 服务器返回的状态为:200
2016-12-09 17:40:30.572186 NSURLConnectionTest[6215:2229156] -[ViewController connection:didReceiveData:]
2016-12-09 17:40:30.572361 NSURLConnectionTest[6215:2229156] -[ViewController connectionDidFinishLoading:]
2016-12-09 17:40:30.572410 NSURLConnectionTest[6215:2229156] 服务器返回的信息为:{"success":"登录成功"}

发送异步Post请求

- (void)httpPost
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login"];

    //创建请求对象(无需设置请求头,会使用默认的请求头)
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //修改请求方法,默认为GET
    request.HTTPMethod = @"POST";//POST必须大写

    //设置请求体信息
    request.HTTPBody = [@"username=520it&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];

    //发送请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
        //输出:2016-12-09 22:10:36.066 NSURLConnectionTest[4950:164558] {"success":"登录成功"}
    }];
}

NSURLRequest的一些方法

请求超时时间,比如10秒

request.timeoutInterval = 10;

重设请求头

比如请求头如下

Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, sdch
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
Connection:keep-alive
Host:120.25.226.186:32812
Referer:http://120.25.226.186:32812/
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36

可以通过如下方法设置请求头,修改头文件键对应的值即可

[request setValue:@"iOS10.1" forHTTPHeaderField:@"User-Agent"];

天朝文字的特殊处理

注意:有个路径中如果含有傻逼的中文字符,还需要做特殊处理

//Get
- (void)httpGetAsynchronousWithTianchaoFont
{
    NSString *str = @"http://120.25.226.186:32812/login2?username=小码哥&pwd=520it&type=JSON";


    NSLog(@"转码前:%@",str);//http://120.25.226.186:32812/login2?username=小码哥&pwd=123&type=JSON

    str = [str stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

    NSLog(@"转码后:%@",str);//http://120.25.226.186:32812/login2?username=%E5%B0%8F%E7%A0%81%E5%93%A5&pwd=123&type=JSON

    NSURL *url = [NSURL URLWithString:str];

    NSURLRequest *request = [NSURLRequest requestWithURL:url];

    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {

        if(connectionError)
        {
            NSLog(@"connectionError: %@",connectionError);
        }

        NSLog(@"服务器返回的信息为:%@",[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);

        NSHTTPURLResponse *httpRespose = (NSHTTPURLResponse*)response;
        NSLog(@"服务器返回的状态为:%ld",httpRespose.statusCode);

    }];
}

//Post
- (void)httpPostWithTianchaoFuckFont
{
    //确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/login2"];//如果url中还有中文字符,也需要转码

    //创建请求对象(无需设置请求头,会使用默认的请求头)
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //修改请求方法,默认为GET
    request.HTTPMethod = @"POST";//POST必须大写

    //设置请求体信息
    request.HTTPBody = [@"username=小码哥&pwd=520it&type=JSON" dataUsingEncoding:NSUTF8StringEncoding];//请求体则不在需要专门转码,这个方法中会处理

    //发送请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
        //输出:2016-12-09 22:10:36.066 NSURLConnectionTest[4950:164558] {"success":"登录成功"}
    }];

    //请求超时时间,比如10秒
    request.timeoutInterval = 10;

    //设置请求头信息
    [request setValue:@"iOS10.1" forHTTPHeaderField:@"User-Agent"];
}

小文件下载的实例1:

+ (void)downloadSmallFile
{
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/images/minion_13.png"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    [NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc]init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
        NSString *fileFullPath = [path stringByAppendingPathComponent:response.suggestedFilename];
        NSLog(@"%@",fileFullPath);

        [data writeToFile:fileFullPath atomically:YES];
    }];
}

小文件下载的实例2:

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>

@property(assign,nonatomic)NSInteger downloadTotal;
@property(strong,nonatomic)NSMutableData *recivedData;

@end

@implementation ViewController

- (NSMutableData *)recivedData
{
    if(!_recivedData)
    {
        _recivedData = [NSMutableData data];
    }
    return _recivedData;
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_03.mp4"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //NSLog(@"%zd",response.expectedContentLength);

    self.downloadTotal = response.expectedContentLength;//表示本次下载的数据长度
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //NSLog(@"%zd",data.length);
    [self.recivedData appendData:data];
    //NSLog(@"%zd----%zd",self.recivedData.length,self.downloadTotal);
    NSLog(@"download progress:%zd",(NSInteger)(1.0 * self.recivedData.length / self.downloadTotal * 100));
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"下载完成");

    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    NSString *fullPath = [path stringByAppendingPathComponent:@"3.mp4"];
    NSLog(@"%@",fullPath);
    [self.recivedData writeToFile:fullPath atomically:YES];
}

- (void)cancelDownload
{
    //取消下载
    [self.connection cancel];
}

@end

大文件下载

上面的代码虽然能做到文件的下载,但他会将网络加载的数据一直缓存在内存中释放不掉,如果是下载轻量级的文件还可以采取,如果是大数据的文件就不能用上面的方法

这个时候要用到NSFileManager和NSFileHandle

首先用NSFileManager创建一个空文件,NSFileHandle类中得方法可以对文件进行基本的读写,偏移量的操作。我们使用NSFileHandle偏移倒文件的末尾,然后向末尾追加数据。在文件下载完毕以后,记得要关闭NSFileHandler

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>

@property(assign,nonatomic)NSInteger downloadTotal;
@property(assign,nonatomic)NSInteger downloadDataLength;
@property(strong,nonatomic)NSString *fullFilePath;
@property(strong,nonatomic)NSFileHandle *fileHandler;

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_03.mp4"];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //NSLog(@"%zd",response.expectedContentLength);

    self.downloadTotal = response.expectedContentLength;//表示本次下载的数据长度

    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    self.fullFilePath = [path stringByAppendingPathComponent:@"3.mp4"];
    NSLog(@"%@",self.fullFilePath);

    [[NSFileManager defaultManager] createFileAtPath:self.fullFilePath contents:nil attributes:nil];

    self.fileHandler = [NSFileHandle fileHandleForWritingAtPath:self.fullFilePath];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    //将指针移动到文件末尾位置
    [self.fileHandler seekToEndOfFile];

    [self.fileHandler writeData:data];

    self.downloadDataLength += data.length;

    NSLog(@"download progress:%zd",(NSInteger)(1.0 * self.downloadDataLength / self.downloadTotal * 100));
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.fileHandler closeFile];

    self.fileHandler = nil;

    NSLog(@"下载完成");
}

- (void)cancelDownload
{
    //取消下载
    [self.connection cancel];
}
@end

支持断点续传的下载实例

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>

@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@property(assign,nonatomic)NSInteger downloadTotal;
@property(assign,nonatomic)NSInteger downloadDataLength;
@property(strong,nonatomic)NSString *fullFilePath;
@property(strong,nonatomic)NSFileHandle *fileHandler;
@property(strong,nonatomic)NSURLConnection *connection;

@end

@implementation ViewController

- (IBAction)startDownload:(id)sender {
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_03.mp4"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //实现断点续传的关键是设置请求头,在请求头设置要获得的数据的范围
    //表示头500个字节:Range:bytes=0-499
    //表示第二个500字节:Range:bytes=500-999
    //表示最后500个字节:Range:bytes=-500
    //表示500字节以后的范围:Range:bytes=500-
    NSString *lastRangeStr = [NSString stringWithFormat:@"bytes=%zd-",self.downloadDataLength];
    [request setValue:lastRangeStr forHTTPHeaderField:@"Range"];

    self.connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
}

- (IBAction)stopDownload:(id)sender {
    [self.connection cancel];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    //NSLog(@"%zd",response.expectedContentLength);

    //self.downloadTotal = response.expectedContentLength + self.downloadDataLength;//表示本次下载的数据长度,如果这句写在return前,就必须每次执行获取本次下载长度再加上已经下载的长度,否则总量会出错

    if(self.downloadTotal)return;

    self.downloadTotal = response.expectedContentLength;//表示本次下载的数据长度

    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    self.fullFilePath = [path stringByAppendingPathComponent:@"3.mp4"];
    NSLog(@"%@",self.fullFilePath);

    [[NSFileManager defaultManager] createFileAtPath:self.fullFilePath contents:nil attributes:nil];

    self.fileHandler = [NSFileHandle fileHandleForWritingAtPath:self.fullFilePath];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.fileHandler seekToEndOfFile];

    [self.fileHandler writeData:data];

    self.downloadDataLength += data.length;

    float progress = 1.0 * self.downloadDataLength / self.downloadTotal;

    [self.progressView setProgress:progress];

    NSLog(@"download progress:%zd",(NSInteger)(progress  * 100));
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.fileHandler closeFile];

    self.fileHandler = nil;

    NSLog(@"下载完成");
}

- (void)cancelDownload
{
    //取消下载
    [self.connection cancel];
}

@end

使用NSOutputStream进行断点续传下载

#import "ViewController.h"

@interface ViewController () <NSURLConnectionDataDelegate>

@property (strong, nonatomic) IBOutlet UIProgressView *progressView;
@property(assign,nonatomic)NSInteger downloadTotal;
@property(assign,nonatomic)NSInteger downloadDataLength;
@property(strong,nonatomic)NSString *fullFilePath;
@property(strong,nonatomic)NSURLConnection *connection;
@property(strong,nonatomic)NSOutputStream *stream;

@end

@implementation ViewController

- (IBAction)startDownload:(id)sender {
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/resources/videos/minion_03.mp4"];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //设置请求头
    //表示头500个字节:Range:bytes=0-499
    //表示第二个500字节:Range:bytes=500-999
    //表示最后500个字节:Range:bytes=-500
    //表示500字节以后的范围:Range:bytes=500-
    NSString *lastRangeStr = [NSString stringWithFormat:@"bytes=%zd-",self.downloadDataLength];
    [request setValue:lastRangeStr forHTTPHeaderField:@"Range"];

    self.connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
}

- (IBAction)stopDownload:(id)sender {
    [self.connection cancel];
}

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    if(self.downloadTotal)return;

    self.downloadTotal = response.expectedContentLength;//表示本次下载的数据长度

    NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
    self.fullFilePath = [path stringByAppendingPathComponent:@"3.mp4"];
    NSLog(@"%@",self.fullFilePath);

    /*
     *参数1:文件的路径
     *参数2:YES表示数据向后追加,NO表示不追加
     */
    self.stream = [[NSOutputStream alloc]initToFileAtPath:self.fullFilePath append:YES];

    //打开输出流
    [self.stream open];
}

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
{
    [self.stream write:data.bytes maxLength:data.length];//注意第一个参数要填data的bytes属性,因为要传的是const uint8_t *类型
    self.downloadDataLength += data.length;

    float progress = 1.0 * self.downloadDataLength / self.downloadTotal;

    [self.progressView setProgress:progress];

    NSLog(@"download progress:%zd",(NSInteger)(progress  * 100));
}

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{

}

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    [self.stream close];
    NSLog(@"下载完成");
}

- (void)cancelDownload
{
    //取消下载
    [self.connection cancel];
}

@end

文件上传

上传一个文件会产生如下所示的请求头和请求体

Request Headers
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:3369
Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryxKActJ0tULkgRBtp
Host:120.25.226.186:32812
Origin:http://120.25.226.186:32812
Referer:http://120.25.226.186:32812/
Upgrade-Insecure-Requests:1
User-Agent:Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36
Request Payload
  ------WebKitFormBoundaryxKActJ0tULkgRBtp
  Content-Disposition: form-data; name="file"; filename="Math2.db"
  Content-Type: application/octet-stream


  ------WebKitFormBoundaryxKActJ0tULkgRBtp
  Content-Disposition: form-data; name="username"

  123456
  ------WebKitFormBoundaryxKActJ0tULkgRBtp--

所有在Http请求的时候要严格按照上述格式来拼接

文件上传的步骤:

设置请求头
[request setValue:@"multipart/form-data; boundary=分割线" forHTTPHeaderField:@"Content-Type"];

设置请求体
非文件参数
--分割线\r\n
Content-Disposition: form-data; name="参数名"\r\n
\r\n
参数值
\r\n

文件参数
--分割线\r\n
Content-Disposition: form-data; name="参数名"; filename="文件名"\r\n
Content-Type: 文件的MIMEType\r\n
\r\n
文件数据
\r\n

参数结束的标记
--分割线--\r\n
//文件上传步骤
/*
 1.设置请求头
 Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryjv0UfA04ED44AhWx
 2.按照固定的格式拼接请求体的数据

 ------WebKitFormBoundaryjv0UfA04ED44AhWx
 Content-Disposition: form-data; name="file"; filename="Snip20160225_341.png"
 Content-Type: image/png


 ------WebKitFormBoundaryjv0UfA04ED44AhWx
 Content-Disposition: form-data; name="username"

 123456
 ------WebKitFormBoundaryjv0UfA04ED44AhWx--

 */
//拼接请求体的数据格式
/*
 拼接请求体
 分隔符:----WebKitFormBoundaryjv0UfA04ED44AhWx
 1)文件参数
 --分隔符
 Content-Disposition: form-data; name="file"; filename="Snip20160225_341.png"
 Content-Type: image/png(MIMEType:大类型/小类型)
 空行
 文件参数
 2)非文件参数
 --分隔符
 Content-Disposition: form-data; name="username"
 空行
 123456
 3)结尾标识
 --分隔符--
 */
#import "ViewController.h"

#define Kboundary @"----WebKitFormBoundaryjv0UfA04ED44AhWx"
#define KNewLine [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]

@interface ViewController ()

@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self upload];
}

-(void)upload
{
    //1.确定请求路径
    NSURL *url = [NSURL URLWithString:@"http://120.25.226.186:32812/upload"];

    //2.创建可变的请求对象
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];

    //3.设置请求方法
    request.HTTPMethod = @"POST";

    //4.设置请求头信息
    //Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryjv0UfA04ED44AhWx
    [request setValue:[NSString stringWithFormat:@"multipart/form-data; boundary=%@",Kboundary] forHTTPHeaderField:@"Content-Type"];

    //5.拼接请求体数据
    NSMutableData *fileData = [NSMutableData data];
    //5.1 文件参数
    /*
     --分隔符
     Content-Disposition: form-data; name="file"; filename="Snip20160225_341.png"
     Content-Type: image/png(MIMEType:大类型/小类型)
     空行
     文件参数
     */
    [fileData appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];

    //name:file 服务器规定的参数
    //filename:Snip20160225_341.png 文件保存到服务器上面的名称
    //Content-Type:文件的类型
    [fileData appendData:[@"Content-Disposition: form-data; name=\"file\"; filename=\"IMG_2545.JPG\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:[@"Content-Type: image/png" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:KNewLine];

    UIImage *image = [UIImage imageNamed:@"Snip20160225_341"];
    //UIImage --->NSData
    //NSData *imageData = UIImagePNGRepresentation(image);
    NSData *imageData = UIImageJPEGRepresentation(image, 1);
    [fileData appendData:imageData];
    [fileData appendData:KNewLine];

    //5.2 非文件参数
    /*
     --分隔符
     Content-Disposition: form-data; name="username"
     空行
     123456
     */
    [fileData appendData:[[NSString stringWithFormat:@"--%@",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:[@"Content-Disposition: form-data; name=\"username\"" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];
    [fileData appendData:KNewLine];
    [fileData appendData:[@"123456" dataUsingEncoding:NSUTF8StringEncoding]];
    [fileData appendData:KNewLine];

    //5.3 结尾标识
    /*
     --分隔符--
     */
    [fileData appendData:[[NSString stringWithFormat:@"--%@--",Kboundary] dataUsingEncoding:NSUTF8StringEncoding]];

    //6.设置请求体
    request.HTTPBody = fileData;

    //7.发送请求
    [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {

        //8.解析数据
        NSLog(@"%@",[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding]);
    }];
}

@end

部分文件的MIMEType

获得MIMEType的途径:

1.使用NSURLConnection请求本地文件url

- (NSString*)getMimeTypeWithPath:(NSString*)path
{
    NSURL *url = [NSURL fileURLWithPath:path];
    NSURLRequest *request = [NSURLRequest requestWithURL:url];
    NSURLResponse *response = nil;
    [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:nil];
    return response.MIMEType;
}

2.使用一个C语言的方法

#import <MobileCoreServices/MobileCoreServices.h>

...

- (NSString *)mimeTypeForFileAtPath:(NSString *)path
{
    if (![[[NSFileManager alloc] init] fileExistsAtPath:path]) {
        return nil;
    }

    CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (__bridge CFStringRef)[path pathExtension], NULL);
    CFStringRef MIMEType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
    CFRelease(UTI);
    if (!MIMEType) {
        return @"application/octet-stream";
    }
    return (__bridge NSString *)(MIMEType);
}

关于NSConnection和Runloop的关系

#import "ViewController.h"

@interface ViewController ()<NSURLConnectionDataDelegate>

@end

@implementation ViewController

-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self newThreadDelegate2];
}

-(void)delegate1
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];

    //设置代理
    //代理方法:默认是在主线程中调用的
    NSURLConnection *connect = [NSURLConnection connectionWithRequest:request delegate:self];


    //设置代理方法在哪个线程中调用
    //[NSOperationQueue alloc]init]]    开子线程
    //[NSOperationQueue mainQueue]  不能这样设置
    [connect setDelegateQueue:[[NSOperationQueue alloc]init]];
    //[connect setDelegateQueue:[NSOperationQueue mainQueue]];

    NSLog(@"-------");
}

-(void)delegate2
{
    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];

    //设置代理
    //代理方法:默认是在主线程中调用的
    NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];


    [connect setDelegateQueue:[[NSOperationQueue alloc]init]];

    //开始发送请求
    [connect start];
    NSLog(@"-------");
}

-(void)newThreadDelegate1
{
   dispatch_async(dispatch_get_global_queue(0, 0), ^{

       NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];

       //设置代理
       //代理方法:默认是在主线程中调用的
       //该方法内部其实会将connect对象作为一个source添加到当前的runloop中,指定运行模式为默认
       NSURLConnection *connect = [NSURLConnection connectionWithRequest:request delegate:self];

       //设置代理方法在哪个线程中调用
       [connect setDelegateQueue:[[NSOperationQueue alloc]init]];

       //[[NSRunLoop currentRunLoop] runMode:UITrackingRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:1000]];
       [[NSRunLoop currentRunLoop]run];

         NSLog(@"---%@----",[NSThread currentThread]);
   });

}

-(void)newThreadDelegate2
{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://120.25.226.186:32812/login?username=123&pwd=123&type=JSON"]];

        //设置代理
        //代理方法:默认是在主线程中调用的
        NSURLConnection *connect = [[NSURLConnection alloc]initWithRequest:request delegate:self startImmediately:NO];

        [connect setDelegateQueue:[[NSOperationQueue alloc]init]];

        //开始发送请求
        //如如果connect对象没有添加到runloop中,那么该方法内部会自动的添加到runloop
        //注意:如果当前的runloop没有开启,那么该方法内部会自动获得当前线程对应的runloop对象并且开启
        [connect start];
        NSLog(@"---%@----",[NSThread currentThread]);
    });
}

#pragma mark ----------------------
#pragma mark NSURLConnectionDataDelegate
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
    NSLog(@"didReceiveResponse---%@",[NSThread currentThread]);
}

@end

results matching ""

    No results matching ""