iOS開發之NSURLSessionUploadTask上傳數據
蘋果在 iOS9 之后已經廢棄了 NSURLConnection , NSURLSession 成為其替代者,其基本知識網上很多,主要可以從 NSURLSessionDataTask 、 NSURLSessionDownloadTask 和 NSURLSessionUploadTask 入手學習。最近在寫案例時發現其中的 NSURLSessionUploadTask 還是有著不少的坑,在開發時有時候很難一次性成功。所以將研究的過程記錄與分享一下。我會以一個完整的案例來講解如何使用。
服務器開發
環境:IDEA 14 +Tomcat 8.x+JDK 8
1、編寫服務器端代碼
由于上傳數據與下載數據不同,下載的時候只要把數據丟進服務器就可以了。但是上傳需要服務器自己來處理。所以以 Java Servlet 來寫服務器端,由于Servlet 3.0 以后可以直接處理文件上傳,所以相對比較簡單,代碼如下,注釋很詳細。
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
@MultipartConfig //標識Servlet支持文件上傳
public class UploadServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//存儲路徑為我們的根目錄
String storePath = req.getServletContext().getRealPath("/");
//獲取part對象,參數為客戶端表單中的name屬性的值
Part part = req.getPart("myfile");
//Servlet3沒有提供直接獲取文件名的方法,需要從請求頭中解析出來
//獲取請求頭
String header = part.getHeader("Content-Disposition");
//獲取文件名
String fileName = header.substring(header.lastIndexOf("=") + 2, header.length() - 1);
//把文件寫到指定路徑
part.write(storePath + File.separator + fileName);
//回寫數據給客戶端
resp.setCharacterEncoding("UTF-8");
PrintWriter pw = resp.getWriter();
pw.print("上傳成功");
}
}
2、部署代碼
找到Tomcat根目錄下的conf文件夾,打開server.xml,在最后加上一行代碼,path就是訪問的項目路徑,docBase就是項目編譯后的位置。
<Context path="/AppTestAPI" docBase="E:\AppTestAPI\out\artifacts\AppTestAPI" auth="Container" />
3、啟動服務器
通過瀏覽器訪問 http://localhost 能出現如下的界面,至此完成服務器端工作。( 由于我修改了默認端口,所以沒有加8080 )
Paste_Image.png
客戶端開發
環境:Xcode 7.3.1
1、創建項目
創建一個項目,布置界面,設置支持http網絡訪問。主界面如下:
界面.png
2、編寫代碼
NSURLSession使用都是一個套路:創建請求,創建任務,執行任務,成功回調。但是在使用NSURLSessionUploadTask進行上傳時最麻煩的是上傳數據的構造,其遵循嚴格的規范,如下圖,不能隨意書寫,不能隨意書寫,不能隨意書寫~,重要的事情說三遍,否則坑得你生活不能自理。
上傳必填字段.png
下面是ViewController的代碼,其中最核心的是 getData 方法。
#import "ViewController.h"
//分隔符
#define YFBoundary @"AnHuiWuHuYungFan"
//換行
#define YFEnter [@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]
//NSString轉NSData
#define YFEncode(string) [string dataUsingEncoding:NSUTF8StringEncoding]
@interface ViewController ()<NSURLSessionTaskDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *uploadProgress;
@property (weak, nonatomic) IBOutlet UILabel *uploadInfo;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//1、確定URL
NSURL *url = [NSURL URLWithString:@"http://192.168.0.5/AppTestAPI/UploadServlet"];
//2、確定請求
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//3、設置請求頭
NSString *head = [NSString stringWithFormat:@"multipart/form-data;boundary=%--@", YFBoundary];
[request setValue:head forHTTPHeaderField:@"Content-Type"];
//4、設置請求方式,上傳時必須是Post請求
request.HTTPMethod = @"POST";
//5、創建NSURLSession
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
//6、獲取上傳的數據
NSData *uploadData = [self getData];
//7、創建上傳任務 上傳的數據來自getData方法
NSURLSessionUploadTask *task = [session uploadTaskWithRequest:request fromData:uploadData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
//上傳成功以后改變UILabel文本為服務器返回的數據
self.uploadInfo.text =[[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
}];
//8、執行上傳任務
[task resume];
}
/**
* 設置請求體
*
* @return 請求體內容
*/
-(NSData *)getData
{
NSMutableData *data = [NSMutableData data];
//1、開始標記
//--
[data appendData:YFEncode(@"--")];
//boundary
[data appendData:YFEncode(YFBoundary)];
//換行符
[data appendData:YFEnter];
//文件參數名 Content-Disposition: form-data; name="myfile"; filename="wall.jpg"
[data appendData:YFEncode(@"Content-Disposition:form-data; name=\"myfile\"; filename=\"wall.jpg\"")];
//換行符
[data appendData:YFEnter];
//Content-Type 上傳文件的類型 MIME
[data appendData:YFEncode(@"Content-Type:image/jpeg")];
//換行符
[data appendData:YFEnter];
//換行符
[data appendData:YFEnter];
//2、上傳的文件數據
//圖片數據 并且轉換為Data
UIImage *image = [UIImage imageNamed:@"wall.jpg"];
NSData *imagedata = UIImageJPEGRepresentation(image, 1.0);
[data appendData:imagedata];
//換行符
[data appendData:YFEnter];
//3、結束標記
//--
[data appendData:YFEncode(@"--")];
//boundary
[data appendData:YFEncode(YFBoundary)];
//--
[data appendData:YFEncode(@"--")];
//換行符
[data appendData:YFEnter];
return data;
}
#pragma mark - 代理方法 只要給服務器上傳數據就會調用 可以計算出上傳進度
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didSendBodyData:(int64_t)bytesSent totalBytesSent:(int64_t)totalBytesSent totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
//設置進度條
self.uploadProgress.progress = 1.0 * totalBytesSent / totalBytesExpectedToSend;
}
@end
最終效果
先看客戶端的表象
客戶端演示.gif
再看服務器端最終上傳的數據,不重要以為是圖片,靜靜觀察一會兒~~~有變化
服務器端.gif
來自:http://www.jianshu.com/p/f0a9c47167fd