网络开发方案
在iOS中,常见的发送HTTP请求的方案包括:
- 苹果官方
- 第三方框架
NSURLConnection 在IOS9之后,已经被苹果废弃,取而代之的是iOS7之后出现的NSURLSession
1.NSURLConnection发送网络请求
1.设置URL NSURL:确定要访问的资源
2.创建请求 NSURLRequest:根据 URL 建立请求,向服务器索要数据
3.发送请求 NSURLConnection:建立网络连接,将请求发送给服务器
4.数据处理 response,data,error处理
- (void)viewDidLoad {
[super viewDidLoad];
// 1.URL : 注意,在客户端开发中,协议头一定要手写上去
NSURL *URL= [NSURL URLWithString:@"http://www.baidu.com"];
// 2.请求 (request)
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 3.发送请求--默认是GET请求
/*
参数1 : request
参数2 : queue : 队列,并发队列,completionHandler所在的队列;如果你想在completionHandler里面刷新UI,队列需要时主队列
参数3 : completionHandler : 请求之后,得到了响应体的回调,这个回调是在子线程执行的(异步回调)
提示 : completionHandler 其实是在耗时操作执行结束之后回调
提示 :sendAsynchronousRequest 是耗时的
*/
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
NSLog(@"%@",[NSThread currentThread]);
/*
response : 响应头
data : 响应体(是程序猿需要的数据,二进制的)
connectionError : 错误信息
*/
//是服务器告诉客户端的额外信息
NSLog(@"响应头 %@",response);
// 响应体
NSLog(@"响应体 %@",data);
//错误信息
NSLog(@"错误信息 %@",connectionError);
// 错误处理
if (connectionError == nil && data != nil && data.length > 0) {
// 获取data
// 4.处理响应 : 响应体就是程序猿需要的数据
NSLog(@"%@",data);
} else {
NSLog(@"%@",connectionError);
}
}];
}
2.NSURLConnection请求缓存策略和超时时长
缓存策略:cachePolicy
/*
cachePolicy : 缓存策略
NSURLRequestUseProtocolCachePolicy = 0, 使用HTTP协议的默认缓存
NSURLRequestReloadIgnoringLocalCacheData = 1, 忽略本地缓存,只加载"最新"网络数据 (股票)
NSURLRequestReturnCacheDataElseLoad = 2, 优先加载缓存数据,如果没有缓存数据就加载最新的数据
NSURLRequestReturnCacheDataDontLoad = 3, 只加载本地缓存数据 (离线APP / 离线地图)
*/
NSURLRequest *request = [NSURLRequest requestWithURL:URL cachePolicy:0 timeoutInterval:15.0];
超时时长:timeoutInterval
- 默认网络时长是 60 s
- 建议超时时长 15~30 秒之间
- SDWebImage 的默认超时时长是 15 秒
- AFN 的默认超时时长是 60 秒
3.NSURLConnection可变请求NSMutableURLRequest
设置请求超时等待时间(超过这个时间就算超时,请求失败)- (void)setTimeoutInterval:(NSTimeInterval)seconds;
设置请求方法(比如GET和POST)- (void)setHTTPMethod:(NSString *)method;
设置请求体- (void)setHTTPBody:(NSData *)data;
设置请求头- (void)setValue:(NSString )value forHTTPHeaderField:(NSString )field;
- (void)viewDidLoad {
[super viewDidLoad];
// 1.URL : 注意,在客户端开发中,协议头一定要手写上去
NSURL *URL = [NSURL URLWithString:@"http://www.baidu.com"];
// 2.创建可变的请求对象,可以告诉服务器一些你想告诉的额外信息
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL cachePolicy:0 timeoutInterval:15.0];
// 可以设置请求方法 :
// 提示 : 苹果的网络框架默认是GET请求
requestM.HTTPMethod = @"GET";
// requestM.HTTPMethod = @"POST";
// requestM.HTTPMethod = @"HEAD";
// 发送请求时,告诉服务器我的客户端是苹果设备 (User-Agent: iPhone AppleWebKit)
[requestM setValue:@"iPhone AppleWebKit" forHTTPHeaderField:@"User-Agent"];
// 3.发送请求
// NSURLConnection : 默认是GET请求
[NSURLConnection sendAsynchronousRequest:requestM queue:[NSOperationQueue mainQueue] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 4.处理响应 : 响应体就是程序猿需要的数据 (错误处理)
if (connectionError == nil && data != nil && data.length > 0) {
} else {
NSLog(@"%@",connectionError);
}
}];
}
4.NSURLConnection响应
此处不谈论状态行,只讨论响应头和响应体
- 响应头 response
- 响应体 data
- data 服务器返回的二进制数据,程序员最关心的内容
- 拿到响应体之后,无法直接使用,需要进行反序列化,转换成OC对象.
// 4.处理响应 : 响应体就是程序猿需要的数据 (错误处理)
if (connectionError == nil && data != nil && data.length > 0) {
// data二进制数据不能直接展示在界面上,客户端无法直接展示/使用二进制数据,我们就需要进行数据解析,把二进制数据转换成客户端可以直接展示/使用的数据
// 1. data是HTML5字符串类型的数据处理
NSString *html = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
// baseURL : 相对URL / 相对路径
[weakSelf.myWebView loadHTMLString:html baseURL:URL];
// 2. data是json类型的数据处理
NSDictionary *dict=[NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingMutableLeaves error:nil];
} else {
NSLog(@"%@",connectionError);
}
5.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
6.NSURLConnection–GET请求
- (IBAction)loginClick:(id)sender
{
NSString *URLString = [NSString stringWithFormat:@"http://localhost/php/login/login.php?username=%@&password=%@",self.userNameTextField.text,self.psdTextField.text];
// 注意!!! : GET请求,在发松请求之前,最好把URL做一个百分号转义,为了防止URL里面有中文/空格...;造成的URL不识别
// 提示 : POST请求不需要转义
// URLQueryAllowedCharacterSet : 转义 ? 后面的查询字符串(URL的参数)
URLString = [URLString stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]];
// URL : URL里面不能有汉字/空格/特殊符号...,如果有服务器不识别.
NSURL *URL = [NSURL URLWithString:URLString];
// 请求 : 默认就是GET请求
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
// 发送异步请求
[NSURLConnection sendAsynchronousRequest:request queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 处理响应
if (connectionError ==nil && data != nil) {
// 反序列化
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
// 登录是否成功的判断
if ([result[@"userId"] intValue] == 1) {
NSLog(@"登录成功");
} else {
NSLog(@"登录失败");
}
} else {
NSLog(@"%@",connectionError);
}
}];
}
7.NSURLConnection–POST请求
- (IBAction)loginClick:(id)sender
{
// URL
NSURL *URL = [NSURL URLWithString:@"http://localhost/php/login/login.php"];
// 可变请求
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
// 设置请求方法为POST : 请求方法是可以小写的,但是,建议大写
requestM.HTTPMethod = @"POST";
// 设置请求体
NSString *body = [NSString stringWithFormat:@"username=%@&password=%@",self.userNameTextField.text,self.psdTextField.text];
requestM.HTTPBody = [body dataUsingEncoding:NSUTF8StringEncoding];
// 发送请求
[NSURLConnection sendAsynchronousRequest:requestM queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 处理响应
if (connectionError == nil && data != nil) {
// 反序列化
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
if ([result[@"userId"] intValue] == 1) {
NSLog(@"登陆成功");
} else {
NSLog(@"登陆失败");
}
} else {
NSLog(@"%@",connectionError);
}
}];
}
8.GET / POST 请求对比
GET : http://localhost/php/login/login.php?username=zhouyu&password=123456
- 从服务器获取数据,效率比POST高.
- GET请求能够被缓存
- 在 HTTP 协议定义中,没有对GET请求的数据大小限制,不过因为浏览器不同一般限制在 2~8K 之间.
- GET发送请求时,URL中除了资源路径以外,所有的参数(查询字符串)也包装在URL中,并且服务器的访问日志会记录,不要传递敏感信息.
- 参数格式
- 在资源路径末尾添加 ? 表示追加参数.
- 每一个变量及值按照 变量名=变量值 方式设定,不能包含空格或者中文.
- 多个参数使用 & 连接.
- 注意 : URL 字符串中如果包含空格或者中文,需要添加百分号转义.
POST :
requestM.HTTPMethod
requestM.HTTPBody
- 向服务器发送数据,也可以获得服务器处理之后的结果,效率不如GET.
- POST请求不能被缓存.
- POST提交数据比较大,大小靠服务器的设定值限制,PHP通常限定 2M.
- POST发送请求时,URL中只有资源路径,但不包含参数,服务器日志不会记录参数,相对更安全.
- 参数被包装成二进制的数据体,格式与 GET 基本一致,只是不包含 ?.
注意 : 所有涉及到用户隐私的数据(密码,银行卡号)一定记住使用 POST 方式传递.
9.NSURLConnection文件上传–单个,多个,文本信息
分为四种情况:
- 单个文件上传,不带文本信息
- 单个文件上传,带文本信息
- 多个文件上传,不带文本信息
- 多文件上传的主方法,可以上传文本信息
文件上传管理者 FileUpLoadManager (单例设计模式)
FileUploadManger.h
#import <Foundation/Foundation.h>
@interface FileUploadManger : NSObject
+ (instancetype)sharedManger;
/**
* 单个文件上传,不带文本信息
*
* @param URLString 文件上传的地址
* @param serverFileName 服务器接收文件的字段名
* @param filePath 文件的路径
*/
- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath;
/**
* 单个文件上传,带文本信息
*
* @param URLString 文件上传的地址
* @param serverFileName 服务器接收文件的字段名
* @param filePath 文件的路径
* @param textDict 文本信息
*/
- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath textDict:(NSDictionary *)textDict;
/**
* 多个文件上传,不带文本信息
*
* @param URLString 文件上传的地址
* @param serverFileName 服务器接收文件的字段名
* @param filePaths 文件的路径数组
*/
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths;
/**
* 多文件上传的主方法,可以上传文本信息
*
* @param URLString 文件上传的地址
* @param serverFileName 服务器接收文件的字段名
* @param filePaths 文件的路径数组
* @param textDict 文件上传时的附带的文本信息
*/
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict;
@end
FileUploadManger.m
#import "FileUploadManger.h"
@implementation FileUploadManger
+ (instancetype)sharedManger {
static FileUploadManger *instance;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[FileUploadManger alloc] init];
});
return instance;
}
#pragma mark - 单个文件上传,不带文本信息
- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath {
[self uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:@[filePath] textDict:nil];
}
#pragma mark - 单个文件上传,带文本信息
- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath textDict:(NSDictionary *)textDict {
[self uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:@[filePath] textDict:textDict];
}
#pragma mark - 多个文件上传,不带文本信息
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths{
[self uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:filePaths textDict:nil];
}
#pragma mark - 多文件上传的主方法,可以上传文本信息
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict {
// URL
NSURL *URL = [NSURL URLWithString:URLString];
// 可变请求
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
// 设置请求头信息 Content-Type:
[requestM setValue:@"multipart/form-data; boundary=itcast" forHTTPHeaderField:@"Content-Type"];
// 设置请求方法
requestM.HTTPMethod = @"POST";
// 设置请求体
requestM.HTTPBody = [self getHTTPBodyWithServerFileName:serverFileName filePaths:filePaths textDict:textDict];
// 发送请求
[NSURLConnection sendAsynchronousRequest:requestM queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 处理响应
if (connectionError == nil && data != nil) {
// 反序列化
NSArray *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@",result);
} else {
NSLog(@"%@",connectionError);
}
}];
}
/**
* 获取多文件请求体信息(二进制)
*
* @param serverFileName 服务器接收文件的字段名
* @param filePaths 文件的路径数组
* @param textDict 文件上传时的附带的文本信息
*
* @return 返回二进制的请求体信息
*/
- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict {
// 定义可变的二进制容器,用于拼接整个请求体二进制信息
NSMutableData *dataM = [NSMutableData data];
// 拼接图片文件的二进制请求体信息
[filePaths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 定义可变的字符串拼接图片二进制数据之前的字符串
NSMutableString *stringM = [NSMutableString string];
// 拼接文件开始的分隔符
[stringM appendString:@"\r\n--mac\r\n"];
// 拼接表单数据
[stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[obj lastPathComponent]];
// 拼接文件类型 : 我不希望告诉服务器我的文件类型,采用8进制流的类型
[stringM appendString:@"Content-Type: application/octet-stream\r\n"];
// 拼接单纯的换行
[stringM appendString:@"\r\n"];
// 直接把图片二进制数据之前的字符串转成二进制,拼接到我们的请求体信息里面去
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
// 拼接图片/文件的二进制数据
[dataM appendData:[NSData dataWithContentsOfFile:obj]];
// 拼接最后的换行
NSString *br = @"\r\n";
[dataM appendData:[br dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 拼接文件上传时的文本信息的请求体信息
[textDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 定义可变的字符串,拼接文件上传时的文本信息
NSMutableString *stringM = [NSMutableString string];
// 拼接文本信息开始的分隔符
[stringM appendString:@"--mac\r\n"];
// 拼接表单数据
[stringM appendFormat:@"Content-Disposition: form-data; name=%@\r\n",key];
// 拼接单纯的换行
[stringM appendString:@"\r\n"];
// 拼接文本信息
[stringM appendFormat:@"%@\r\n",obj];
// 把文本信息的请求体二进制添加到dataM
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 拼接文件上传时的结束分隔符
NSString *end = @"--mac--";
[dataM appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}
@end
外界调用 ViewController.m
#import "ViewController.h"
#import "FileUploadManger.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 参数1
NSString *URLString = @"http://localhost/php/upload/upload-m.php";
// 参数2 - 服务器的字段
NSString *serverFileName = @"userfile[]";
// 参数3 - 需要上传的文件路径
NSString *filePath1 = [[NSBundle mainBundle] pathForResource:@"mm01.jpg" ofType:nil];
NSString *filePath2 = [[NSBundle mainBundle] pathForResource:@"mm02.jpg" ofType:nil];
NSArray *filePaths = @[filePath1,filePath2];
// 参数3
NSDictionary *textDict = [NSDictionary dictionaryWithObject:@"上传的文件都是图片" forKey:@"status"];
// 单个文件上传,不带文本信息
[[FileUploadManger sharedManger] uploadFileWithURLString:URLString serverFileName:serverFileName filePath:filePath2];
// 单个文件上传,带文本信息
// [[FileUploadManger sharedManger] uploadFileWithURLString:URLString serverFileName:serverFileName filePath:filePath1 textDict:textDict];
// 多个文件上传,不带文本信息
// [[FileUploadManger sharedManger] uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:filePaths];
// 多个文件上传,并且可以上传文本信息
// [[FileUploadManger sharedManger] uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:filePaths textDict:textDict];
}
@end
10.NSURLConnection文件上传原理分析
单个文件上传的原理
单个文件上传需要注意两点
第一点 : Content-Type (请求头里面的Content-Type)
1.文件上传时的Content-Type : "Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryECBUhMhhznU4PqiU"
2.Content-Type标示 : 告诉服务器我的这个请求是在干什么,是在做文件上传还是普通的请求
3.普通的请求的Content-Type : "Content-Type: application/x-www-form-urlencoded"
4.boundary : 文件上传的分隔符,可以自定义,由非中文的字符组成,四个中画线是可以省略的
5.自定义boundary : boundary=mac
6.自定义boundary之后的Content-Type : "Content-Type: multipart/form-data; boundary=mac"
第二点 : 请求体 (二进制)
------WebKitFormBoundaryECBUhMhhznU4PqiU
Content-Disposition: form-data; name="userfile"; filename="mm01.jpg"
Content-Type: image/jpeg
------WebKitFormBoundaryECBUhMhhznU4PqiU--
1.分析请求体信息
第一行 : 文件上传的开始的分隔符,前面的两个中划线不能省略,可以自定义,但是一定要跟boundary一模一样 (–mac)
第二行 : 需要处理的表单数据
name=”userfile” 标示 : 服务器接收文件的字段名,”userfile”是服务器提供给客户端使用的;客户端开发者千万不要修改
filename=”mm01.jpg” 标示 : 文件保存到服务器上的文件名,可以使用文件的原名,还可以在上传之前自定义一个名字,提示,一般直接使用原名第三行 : 要上传的文件的类型 .
第四行 : 单纯的换行 (\r\n) .
第六行 :文件上传结束的分隔符,前面和后面的两个中划线都不能省略
2.总结请求体 (总结请求体的格式)
--mac\r\n
Content-Disposition: form-data; name="userfile"; filename="mm01.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n
--mac--
总结文件上传的步骤
第一步 : 设置请求头信息里面的Content-Type
第二步 : 设置文件上传的请求体信息
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 参数1
NSString *URLString = @"http://localhost/php/upload/upload.php";
// 参数2
NSString *serverFileName = @"userfile";
// 参数3
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"sunli.jpg" ofType:nil];
[self uploadFileWithURLString:URLString serverFileName:serverFileName filePath:filePath];
}
/**
* 单个文件上传的主方法
*
* @param URLString 文件上传的路径
* @param serverFileName 服务器接收文件的字段名
* @param filePath 要上传的文件的路径(有了文件路径就可以获取文件名和文件的二进制)
*/
- (void)uploadFileWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePath:(NSString *)filePath
{
// URL
NSURL *URL = [NSURL URLWithString:URLString];
// 可变请求
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
// 设置请求头信息 Content-Type:
[requestM setValue:@"multipart/form-data; boundary=itcast" forHTTPHeaderField:@"Content-Type"];
// 设置请求方法
requestM.HTTPMethod = @"POST";
// 设置请求体
requestM.HTTPBody = [self getHTTPBodyWithServerFileName:serverFileName filePath:filePath];
// 发送请求
[NSURLConnection sendAsynchronousRequest:requestM queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 处理响应
if (connectionError == nil && data != nil) {
// 反序列化
NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@",result);
} else {
NSLog(@"%@",connectionError);
}
}];
}
/**
* 获取文件上传的请求体信息(二进制)
*
* @param serverFileName 服务器接收文件的字段名
* @param filePath 要上传的文件的路径
*
* @return 返回文件上传的请求体二进制信息
*/
- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePath:(NSString *)filePath {
/*
--mac\r\n
Content-Disposition: form-data; name="userfile"; filename="mm01.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data
\r\n--mac--
*/
// 定义可变的二进制容器,拼接请求体的二进制信息
NSMutableData *dataM = [NSMutableData data];
// 定义可变字符串,拼接开始的请求体字符串信息
NSMutableString *stringM = [NSMutableString string];
// 拼接文件开始上传的分隔符
[stringM appendString:@"--mac\r\n"];
// 拼接表单数据
[stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[filePath lastPathComponent]];
// 拼接要上传的文件的类型 : 如果你不想告诉服务器你的文件类型具体是什么,就可以使用 "Content-Type: application/octet-stream"
[stringM appendString:@"Content-Type: image/jpeg\r\n"];
// 拼接单穿的换行
[stringM appendString:@"\r\n"];
// 在这里(拼接文件的二进制数据之前),把前面的请求体字符串转换成二进制,先拼接一次
NSData *stringM_data = [stringM dataUsingEncoding:NSUTF8StringEncoding];
[dataM appendData:stringM_data];
// 拼接文件的二进制数据
NSData *file_data = [NSData dataWithContentsOfFile:filePath];
[dataM appendData:file_data];
NSString *end = @"\r\n--mac--";
[dataM appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}
多个文件上传的原理
多个文件上传需要注意两点
第一点 : Content-Type (请求头里面的Content-Type)
1.文件上传时的Content-Type : "Content-Type: multipart/form-data; boundary=----WebKitFormBoundarymWW2zh14kRXTZzbV"
2.Content-Type标示 : 告诉服务器我的这个请求是在干什么,是在做文件上传还是普通的请求
3.普通的请求的Content-Type : "Content-Type: application/x-www-form-urlencoded"
4.boundary : 文件上传的分隔符,可以自定义,由非中文的字符组成,四个中画线是可以省略的
5.自定义boundary : boundary=mac
6.自定义boundary之后的Content-Type : "Content-Type: multipart/form-data; boundary=mac"
第二点 : 请求体信息 (二进制)
------WebKitFormBoundarymWW2zh14kRXTZzbV
Content-Disposition: form-data; name="userfile[]"; filename="mm01.jpg"
Content-Type: image/jpeg
------WebKitFormBoundarymWW2zh14kRXTZzbV
Content-Disposition: form-data; name="userfile[]"; filename="mm02.jpg"
Content-Type: image/jpeg
------WebKitFormBoundarymWW2zh14kRXTZzbV
Content-Disposition: form-data; name="status"
今天大家都不在状态!为什么?
------WebKitFormBoundarymWW2zh14kRXTZzbV--
1.分析请求体信息
--mac\r\n (第一张图片开始的分隔符)
Content-Disposition: form-data; name="userfile[]"; filename="mm01.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n (第一张图片的二进制信息)
--itcast\r\n (第二张图片开始的分隔符)
Content-Disposition: form-data; name="userfile[]"; filename="mm02.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n (第二张图片的二进制信息)
--itcast\r\n (文本信息的开始分隔符)
Content-Disposition: form-data; name="status"\r\n (status : 标示服务器接收文本信息的字段名,千万不要修改)
\r\n
今天大家都不在状态!为什么?\r\n
--mac--
总结多文件上传的步骤 :
第一步 : 设置请求头信息里面的Contnent-Type
第二步 : 设置请求体二进制信息
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 参数1
NSString *URLString = @"http://localhost/php/upload/upload-m.php";
// 参数2
NSString *serverFileName = @"userfile[]";
// 参数3
NSString *filePath1 = [[NSBundle mainBundle] pathForResource:@"mm01.jpg" ofType:nil];
NSString *filePath2 = [[NSBundle mainBundle] pathForResource:@"mm02.jpg" ofType:nil];
NSArray *filePaths = @[filePath1,filePath2];
// 参数3
//Content-Disposition: form-data; name="status"
//今天是个好日子
NSDictionary *textDict = [NSDictionary dictionaryWithObject:@"今天大家都不在状态!为什么?" forKey:@"status"];
[self uploadFilesWithURLString:URLString serverFileName:serverFileName filePaths:filePaths textDict:textDict];
}
/**
* 多文件上传的主方法,可以上传文本信息
*
* @param URLString 文件上传的地址
* @param serverFileName 服务器接收文件的字段名
* @param filePaths 文件的路径数组
* @param textDict 文件上传时的附带的文本信息
*/
- (void)uploadFilesWithURLString:(NSString *)URLString serverFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict {
// URL
NSURL *URL = [NSURL URLWithString:URLString];
// 可变请求
NSMutableURLRequest *requestM = [NSMutableURLRequest requestWithURL:URL];
// 设置请求头信息 Content-Type:
[requestM setValue:@"multipart/form-data; boundary=itcast" forHTTPHeaderField:@"Content-Type"];
// 设置请求方法
requestM.HTTPMethod = @"POST";
// 设置请求体
requestM.HTTPBody = [self getHTTPBodyWithServerFileName:serverFileName filePaths:filePaths textDict:textDict];
// 发送请求
[NSURLConnection sendAsynchronousRequest:requestM queue:[[NSOperationQueue alloc] init] completionHandler:^(NSURLResponse * _Nullable response, NSData * _Nullable data, NSError * _Nullable connectionError) {
// 处理响应
if (connectionError == nil && data != nil) {
// 反序列化
NSArray *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
NSLog(@"%@",result);
} else {
NSLog(@"%@",connectionError);
}
}];
}
/**
* 获取多文件请求体信息(二进制)
*
* @param serverFileName 服务器接收文件的字段名
* @param filePaths 文件的路径数组
* @param textDict 文件上传时的附带的文本信息
*
* @return 返回二进制的请求体信息
*/
- (NSData *)getHTTPBodyWithServerFileName:(NSString *)serverFileName filePaths:(NSArray *)filePaths textDict:(NSDictionary *)textDict
{
/*
--mac\r\n
Content-Disposition: form-data; name="userfile[]"; filename="mm01.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n
--mac\r\n
Content-Disposition: form-data; name="userfile[]"; filename="mm02.jpg"\r\n
Content-Type: image/jpeg\r\n
\r\n
data\r\n
--mac\r\n
Content-Disposition: form-data; name="status"\r\n
\r\n
今天大家都不在状态!为什么?\r\n
--mac--
*/
// 定义可变的二进制容器,用于拼接整个请求体二进制信息
NSMutableData *dataM = [NSMutableData data];
// 拼接图片文件的二进制请求体信息
[filePaths enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
// 定义可变的字符串拼接图片二进制数据之前的字符串
NSMutableString *stringM = [NSMutableString string];
// 拼接文件开始的分隔符
[stringM appendString:@"\r\n--mac\r\n"];
// 拼接表单数据
[stringM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n",serverFileName,[obj lastPathComponent]];
// 拼接文件类型 : 我不希望告诉服务器我的文件类型,采用8进制流的类型
[stringM appendString:@"Content-Type: application/octet-stream\r\n"];
// 拼接单纯的换行
[stringM appendString:@"\r\n"];
// 直接把图片二进制数据之前的字符串转成二进制,拼接到我们的请求体信息里面去
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
// 拼接图片/文件的二进制数据
[dataM appendData:[NSData dataWithContentsOfFile:obj]];
// 拼接最后的换行
NSString *br = @"\r\n";
[dataM appendData:[br dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 拼接文件上传时的文本信息的请求体信息
[textDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 定义可变的字符串,拼接文件上传时的文本信息
NSMutableString *stringM = [NSMutableString string];
// 拼接文本信息开始的分隔符
[stringM appendString:@"--mac\r\n"];
// 拼接表单数据
[stringM appendFormat:@"Content-Disposition: form-data; name=%@\r\n",key];
// 拼接单纯的换行
[stringM appendString:@"\r\n"];
// 拼接文本信息
[stringM appendFormat:@"%@\r\n",obj];
// 把文本信息的请求体二进制添加到dataM
[dataM appendData:[stringM dataUsingEncoding:NSUTF8StringEncoding]];
}];
// 拼接文件上传时的结束分隔符
NSString *end = @"--mac--";
[dataM appendData:[end dataUsingEncoding:NSUTF8StringEncoding]];
return dataM.copy;
}