一. POST單文件上傳-簡單使用
1. 創(chuàng)建請求
- 實(shí)例化請求并設(shè)置基本參數(shù)
// 0. 獲取服務(wù)器端口的地址
NSURL *url = [NSURL URLWithString:@"http://localhost/upload/upload.php"];
#warning:對于POST請求缘厢,必須手動設(shè)置其請求方法,因此要使用可變請求
// 1. 創(chuàng)建請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// 2. 設(shè)置請求方式
request.HTTPMethod = @"POST";
// 3. 告訴服務(wù)器本次上傳文件的相關(guān)信息
// 固定格式: 設(shè)置Content-Type
// Content-Type: multipart/form-data; boundary=---------------------------198596859919834017191791522499
// Content-Type:本次上傳文件類型信息,包含boundary
// boundary:本次上傳文件的邊界(自己隨意設(shè)置垃环,只要三個地方一致即可)
NSString *type = [NSString stringWithFormat:@"multipart/form-data; boundary=%@", kBoundary];
[request setValue:type forHTTPHeaderField:@"Content-Type"];
-
重難點(diǎn)
: 設(shè)置請求體筒捺,分為三個部分-
上邊界部分,告訴服務(wù)器要做數(shù)據(jù)上傳,包含了
-
userfile
-> 負(fù)責(zé)上傳文件腳本中的 字段名,開發(fā)的時候,可以咨詢后端程序員 -
filename
-> 將文件保存在服務(wù)器上的文件名稱 -
Content-Type
-> 客戶端告訴服務(wù)器上傳文件的文件類型(如果不想寫文件類型,統(tǒng)一用 application/octet-stream[8進(jìn)制流])
-
上傳文件的數(shù)據(jù)部分(即文件內(nèi)容的二進(jìn)制數(shù)據(jù))
下邊界部分,嚴(yán)格按照字符串格式來設(shè)置:--boundary--
-
// 實(shí)例化請求體
NSMutableData *data = [NSMutableData data];
// -----------------------------198596859919834017191791522499
// Content-Disposition: form-data; name="userfile"; filename="WPFNetWorkTool.h"
// Content-Type: application/octet-stream
#warning 有些服務(wù)器可以直接使用 \n回窘,但是新浪微博如果使用 \n 上傳文件坡氯,服務(wù)器會返回“沒有權(quán)限”的錯誤! 因此一定要注意安全換行:\r\n
// 1. 拼接上傳文件的上邊界信息
NSMutableString *headerStrM = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服務(wù)器接收參數(shù)的key值帜羊,后臺工作人員告訴我們
// filename=%@ :文件上傳到服務(wù)器的存儲名咒程,若不設(shè)置則為默認(rèn)名,名稱保持不變
[headerStrM appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", @"userfile", @"123" ];
// Content-Type: application/octet-stream 表明文件的上傳類型讼育,亂寫類型不會影響上傳帐姻,但是不符合規(guī)范
[headerStrM appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
// 將上傳文件的上邊界信息添加到請求體中
[data appendData:[headerStrM dataUsingEncoding:NSUTF8StringEncoding]];
// 2. 設(shè)置文件內(nèi)容
// 文件地址
NSString *filePath = @"/Users/wangpengfei/Desktop/WPFNetWorkTool.h";
// 將文件轉(zhuǎn)化為二進(jìn)制形式
NSData *fileData = [NSData dataWithContentsOfFile:filePath];
// 將文件內(nèi)容添加到請求體中
[data appendData:fileData];
// 3. 設(shè)置文件的下邊界
// -----------------------------198596859919834017191791522499--
NSString *footerStrM = [NSString stringWithFormat:@"\r\n--%@--", kBoundary];
NSLog(@"footerStrM--->%@", footerStrM);
// 將下邊界添加到請求體中
[data appendData:[footerStrM dataUsingEncoding:NSUTF8StringEncoding]];
// 4. 設(shè)置請求體
request.HTTPBody = data;
常見的 Content-Type 類型:
大類型 | 小類型 |
---|---|
image | png |
image | jpg |
image | gif |
text | html |
application | json |
2. 發(fā)送請求
[[[NSURLSession sharedSession] dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(@"%@", [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
/*
打印結(jié)果:
{"userfile":{"name":"123","type":"application\/octet-stream","tmp_name":"\/private\/var\/tmp\/phpEk0KCK","error":0,"size":0}}
*/
}] resume];
二. POST單文件上傳-簡單封裝結(jié)構(gòu)體
1. 獲得本地文件響應(yīng)頭信息,使用同步方法*重難點(diǎn)*
通過響應(yīng)頭信息,可以獲得文件的類型/長度/建議的名稱.
-
MIMEType
:就是文件類型 -
suggestedFilename
: 推薦文件名(本地存儲名) -
expectedContentLength
: 文件長度
如果文件比較大,不建議發(fā)送本地請求.發(fā)送本地請求,會將文件從沙盒中加載到內(nèi)存中,造成內(nèi)存開銷.
- (NSURLResponse *)getFileResponseWithFilePath:(NSString *)filePath {
// 動態(tài)獲取文件類型
// 1. 獲取文件路徑奶段,根據(jù)路徑獲取 url 地址饥瓷,本地協(xié)議名 file://
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:@"file://%@", filePath]];
// 2. 創(chuàng)建請求
NSURLRequest *request = [NSURLRequest requestWithURL:url];
// NSURLSession 沒有同步請求的方法
// 利用 NSURLConnection 發(fā)送同步請求
// 定義一片空的地址
NSURLResponse *response = nil;
// &response 二級指針
[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:NULL];
return response;
}
2. 封裝請求體格式
-
filePath
: 上傳文件的路徑 -
fileKey
: 服務(wù)器接受文件的 key 值 -
fileName
: 上傳文件在服務(wù)器中保存的名稱(可選)
- (NSData *)setupHttpBodyWithFilePath:(NSString *)filePath fileKey:(NSString *)fileKey fileName:(NSString *)fileName;
- 當(dāng)用戶沒有設(shè)置fileName時,調(diào)用方法一:設(shè)置文件為默認(rèn)名
// 調(diào)用獲得本地文件信息的方法
NSURLResponse *response = [self getFileResponseWithFilePath:filePath];
if (!fileName) {
fileName = response.suggestedFilename;
}
三. POST單文件上傳封裝
1. 取出已封裝好的單例類WPFNetWorkTool
,封裝以下方法
- urlString:網(wǎng)絡(luò)接口
- filePath:需要上傳的文件路徑
- fileKey:服務(wù)器接收用戶上傳文件的key值
- fileName:上傳文件存儲到服務(wù)器的名稱
- success:上傳成功時調(diào)用的block
- fail:上傳失敗時調(diào)用的block
- (void)POSTFileWithUrlString:(NSString *)urlString
filePath:(NSString *)filePath fileKey:(NSString *)fileKey
fileName:(NSString *)fileName
success:(successBlock)success fail:(failBlock)fail;
2. 在發(fā)送請求方法中增加以下內(nèi)容
[[[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數(shù)據(jù)
if (!responseObj) {
responseObj = data;
}
// 執(zhí)行回調(diào)
if (success) {
success(responseObj, response);
}
// 失敗
} else {
// 執(zhí)行回調(diào)
if (fail) {
fail(error);
}
}
}] resume];
3. 封裝方法的調(diào)用
// 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程序文件不能顯示
/*
打印結(jié)果:
obj--->{
userfile = {
error = 0;
name = "(null)";
size = 1702;
"tmp_name" = "/private/var/tmp/phposR0Tv";
type = "application/octet-stream";
};
}
*/
四. POST多文件上傳-簡單使用
多文件上傳和單文件上傳的基本思路是一樣的,唯一的區(qū)別在于對請求體的封裝
.
-
多文件上傳的請求體格式
// 第一個文件上邊界及參數(shù) \r\n--boundary\r\n Content-Disposition: form-data; name=userfile[]; filename=美女\r\n Content-Type:image/jpeg\r\n\r\n 第一個文件的二進(jìn)制數(shù)據(jù)部分 // 第二個文件上邊界及參數(shù) \r\n--boundary\r\n Content-Disposition: form-data; name=userfile[]; filename=JSON\r\n Content-Type:text/plain\r\n\r\n 第二個文件的二進(jìn)制數(shù)據(jù)部分 // 下邊界 \r\n--boundary--
1.設(shè)置第一個文件的上邊界及參數(shù)
NSMutableString *headerStrM1 = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服務(wù)器接收參數(shù)的key值呢铆,后臺工作人員告訴我們
// filename=%@ :文件上傳到服務(wù)器的存儲名,若不設(shè)置則為默認(rèn)名蹲缠,名稱保持不變
#warning @"userfile[]"后臺人員提供的數(shù)據(jù)
[headerStrM1 appendFormat:@"Content-Disposition: form-data; name=%@; filename=%@\r\n", @"userfile[]", @"234" ];
// Content-Type: application/octet-stream 表明文件的上傳類型棺克,亂寫類型不會影響上傳悠垛,但是不符合規(guī)范
[headerStrM1 appendString:@"Content-Type: application/octet-stream\r\n\r\n"];
// 將上傳文件的上邊界信息添加到請求體中
[data appendData:[headerStrM1 dataUsingEncoding:NSUTF8StringEncoding]];
- 2.設(shè)置第一個文件的二進(jìn)制數(shù)據(jù)
// 文件地址
NSString *filePath1 = @"/Users/wangpengfei/Desktop/photo/IMG_5544.jpg";
// 將文件轉(zhuǎn)化為二進(jìn)制形式
NSData *fileData1 = [NSData dataWithContentsOfFile:filePath1];
// 將文件內(nèi)容添加到請求體中
[data appendData:fileData1];
- 3.設(shè)置第二個文件的上邊界及參數(shù)
NSMutableString *headerStrM2 = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服務(wù)器接收參數(shù)的key值,后臺工作人員告訴我們
// filename=%@ :文件上傳到服務(wù)器的存儲名娜谊,若不設(shè)置則為默認(rèn)名确买,名稱保持不變
[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.設(shè)置第二個文件的二進(jìn)制數(shù)據(jù)
// 文件地址
NSString *filePath2 = @"/Users/wangpengfei/Desktop/photo/beauty1.jpg";
// 將文件轉(zhuǎn)化為二進(jìn)制形式
NSData *fileData2 = [NSData dataWithContentsOfFile:filePath2];
// 將文件內(nèi)容添加到請求體中
[data appendData:fileData2];
- 5.設(shè)置下邊界
NSString *footerStrM = [NSString stringWithFormat:@"\r\n--%@--", kBoundary];
// 將下邊界添加到請求體中
[data appendData:[footerStrM dataUsingEncoding:NSUTF8StringEncoding]];
五. POST多文件上傳-添加普通參數(shù)
- 有些網(wǎng)絡(luò)請求,客戶端需要告訴服務(wù)器一些必要的數(shù)據(jù),服務(wù)器根據(jù)客戶端傳過來的數(shù)據(jù)(參數(shù))去數(shù)據(jù)庫檢索出對應(yīng)的數(shù)據(jù).返回給客戶端.
- 必須參數(shù): 必須附帶的參數(shù)(登錄時候的賬號和密碼).
- 可選參數(shù): 可以自由選擇是否告訴給服務(wù)器的參數(shù).
- 典型應(yīng)用:
- 新浪微博: 上傳圖片的同時,發(fā)送一條微博信息!
- 購物評論: 購買商品之后發(fā)表評論的時候圖片+評論內(nèi)容!
- 多個參數(shù)之間以 & 分割.參數(shù)是'無序'的.
- 大公司: 能夠附帶參數(shù),就會盡量多的附帶參數(shù).網(wǎng)絡(luò)監(jiān)測大數(shù)據(jù)開發(fā)/頁面檢測都必須由客戶端發(fā)送參數(shù)給服務(wù)器.
普通參數(shù)的格式如下:
-----------------------------16778832101575341713442286528
Content-Disposition: form-data; name="username"
Wpf
普通參數(shù)添加的位置:最后一個文件的二進(jìn)制內(nèi)容
與下邊界
之間
使用一個可變字符串連接所有參數(shù)的全部信息(上邊界和具體內(nèi)容),然后統(tǒng)一轉(zhuǎn)化為二進(jìn)制形式
- 設(shè)置第一個參數(shù)的上邊界
NSMutableString *parameterStr = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服務(wù)器接收普通文本參數(shù)的key值.后端人員告訴我們.
// 文本參數(shù)也有可能有多個...
[parameterStr appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", @"username"];
- 設(shè)置第一個參數(shù)的內(nèi)容
[parameterStr appendString:@"WangPengfei"];
- 設(shè)置第二個參數(shù)的上邊界
[parameterStr appendFormat:@"\r\n--%@\r\n", kBoundary];
[parameterStr appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", @"password"];
- 設(shè)置第二個參數(shù)的內(nèi)容
[parameterStr appendString:@"123321"];
- 統(tǒng)一轉(zhuǎn)化為二進(jìn)制形式
[data appendData:[parameterStr dataUsingEncoding:NSUTF8StringEncoding]];
六. 簡單封裝
1. 將文件名和文件地址
纱皆、參數(shù)key值和參數(shù)具體值
分別封裝為字典
- 設(shè)置文件字典
// 設(shè)置上傳文件在服務(wù)器存儲的名稱
NSString *name1 = @"photo.jpg";
NSString *name2 = @"math.h";
NSString *name3 = @"video.json";
// 設(shè)置文件地址
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
};
- 設(shè)置普通文本參數(shù)字典
NSDictionary *parameters = @{
@"username":@"wpf",
@"password":@"12300",
@"age":@"24"
};
2. 方法的封裝
- 方法名
- (NSData *)getHttpBodyWithFileKey:(NSString *)fileKey fileDict:(NSDictionary *)fileDict parameters:(NSDictionary *)parameters
- 遍歷文件字典
// 遍歷文件參數(shù)字典拇惋,取出文件字典中的 key值 和 value 值
[fileDict enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 上傳文件在服務(wù)器中保存的名稱
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 將字符串轉(zhuǎn)為二進(jìn)制數(shù)據(jù),并添加到請求體中
[data appendData:[headerStrM1 dataUsingEncoding:NSUTF8StringEncoding]];
// 2. 獲取文件的二進(jìn)制數(shù)據(jù)抹剩,并添加到請求體中
[data appendData:[NSData dataWithContentsOfFile:filePath]];
}];
- 遍歷普通文本參數(shù)字典
[parameters enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
// 參數(shù)格式:
// -----------------------------16778832101575341713442286528
// Content-Disposition: form-data; name="username"
//
// Wpf
NSString *parameterKey = key;
NSString *parameterValue = obj;
// 1. 普通參數(shù)的上邊界
NSMutableString *parameterStr = [NSMutableString stringWithFormat:@"\r\n--%@\r\n", kBoundary];
// name=%@ :服務(wù)器接收普通文本參數(shù)的key值.后端人員告訴我們.
// 文本參數(shù)也有可能有多個...
[parameterStr appendFormat:@"Content-Disposition: form-data; name=%@\r\n\r\n", parameterKey];
// 2. 第一個普通參數(shù)的內(nèi)容
[parameterStr appendString:parameterValue];
[data appendData:[parameterStr dataUsingEncoding:NSUTF8StringEncoding]];
}];
如果在 iOS 中,要實(shí)現(xiàn)POST上傳文件,需要按照上述格式,拼接數(shù)據(jù)!
因?yàn)?格式是 W3C 指定的標(biāo)準(zhǔn)格式,蘋果沒有做任何封裝!其他語言,都做了封裝!
3. 方法的調(diào)用
- 設(shè)置請求體
request.HTTPBody = [self getHttpBodyWithFileKey:@"userfile[]" fileDict:fileDict parameters:parameters];
七. POST多文件上傳方法的封裝
- 基本參數(shù)
- urlString:網(wǎng)絡(luò)接口
- fileKey:服務(wù)器接收用戶上傳文件的key值
- fileDict:文件字典
- parameters:普通文本參數(shù)的字典
- success:上傳成功時調(diào)用的block
- fail:上傳失敗時調(diào)用的block
- 方法名
- (void)POSTMoreFileWithUrlString:(NSString *)urlString
fileKey:(NSString *)fileKey fileDict:(NSDictionary *)fileDict
parameters:(NSDictionary *)parameters
success:(successBlock)success fail:(failBlock)fail;
- 其他改動同POST單文件的深入封裝-上傳封裝
八. AFN-第三方框架的使用
- AFN 能夠同時實(shí)現(xiàn)上傳
一個文件
,有些格式的文件,用 AFN 無法上傳! - ASI 能夠同時實(shí)現(xiàn)上傳多個文件,MRC的,2012年就停止更新了,設(shè)計(jì)的目標(biāo)平臺, iOS 2.0/iOS 3.0 !
1. 上傳文件
// 1. 創(chuàng)建管理者
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
// 2. 發(fā)送請求
[mgr POST:@"http://localhost/upload/upload.php" parameters:nil constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {
// formData :設(shè)置上傳文件所需要的參數(shù),兩種上傳方法:
// <1> 通過本地文件的 url 上傳
{
NSString *fromFile = @"/Users/wangpengfei/Desktop/meinv.jpg";
NSURL *url = [NSURL URLWithString:@"file:///Users/wangpengfei/Desktop/IMG_5544.jpg"];
// url :需要上傳文件的文件路徑
// name :服務(wù)器接收的文件名.
// fileName: 文件在服務(wù)器中保存的名字
// mimeType : 文件類型
[formData appendPartWithFileURL:url name:@"userfile" fileName:@"beauty.jpg" mimeType:@"image/jpg" error:NULL];
}
// <2> 通過文件的 二進(jìn)制數(shù)據(jù) 上傳
{
NSData *data = [NSData dataWithContentsOfFile:zipFile];
[formData appendPartWithFileData:data name:@"userfile" fileName:@"beauty.zip" mimeType:@"gzip"];
}
} success:^(AFHTTPRequestOperation *operation, id responseObject) {
// 上傳成功之后的回調(diào)
NSLog(@"%@",responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
// 上傳失敗之后的回調(diào)
NSLog(@"error-->%@", error);
}];
2. 監(jiān)測網(wǎng)絡(luò)狀態(tài)
// 創(chuàng)建 網(wǎng)絡(luò)狀態(tài)管理者
AFNetworkReachabilityManager *mgr = [AFNetworkReachabilityManager sharedManager];
// 監(jiān)測網(wǎng)絡(luò)狀態(tài)的改變
[mgr setReachabilityStatusChangeBlock:^(AFNetworkReachabilityStatus status) {
// 當(dāng)網(wǎng)絡(luò)狀態(tài)發(fā)生改變的時候調(diào)用這個block
switch (status) {
case AFNetworkReachabilityStatusReachableViaWiFi:
NSLog(@"WIFI網(wǎng)絡(luò)");
break;
case AFNetworkReachabilityStatusReachableViaWWAN:
NSLog(@"蜂窩網(wǎng)絡(luò)");
break;
case AFNetworkReachabilityStatusNotReachable:
NSLog(@"沒有網(wǎng)絡(luò)");
break;
case AFNetworkReachabilityStatusUnknown:
NSLog(@"未知網(wǎng)絡(luò)");
break;
default:
break;
}
}];
// 開始監(jiān)控
[mgr startMonitoring];
3. Reachability 監(jiān)測網(wǎng)絡(luò)狀態(tài)(第三方框架)
- 注冊通知觀察者,網(wǎng)絡(luò)狀態(tài)改變時,接收通知!
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(InternetStatusChanged) name:kReachabilityChangedNotification object:nil];
// 控制器銷毀時,移除通知觀察者.
-(void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- 根據(jù)當(dāng)前網(wǎng)絡(luò)狀態(tài),做出不同的響應(yīng).
- (void)InternetStatusChanged
{
NSLog(@"網(wǎng)絡(luò)狀態(tài)改變了");
if ([Reachability reachabilityForLocalWiFi].currentReachabilityStatus == ReachableViaWiFi) {
NSLog(@"Wifi 網(wǎng)絡(luò)");
}
if ([Reachability reachabilityForInternetConnection].currentReachabilityStatus == ReachableViaWWAN) {
NSLog(@"蜂窩移動網(wǎng)絡(luò)");
}
if ([Reachability reachabilityForInternetConnection].currentReachabilityStatus == NotReachable)
{
NSLog(@"沒有網(wǎng)絡(luò)");
}
}
- 創(chuàng)建 Reachability 對象,開始監(jiān)測網(wǎng)絡(luò)狀態(tài)的改變
- (void)MonitorInternetStatus
{
Reachability *reachability = [Reachability reachabilityForInternetConnection];
[reachability startNotifier];
self.reachability = reachability;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self MonitorInternetStatus];
}