一. POST单文件上传-简单使用
1. 创建请求
- 实例化请求并设置基本参数
// 0. 获取服务器端口的地址
NSURL *url = [NSURL URLWithString:@"http://localhost/upload/upload.php"];
#warning:对于POST请求,必须手动设置其请求方法,因此要使用可变请求
// 1. 创建请求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2. 设置请求方式
request.HTTPMethod = @"POST";
// 3. 告诉服务器本次上传文件的相关信息
// 固定格式: 设置Content-Type
// Content-Type: multipart/form-data; boundary=---------------------------198596859919834017191791522499
// Content-Type:本次上传文件类型信息,包含boundary
// boundary:本次上传文件的边界(自己随意设置,只要三个地方一致即可)
NSString *type = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBoundary];
[request setValue:type forHTTPHeaderField:@"Content-Type"];
-
重难点
: 设置请求体,分为三个部分-
上边界部分,告诉服务器要做数据上传,包含了
-
userfile
-> 负责上传文件脚本中的 字段名,开发的时候,可以咨询后端程序员 -
filename
-> 将文件保存在服务器上的文件名称 -
Content-Type
-> 客户端告诉服务器上传文件的文件类型(如果不想写文件类型,统一用 application/octet-stream[8进制流])
-
上传文件的数据部分(即文件内容的二进制数据)
下边界部分,严格按照字符串格式来设置:--boundary--
-
// 实例化请求体
NSMutableData *data = [NSMutableData data];
// -----------------------------198596859919834017191791522499
// Content-Disposition: form-data; name="userfile"; filename="WPFNetWorkTool.h"
// Content-Type: application/octet-stream
#warning 有些服务器可以直接使用 \n,但是新浪微博如果使用 \n 上传文件,服务器会返回“没有权限”的错误! 因此一定要注意安全换行:\r\n
// 1. 拼接上传文件的上边界信息
NSMutableString *headerStrM = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服务器接收参数的key值,后台工作人员告诉我们
// filename=%@ :文件上传到服务器的存储名,若不设置则为默认名,名称保持不变
[headerStrM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", @"userfile", @"123" ];
// Content-Type: application/octet-stream 表明文件的上传类型,乱写类型不会影响上传,但是不符合规范
[headerStrM appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
// 将上传文件的上边界信息添加到请求体中
[data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]];
// 2. 设置文件内容
// 文件地址
NSString *filePath = @"/Users/wangpengfei/Desktop/WPFNetWorkTool.h";
// 将文件转化为二进制形式
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
// 将文件内容添加到请求体中
[data appendData:fileData];
// 3. 设置文件的下边界
// -----------------------------198596859919834017191791522499--
NSString *footerStrM = [NSString stringWithFormat:@"\r\n--%@--", kBoundary];
NSLog(@"footerStrM--->%@", footerStrM);
// 将下边界添加到请求体中
[data appendData:[footerStrM dataUsingEncoding:NSUTF8StringEncoding]];
// 4. 设置请求体
request.HTTPBody = data;
常见的 Content-Type 类型:
大类型 | 小类型 |
---|---|
image | png |
image | jpg |
image | gif |
text | html |
application | json |
2. 发送请求
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
/*
打印结果:
{"userfile":{"name":"123","type":"application\/octet-stream","tmp_name":"\/private\/var\/tmp\/phpEk0KCK","error":0,"size":0}}
*/
}] resume];
二. POST单文件上传-简单封装结构体
1. 获得本地文件响应头信息,使用同步方法*重难点*
通过响应头信息,可以获得文件的类型/长度/建议的名称.
-
MIMEType
:就是文件类型 -
suggestedFilename
: 推荐文件名(本地存储名) -
expectedContentLength
: 文件长度
如果文件比较大,不建议发送本地请求.发送本地请求,会将文件从沙盒中加载到内存中,造成内存开销.
- (NSURLResponse *)getFileResponseWithFilePath:(NSString *)filePath {
// 动态获取文件类型
// 1. 获取文件路径,根据路径获取 url 地址,本地协议名 file://
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@", filePath]];
// 2. 创建请求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// NSURLSession 没有同步请求的方法
// 利用 NSURLConnection 发送同步请求
// 定义一片空的地址
NSURLResponse *response = nil;
// &response 二级指针
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
return response;
}
2. 封装请求体格式
-
filePath
: 上传文件的路径 -
fileKey
: 服务器接受文件的 key 值 -
fileName
: 上传文件在服务器中保存的名称(可选)
- (NSData *)setupHttpBodyWithFilePath:(NSString *)filePath fileKey:(NSString *)fileKey fileName:(NSString *)fileName;
- 当用户没有设置fileName时,调用方法一:设置文件为默认名
// 调用获得本地文件信息的方法
NSURLResponse *response = [self getFileResponseWithFilePath:filePath];
if (!fileName) {
fileName = response.suggestedFilename;
}
三. POST单文件上传封装
1. 取出已封装好的单例类WPFNetWorkTool
,封装以下方法
- urlString:网络接口
- filePath:需要上传的文件路径
- fileKey:服务器接收用户上传文件的key值
- fileName:上传文件存储到服务器的名称
- success:上传成功时调用的block
- fail:上传失败时调用的block
- (void)POSTFileWithUrlString:(NSString *)urlString
filePath:(NSString *)filePath fileKey:(NSString *)fileKey
fileName:(NSString *)fileName
success:(successBlock)success fail:(failBlock)fail;
2. 在发送请求方法中增加以下内容
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
// 成功
if (data && !error) {
id responseObj = [NSJSONSerialization JSONObjectWithData:data options:0 error:NULL];
// 如果不能解析JSON数据
if (!responseObj) {
responseObj = data;
}
// 执行回调
if (success) {
success(responseObj, response);
}
// 失败
} else {
// 执行回调
if (fail) {
fail(error);
}
}
}] resume];
3. 封装方法的调用
// POST文件上传
[[WPFNetWorkTool sharedTool] POSTFileWithUrlString:@"http://localhost/upload/upload.php" filePath:@"/Users/wangpengfei/Desktop/葵花宝典/下载工具/WPFNetWorkTool/WPFNetWorkTool.h" fileKey:@"userfile" fileName:NULL success:^(id obj, NSURLResponse *response) {
NSLog(@"obj--->%@", obj);
} fail:^(NSError *error) {
NSLog(@"error--->%@", error);
}];
#warning 图片等文件可以显示name,但是oc程序文件不能显示
/*
打印结果:
obj--->{
userfile = {
error = 0;
name = "(null)";
size = 1702;
"tmp_name" = "/private/var/tmp/phposR0Tv";
type = "application/octet-stream";
};
}
*/
四. POST多文件上传-简单使用
多文件上传和单文件上传的基本思路是一样的,唯一的区别在于对请求体的封装
.
-
多文件上传的请求体格式
// 第一个文件上边界及参数 \r\n--boundary\r\n Content-Disposition: form-data; name=userfile[]; filename=美女\r\n Content-Type:image/jpeg\r\n\r\n 第一个文件的二进制数据部分 // 第二个文件上边界及参数 \r\n--boundary\r\n Content-Disposition: form-data; name=userfile[]; filename=JSON\r\n Content-Type:text/plain\r\n\r\n 第二个文件的二进制数据部分 // 下边界 \r\n--boundary--
1.设置第一个文件的上边界及参数
NSMutableString *headerStrM1 = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服务器接收参数的key值,后台工作人员告诉我们
// filename=%@ :文件上传到服务器的存储名,若不设置则为默认名,名称保持不变
#warning @"userfile[]"后台人员提供的数据
[headerStrM1 appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", @"userfile[]", @"234" ];
// Content-Type: application/octet-stream 表明文件的上传类型,乱写类型不会影响上传,但是不符合规范
[headerStrM1 appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
// 将上传文件的上边界信息添加到请求体中
[data appendData:[headerStrM1 dataUsingEncoding:NSUTF8StringEncoding]];
- 2.设置第一个文件的二进制数据
// 文件地址
NSString *filePath1 = @"/Users/wangpengfei/Desktop/photo/IMG_5544.jpg";
// 将文件转化为二进制形式
NSData *fileData1 = [NSData dataWithContentsOfFile:filePath1];
// 将文件内容添加到请求体中
[data appendData:fileData1];
- 3.设置第二个文件的上边界及参数
NSMutableString *headerStrM2 = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服务器接收参数的key值,后台工作人员告诉我们
// filename=%@ :文件上传到服务器的存储名,若不设置则为默认名,名称保持不变
[headerStrM2 appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", @"userfile[]", @"123" ];
[headerStrM2 appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
// 将上传文件的上边界信息添加到请求体中
[data appendData:[headerStrM2 dataUsingEncoding:NSUTF8StringEncoding]];
- 4.设置第二个文件的二进制数据
// 文件地址
NSString *filePath2 = @"/Users/wangpengfei/Desktop/photo/beauty1.jpg";
// 将文件转化为二进制形式
NSData *fileData2 = [NSData dataWithContentsOfFile:filePath2];
// 将文件内容添加到请求体中
[data appendData:fileData2];
- 5.设置下边界
NSString *footerStrM = [NSString stringWithFormat:@"\r\n--%@--", kBoundary];
// 将下边界添加到请求体中
[data appendData:[footerStrM dataUsingEncoding:NSUTF8StringEncoding]];
五. POST多文件上传-添加普通参数
- 有些网络请求,客户端需要告诉服务器一些必要的数据,服务器根据客户端传过来的数据(参数)去数据库检索出对应的数据.返回给客户端.
- 必须参数: 必须附带的参数(登录时候的账号和密码).
- 可选参数: 可以自由选择是否告诉给服务器的参数.
- 典型应用:
- 新浪微博: 上传图片的同时,发送一条微博信息!
- 购物评论: 购买商品之后发表评论的时候图片+评论内容!
- 多个参数之间以 & 分割.参数是'无序'的.
- 大公司: 能够附带参数,就会尽量多的附带参数.网络监测大数据开发/页面检测都必须由客户端发送参数给服务器.
普通参数的格式如下:
-----------------------------16778832101575341713442286528
Content-Disposition: form-data; name="username"
Wpf
普通参数添加的位置:最后一个文件的二进制内容
与下边界
之间
使用一个可变字符串连接所有参数的全部信息(上边界和具体内容),然后统一转化为二进制形式
- 设置第一个参数的上边界
NSMutableString *parameterStr = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服务器接收普通文本参数的key值.后端人员告诉我们.
// 文本参数也有可能有多个...
[parameterStr appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", @"username"];
- 设置第一个参数的内容
[parameterStr appendString:@"WangPengfei"];
- 设置第二个参数的上边界
[parameterStr appendFormat:@"\r\n--%@\r\n", kBoundary];
[parameterStr appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", @"password"];
- 设置第二个参数的内容
[parameterStr appendString:@"123321"];
- 统一转化为二进制形式
[data appendData:[parameterStr dataUsingEncoding:NSUTF8StringEncoding]];
六. 简单封装
1. 将文件名和文件地址
、参数key值和参数具体值
分别封装为字典
- 设置文件字典
// 设置上传文件在服务器存储的名称
NSString *name1 = @"photo.jpg";
NSString *name2 = @"math.h";
NSString *name3 = @"video.json";
// 设置文件地址
NSString *filePath1 = @"/Users/wangpengfei/Desktop/IMG_5097.jpg";
NSString *filePath2 = @"/Users/wangpengfei/Desktop/Math.m";
NSString *filePath3 = @"/Users/wangpengfei/Desktop/vedios.json";
NSDictionary *fileDict = @{
name1:filePath1,
name2:filePath2,
name3:filePath3
};
- 设置普通文本参数字典
NSDictionary *parameters = @{
@"username":@"wpf",
@"password":@"12300",
@"age":@"24"
};
2. 方法的封装
- 方法名
- (NSData *)getHttpBodyWithFileKey:(NSString *)fileKey fileDict:(NSDictionary *)fileDict parameters:(NSDictionary *)parameters
- 遍历文件字典
// 遍历文件参数字典,取出文件字典中的 key值 和 value 值
[fileDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 上传文件在服务器中保存的名称
NSString *fileName = key;
// 上传文件在本地的路径
NSString *filePath = obj;
// 上传文件的请求体格式
// 1. 文件的上边界
// 1.1 获取文件上边界的字符串
NSMutableString *headerStrM1 = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
[headerStrM1 appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", fileKey, fileName];
[headerStrM1 appendFormat:@"Content-Type: %@\r\n\r\n", @"application/octet-stream"];
// 1.2 将字符串转为二进制数据,并添加到请求体中
[data appendData:[headerStrM1 dataUsingEncoding:NSUTF8StringEncoding]];
// 2. 获取文件的二进制数据,并添加到请求体中
[data appendData:[NSData dataWithContentsOfFile:filePath]];
}];
- 遍历普通文本参数字典
[parameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 参数格式:
// -----------------------------16778832101575341713442286528
// Content-Disposition: form-data; name="username"
//
// Wpf
NSString *parameterKey = key;
NSString *parameterValue = obj;
// 1. 普通参数的上边界
NSMutableString *parameterStr = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服务器接收普通文本参数的key值.后端人员告诉我们.
// 文本参数也有可能有多个...
[parameterStr appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", parameterKey];
// 2. 第一个普通参数的内容
[parameterStr appendString:parameterValue];
[data appendData:[parameterStr dataUsingEncoding:NSUTF8StringEncoding]];
}];
如果在 iOS 中,要实现POST上传文件,需要按照上述格式,拼接数据!
因为:格式是 W3C 指定的标准格式,苹果没有做任何封装!其他语言,都做了封装!
3. 方法的调用
- 设置请求体
request.HTTPBody = [self getHttpBodyWithFileKey:@"userfile[]" fileDict:fileDict parameters:parameters];
七. POST多文件上传方法的封装
- 基本参数
- urlString:网络接口
- fileKey:服务器接收用户上传文件的key值
- fileDict:文件字典
- parameters:普通文本参数的字典
- success:上传成功时调用的block
- fail:上传失败时调用的block
- 方法名
- (void)POSTMoreFileWithUrlString:(NSString *)urlString
fileKey:(NSString *)fileKey fileDict:(NSDictionary *)fileDict
parameters:(NSDictionary *)parameters
success:(successBlock)success fail:(failBlock)fail;
- 其他改动同POST单文件的深入封装-上传封装
八. AFN-第三方框架的使用
- AFN 能够同时实现上传
一个文件
,有些格式的文件,用 AFN 无法上传! - ASI 能够同时实现上传多个文件,MRC的,2012年就停止更新了,设计的目标平台, iOS 2.0/iOS 3.0 !
1. 上传文件
// 1. 创建管理者
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
// 2. 发送请求
[mgr POST:@"http://localhost/upload/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// formData :设置上传文件所需要的参数,两种上传方法:
// <1> 通过本地文件的 url 上传
{
NSString *fromFile = @"/Users/wangpengfei/Desktop/meinv.jpg";
NSURL *url = [NSURL URLWithString:@"file:///Users/wangpengfei/Desktop/IMG_5544.jpg"];
// url :需要上传文件的文件路径
// name :服务器接收的文件名.
// fileName: 文件在服务器中保存的名字
// mimeType : 文件类型
[formData appendPartWithFileURL:url name:@"userfile" fileName:@"beauty.jpg" mimeType:@"image/jpg" error:NULL];
}
// <2> 通过文件的 二进制数据 上传
{
NSData *data = [NSData dataWithContentsOfFile:zipFile];
[formData appendPartWithFileData:data name:@"userfile" fileName:@"beauty.zip" mimeType:@"gzip"];
}
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
// 上传成功之后的回调
NSLog(@"%@",responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 上传失败之后的回调
NSLog(@"error-->%@", error);
}];
2. 监测网络状态
// 创建 网络状态管理者
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
// 监测网络状态的改变
[mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
// 当网络状态发生改变的时候调用这个block
switch (status) {
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WIFI网络");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"蜂窝网络");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"没有网络");
break;
case AFNetworkReachabilityStatusUnknown:
NSLog(@"未知网络");
break;
default:
break;
}
}];
// 开始监控
[mgr startMonitoring];
3. Reachability 监测网络状态(第三方框架)
- 注册通知观察者,网络状态改变时,接收通知!
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(InternetStatusChanged) name:kReachabilityChangedNotification object:nil];
// 控制器销毁时,移除通知观察者.
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- 根据当前网络状态,做出不同的响应.
- (void)InternetStatusChanged
{
NSLog(@"网络状态改变了");
if ([Reachability reachabilityForLocalWiFi].currentReachabilityStatus == ReachableViaWiFi) {
NSLog(@"Wifi 网络");
}
if ([Reachability reachabilityForInternetConnection].currentReachabilityStatus == ReachableViaWWAN) {
NSLog(@"蜂窝移动网络");
}
if ([Reachability reachabilityForInternetConnection].currentReachabilityStatus == NotReachable)
{
NSLog(@"没有网络");
}
}
- 创建 Reachability 对象,开始监测网络状态的改变
- (void)MonitorInternetStatus
{
Reachability *reachability = [Reachability reachabilityForInternetConnection];
[reachability startNotifier];
self.reachability = reachability;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self MonitorInternetStatus];
}