iOS开发之NSURLSessionUploadTask上传数据

ChristyDalg 8年前
   <p>苹果在 iOS9 之后已经废弃了 NSURLConnection , NSURLSession 成为其替代者,其基本知识网上很多,主要可以从 NSURLSessionDataTask 、 NSURLSessionDownloadTask 和 NSURLSessionUploadTask 入手学习。最近在写案例时发现其中的 NSURLSessionUploadTask 还是有着不少的坑,在开发时有时候很难一次性成功。所以将研究的过程记录与分享一下。我会以一个完整的案例来讲解如何使用。</p>    <p><strong>服务器开发</strong></p>    <p><strong>环境:IDEA 14 +Tomcat 8.x+JDK 8</strong></p>    <p><strong>1、编写服务器端代码</strong></p>    <p>由于上传数据与下载数据不同,下载的时候只要把数据丢进服务器就可以了。但是上传需要服务器自己来处理。所以以 Java Servlet 来写服务器端,由于Servlet 3.0 以后可以直接处理文件上传,所以相对比较简单,代码如下,注释很详细。</p>    <pre>  <code class="language-objectivec">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("上传成功");      }    }</code></pre>    <p><strong>2、部署代码</strong></p>    <p>找到Tomcat根目录下的conf文件夹,打开server.xml,在最后加上一行代码,path就是访问的项目路径,docBase就是项目编译后的位置。</p>    <pre>  <code class="language-objectivec"><Context path="/AppTestAPI" docBase="E:\AppTestAPI\out\artifacts\AppTestAPI" auth="Container" /></code></pre>    <p><strong>3、启动服务器</strong></p>    <p>通过浏览器访问 http://localhost 能出现如下的界面,至此完成服务器端工作。( 由于我修改了默认端口,所以没有加8080 )</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/19306d0855a4ea8e6168b54087e2a381.png"></p>    <p style="text-align:center">Paste_Image.png</p>    <p><strong>客户端开发</strong></p>    <p><strong>环境:Xcode 7.3.1</strong></p>    <p><strong>1、创建项目</strong></p>    <p>创建一个项目,布置界面,设置支持http网络访问。主界面如下:</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f9466e866b98a0c2b6ddab5a27968c18.png"></p>    <p style="text-align:center">界面.png</p>    <p><strong>2、编写代码</strong></p>    <p>NSURLSession使用都是一个套路:创建请求,创建任务,执行任务,成功回调。但是在使用NSURLSessionUploadTask进行上传时最麻烦的是上传数据的构造,其遵循严格的规范,如下图,不能随意书写,不能随意书写,不能随意书写~,重要的事情说三遍,否则坑得你生活不能自理。</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/1f5510b37062c73d6dacfa15021ec074.png"></p>    <p style="text-align:center">上传必填字段.png</p>    <p>下面是ViewController的代码,其中最核心的是 getData 方法。</p>    <pre>  <code class="language-objectivec">#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</code></pre>    <p><strong>最终效果</strong></p>    <p>先看客户端的表象</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/216b8692cdcd9507e0bcbe3e853bc38e.gif"></p>    <p style="text-align:center">客户端演示.gif</p>    <p>再看服务器端最终上传的数据,不重要以为是图片,静静观察一会儿~~~有变化</p>    <p style="text-align:center"><img src="https://simg.open-open.com/show/b58d99bc5d7df94ec0be7a30ce39bc75.gif"></p>    <p style="text-align:center">服务器端.gif</p>    <p> </p>    <p>来自:http://www.jianshu.com/p/f0a9c47167fd</p>    <p> </p>