【转载】NSURLSession教程

 

原文:http://www.raywenderlich.com/51127/nsurlsession-tutorial

 

 

查理·富尔顿

NSURLSession Tutorial

注意从雷 :这是一个缩写版的一章 iOS 7教程 我们发布的一部分 iOS 7盛宴 。 我们希望你能喜欢!

每一个新的iOS版本包含了一些很棒的新的网络api,和iOS 7也不例外。 在iOS 7中,苹果推出了 NSURLSession ,这是一套类取代 NSURLConnection 作为网络的首选方法。

在这NSURLSession教程中,您将学习这个新类是什么,为什么和应该如何使用它,如何比较之前的,最重要的是:得到实践整合成一个真正的应用程序!

请注意,本教程假设您熟悉基本的网络概念。 如果你是全新的网络,你仍然可以跟随一切都是一步一步的,但可能会有一些不熟悉的概念,你可能需要查找。

为什么使用NSURLSession ?

你为什么要使用 NSURLSession 吗? 它带给你一些新优势和好处:

  • 后台上传和下载: 只有当一个配置选项 NSURLSession 创建,得到所有背景网络的好处。 这有助于与电池寿命,支持UIKit多任务和使用相同的委托模型作为进程内转移。
  • 暂停和恢复网络操作能力: 稍后您将看到,NSURLSession API的任何网络任务可以暂停,停止,并重新启动。 没有必要NSOperation生成子类。
  • 可配置的容器: 每一个 NSURLSession 是可配置为将请求放入容器。 例如,如果您需要设置一个HTTP头的选择你只需要这样做一次,每个请求的会话将会有相同的配置。
  • Subclassable和私人存储: NSURLSession subclassable,您可以配置一个会话使用的私有存储在每个会话的基础上。 这允许您有私人存储对象之外的全局状态。
  • 改进的认证处理: 身份验证是在特定的连接的基础上完成的。 当使用 NSURLConnection 如果身份验证发出挑战,挑战为任意请求会回来,你不会知道请求的挑战。 与 NSURLSession ,委托处理身份验证。

  • 丰富的委托模型: NSURLConnection 有一些基于异步阻塞方法,但是不能使用一个委托。 当请求使其作品或失败,即使需要身份验证。 与 NSURLSession 你可以有一个混合的方法,使用基于异步阻塞方法并设置一个委托处理身份验证。
  • 上传和下载的文件系统: 这鼓励分离数据(文件内容)的元数据(URL和设置)。

NSURLSession vs NSURLConnection

“哇,NSURLSession听起来复杂! ”,你可能会想。 “也许我会坚持我的老朋友NSURLConnection。”

别担心——使用 NSURLSession 使用其predecessory一样简单 NSURLConnection 对于简单的任务。 为一个例子,让我们看看一个例子的一个简单的网络调用JSON的最新天气在伦敦。

假设您有这NSString构造NSURL:

NSString *londonWeatherUrl =
  @"http://api.openweathermap.org/data/2.5/weather?q=London,uk";

首先是如何使这个调用时使用 NSURLConnection :

NSURLRequest *request = [NSURLRequest requestWithURL:
[NSURL URLWithString:londonWeatherUrl]];
 
[NSURLConnection sendAsynchronousRequest:request
   queue:[NSOperationQueue mainQueue]
   completionHandler:^(NSURLResponse *response,
                       NSData *data,
                       NSError *connectionError) {
      // handle response
}];

现在,让我们使用 NSURLSession 。 注意,这是最简单的方法快速使用 NSURLSession 。 后来在本教程中,您将看到如何配置会话和设置其他功能,如代表团。

NSURLSession *session = [NSURLSession sharedSession];
[[session dataTaskWithURL:[NSURL URLWithString:londonWeatherUrl]
          completionHandler:^(NSData *data,
                              NSURLResponse *response,
                              NSError *error) {
            // handle response
 
  }] resume];

请注意,您不需要指定运行队列。 除非特别指定,否则将调用后台线程。 它可能很难注意到这两个之间的差异,这是由设计。 苹果提到dataTaskWithURL旨在取代sendAsynchronousRequest NSURLConnection

所以基本上, NSURLSession 一样容易使用吗 NSURLConnection 对于简单的任务,有一组丰富的额外的功能,当你需要它。

NSURLSession vs AFNetworking

讨论网络代码没有完全不提到AFNetworking框架。 这是一个最流行的框架用于iOS / OS X,创造的辉煌Mattt汤普森。

注意: 了解更多关于AFNetworking,结帐github页面发现: https://github.com/AFNetworking/AFNetworking 。 我们也有一个教程:
http://www.raywenderlich.com/30445/afnetworking-crash-course

这就是相同的数据任务的代码看起来像使用AFNetworking 1. x:

NSURLRequest *request = [NSURLRequest requestWithURL:
                         [NSURL URLWithString:londonWeatherUrl]];
 
AFJSONRequestOperation *operation =
[AFJSONRequestOperation JSONRequestOperationWithRequest:request
    success:^(NSURLRequest *request,
              NSHTTPURLResponse *response,
              id JSON) {
    // handle response
} failure:nil];
[operation start];

使用AFNetworking的好处之一是数据类型的类来处理响应数据。 使用AFJSONRequestOperation(或类似的类XML plist)块已经成功为您解析响应并返回的数据。 与 NSURLSession 你收到NSData完成处理器,所以你需要把NSData转换成JSON或其他格式。

注意: 您可以轻松地将NSData转换成JSON使用NSJSONSerialization类中引入iOS 5。 为了了解更多,看看23章 iOS 5的教程 “使用JSON”。

所以您可能想知道如果你应该使用AFNetworking还是坚持 NSURLSession

就我个人而言,我认为最好是坚持简单的需求 NSURLSession ——这可以避免不必要的第三方依赖引入您的项目。 新代表,配置和基于任务的API的很多“缺失的功能”AFNetworking添加现在包括在内。

然而,如果你想使用2.0的一些新特性中发现AFNetworking序列化和进一步UIKit集成(除了UIImageView类别),那么很难反对使用它!

注意: 的2.0分支AFNetworking,转换到使用 NSURLSession 。 有关更多信息,请参见这篇文章:
https://github.com/afnetworking/afnetworking/wiki/afnetworking - 2.0 -迁移-指南

引入字节俱乐部

在这NSURLSession教程中,您将探索这种新的API通过构建一个笔记和图片共享应用程序之上Dropbox核心API的绝密组织命名为字节俱乐部。

所以考虑本教程你正式邀请字节俱乐部! 字节俱乐部的第一条规矩,你可能会问? 没有人谈论字节俱乐部——除了那些很酷的足以阅读本教程。 绝对不是那些Android用户;他们终身禁止。 :]

头在下一节开始构建应用程序,将作为你的起始字节俱乐部。

请注意,本教程假设您拥有一些基本的熟悉网络如果iOS在以前的版本中。 很有帮助如果你使用api NSURLConnection NSURLSession 在过去。 如果你全新的网络在iOS,您应该检查我们的iOS学徒系列初级开发者,然后再继续学习本教程。

开始

字节俱乐部是独家的iOS开发者加入分组进行编码的挑战。 因为每个成员这些挑战来自世界各地的远程工作,成员也发现它有趣分享全景照片的“战斗”。

这里有一个全景的照片光线的办公室设置,例如:

ray's office

注意: 您可能想要创建自己的全景的照片,你的办公室,很有趣,并且它将在本教程中,晚些时候派上用场。

在iOS 7中,你可以通过打开全景照片 照片 并选择标签命名 帕诺人

如果你喜欢结果,你可以把它作为你的锁定屏幕的壁纸 设置 并选择 亮度和壁纸\选择壁纸\我的全景照片

当然字节俱乐部有自己的应用程序让这一切发生。 您可以使用应用程序创建编码挑战或与其他成员分享全景照片。 在幕后,这是通过网络实现,具体地说,通过共享文件Dropbox API。

Starter项目概述

首先,下载 本教程启动项目。

这对你启动项目包括UI预制,这样你就可以专注于本教程中的网络应用程序的一部分。 starter项目还包括一些代码来处理Dropbox认证,以后你会了解更多。

打开Xcode项目和你的设备或模拟器上运行它。 您应该看到一个屏幕是这样的:

starter app login

然而,您将无法登录,您必须配置应用程序首先,你要做的。

主要的下一个开放。 故事板,看看这个应用程序的总体设计:

storyboard

这是一个基本选项卡应用与两个选项卡:一个用于代码的挑战,一个全景照片。 还有一个步骤之前,记录用户的应用程序。你设置登录在您创建Dropbox平台下面的应用程序。

随时查看应用程序的其余部分和熟悉有什么到目前为止。 你会发现除了授权组件,没有网络代码检索代码挑战或全景照片,那是你的工作!

创建一个新的Dropbox平台应用

开始与你的Dropbox应用程序,打开Dropbox位于应用程序控制台 https://www.dropbox.com/developers/apps

登录如果您有一个Dropbox帐户,但如果不是,没有汗水:创建一个免费的Dropbox帐户。 如果这是你第一次使用Dropbox API,你需要同意Dropbox条款和条件。

在法律的东西,选择创建应用程序选项。 你会面对一系列的问题——提供以下回复

  • 你想要什么类型的应用程序创建?
    • 选择: Dropbox API的应用程序
  • 你的应用程序需要什么类型的数据存储在Dropbox ?
    • 选择: 文件和数据存储
  • 你的应用可以局限于自己的,私人文件夹吗?
    • 选择: 不——我的程序需要访问文件已经在Dropbox
  • 什么类型的文件你的应用程序需要访问吗?
    • 选择: 所有的文件类型

最后,为应用程序提供一个名称,不管你选择什么,只要它是独一无二的。 Dropbox将让你知道如果你选择了一个名字,已经在使用。 您看到的屏幕应该类似如下:

dropbox app name

点击 创建应用程序 和你的路上!

在下一个屏幕上,您将看到显示屏幕包含 应用的关键 应用程序的秘密:

app key and secret

不要关闭这个屏幕;你需要应用程序下一步的关键和应用的秘密。

打开Dropbox 00 找到以下行:

#warning INSERT YOUR OWN API KEY and SECRET HERE
static NSString *apiKey = @"YOUR_KEY";
static NSString *appSecret = @"YOUR_SECRET";

填写你的应用关键和秘密,和删除#警戒线。 您可以关闭Dropbox Web应用程序页面。

接下来,创建一个文件夹的根目录主要Dropbox文件夹并命名为你的愿望。 如果你与其他Dropbox用户和共享此文件夹发送字节俱乐部的构建应用程序,他们将能够创建笔记和上传照片。

在Dropbox.m找到以下行:

#warning THIS FOLDER MUST BE CREATED AT THE TOP LEVEL OF YOUR DROPBOX FOLDER, you can then share this folder with others
NSString * const appFolder = @"byteclub";

将字符串值更改为Dropbox文件夹的名称创建和删除#警告编译指示。

这个程序分发给其他用户,给他们访问令牌,您需要打开“启用额外用户”设置为你的Dropbox平台应用。

Dropbox app控制台 https://www.dropbox.com/developers/apps 。 点击应用程序名,然后单击启用额外的用户按钮。 会出现一个对话框说你增加了用户限制。 单击对话框上的好清楚。 应用程序页面将现在看起来像下面的:

user limit

注意: 您可能会注意到,当你开发你的应用,你可以给100用户访问。 当你准备释放真正的应用程序,你必须申请生产状态,你可以通过单击 申请生产 Dropbox按钮和发送一些额外的信息。

Dropbox将回顾你的应用程序,以确保它符合他们的指南,如果一切顺利的话,他们将打开你的应用程序的API访问无限的用户。

Dropbox认证:概述

在您的应用程序可以使用Dropbox API之前,你需要对用户进行身份验证。 在Dropbox,这是通过OAuth——一个流行的开源协议,允许安全授权。

本教程的重点是在网络上,不是OAuth,所以我已经创建了一个小API Dropbox。 m处理大部分的给你。

如果您曾经使用过第三方twitter客户端应用程序,像TweetBot,那么你就会熟悉OAuth安装过程中从用户的视角。 OAuth过程几乎相同的应用程序。

构建和运行您的应用程序,并遵循的步骤登录。 你会看到一个空白的屏幕上有两个标签,一个用于笔记和一个用于PanoPhotos,如下所示:

App Login

OAuth身份验证发生在三个高水平的步骤:

  1. 获得一个OAuth请求令牌用于身份验证过程的其余部分。 这是请求令牌。
  2. 一个网页呈现给用户通过web浏览器。 没有用户的授权在此步骤中,为您的应用程序是不可能获得一个访问令牌从步骤3。
  3. 第二步完成后,应用程序调用一个web服务交换临时请求令牌(从步骤1)永久访问令牌,这是存储在应用程序中。

注意: 本教程保持简洁,我们不打算详细Dropbox身份验证是如何工作的。 然而,如果你想了解更多查看本教程的完整版的一部分 iOS 7教程

NSURLSession套类

苹果公司描述 NSURLSession 作为一个新类和套类。 有新工具上传、下载、处理授权和处理HTTP协议的任何东西。

在开始编码之前,重要的是要有一个好的理解的主要类 NSURLSession 套件以及它们如何一起工作。

NSURLSession overview

一个 NSURLSession 是使用一个 NSURLSessionConfiguration 和一个可选的委托。 在您创建会话然后满足你网络需要通过创建 NSURLSessionTask的

NSURLSessionConfiguration

有三种方法来创建一个 NSURLSessionConfiguration :

  • defaultSessionConfiguration ——创建一个配置对象,采用全局缓存,饼干和证书存储对象。 这是一个配置导致你的会话是最像 NSURLConnection
  • ephemeralSessionConfiguration ——这个配置是“私人”会话和没有持久性存储缓存,饼干,或凭证存储对象。
  • backgroundSessionConfiguration ——这是在当你想要使用的配置使网络电话远程推送通知或在应用程序暂停。 指17和18章 iOS 7教程 “初级和中级多任务”,为更多的细节。

一旦你创建一个 NSURLSessionConfiguration 这样,你可以设置各种属性:

NSURLSessionConfiguration *sessionConfig =
[NSURLSessionConfiguration defaultSessionConfiguration];
 
// 1
sessionConfig.allowsCellularAccess = NO;
 
// 2
[sessionConfig setHTTPAdditionalHeaders:
          @{@"Accept": @"application/json"}];
 
// 3
sessionConfig.timeoutIntervalForRequest = 30.0;
sessionConfig.timeoutIntervalForResource = 60.0;
sessionConfig.HTTPMaximumConnectionsPerHost = 1;
  1. 在这里你只限制对wifi网络操作。
  2. 这将设置所有请求只接受JSON响应。
  3. 这些属性将配置资源或请求超时。 还可以限制应用程序只有一个网络连接到主机。

这些只是您可以配置的一些事情,一定要检查出一个完整的文档列表。

NSURLSession

NSURLSession 设计作为一个替代API NSURLConnection 。 通过他们的爪牙会话做所有的工作,也被称为 NSURLSessionTask 对象。 与 NSURLSession 您可以创建任务使用基于块的便利方法,设置一个委托,或两者兼而有之。 例如,如果你想下载一个图像(* *)挑战,你将需要创建一个 NSURLSessionDownloadTask

首先,您需要创建一个会话。 这里有一个例子:

// 1
NSString *imageUrl =
@"http://www.raywenderlich.com/images/store/iOS7_PDFonly_280@2x_authorTBA.png";
 
// 2
NSURLSessionConfiguration *sessionConfig =
  [NSURLSessionConfiguration defaultSessionConfiguration];
 
// 3
NSURLSession *session =
  [NSURLSession sessionWithConfiguration:sessionConfig
                                delegate:self
                           delegateQueue:nil];

好这只是有点不同于你所拥有的。 让我们去一步一步。

  1. 这个片段我们下载相同的两个任务。
  2. 你总是首先创建一个NSURLConfiguration。
  3. 这将创建一个会话使用当前类作为一个委托。

在您创建会话,您可以下载图像通过创建一个任务完成处理器,是这样的:

// 1
NSURLSessionDownloadTask *getImageTask =
[session downloadTaskWithURL:[NSURL URLWithString:imageUrl]
 
    completionHandler:^(NSURL *location,
                        NSURLResponse *response,
                        NSError *error) {
        // 2
        UIImage *downloadedImage =
          [UIImage imageWithData:
              [NSData dataWithContentsOfURL:location]];
      //3
      dispatch_async(dispatch_get_main_queue(), ^{
        // do stuff with image
        _imageWithBlock.image = downloadedImage;
      });
}];
 
// 4
[getImageTask resume];

啊哈! 现在这看起来像一些网络代码!

  1. 任务总是由会话。 这是创建基于块的方法。 记住你仍然可以使用 NSURLSessionDownloadDelegate 跟踪下载进度。 所以你得到两全其美! (挑战* *提示)
    -URLSession:downloadTask
    :didWriteData:totalBytesWritten
    :totalBytesExpectedToWrite:
  2. 你完成处理器提供的使用位置变量指针图像。
  3. 例如,最后你可以更新UIImageView显示新文件的形象。 (提示提示 ☺)
  4. 你总是要启动任务!
  5. 还记得我之前说过,一个会话可以创建任务,将发送消息委托方法完成通知你,等。

这是看起来如何,使用相同的会话从上面:

// 1
NSURLSessionDownloadTask *getImageTask =
  [session downloadTaskWithURL:[NSURL URLWithString:imageUrl]];
 
[getImageTask resume];
  1. 这肯定是更少的代码 ☺然而,如果你只有这样做时,你永远不会看到任何东西。

你需要委托实现的一些方法 NSURLSessionDownloadDelegate 协议。

首先,我们需要下载完成时得到通知:

-(void)URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
  // use code above from completion handler
}

你再次提供文件下载到的位置,你可以用它来处理图像。

最后,如果你需要下载进度跟踪,为任务创建方法,您将需要使用以下:

-(void)URLSession:(NSURLSession *)session
     downloadTask:(NSURLSessionDownloadTask *)downloadTask
     didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
  NSLog(@"%f / %f", (double)totalBytesWritten,
    (double)totalBytesExpectedToWrite);
}

正如您可以看到的, NSURLSessionTask 是真正的主力通过网络完成的东西”。

NSURLSessionTask

到目前为止,您已经看到 NSURLSessionDataTask NSURLSessionDownloadTask 在使用。 来自这两个任务 NSURLSessionTask 这两个基类,在这里你可以看到:

NSURLSessionTask classes

NSURLSessionTask 是在您的会话的基类任务,他们只能从一个会话,并创建一个子类。

NSURLSessionDataTask

这个任务发出HTTP GET请求从服务器下拉数据。 NSData形式返回的数据。 然后将这些数据转换成正确的XML类型,JSON,界面图像,plist等等。

NSURLSessionDataTask *jsonData = [session dataTaskWithURL:yourNSURL
      completionHandler:^(NSData *data,
                          NSURLResponse *response,
                          NSError *error) {
        // handle NSData
}];

NSURLSessionUploadTask

使用这个类当您需要上传一些web服务使用HTTP POST或PUT命令。 委托任务时还允许您看好网络流量的传输。

上传一个图像:

NSData *imageData = UIImageJPEGRepresentation(image, 0.6);
 
NSURLSessionUploadTask *uploadTask =
  [upLoadSession uploadTaskWithRequest:request
                              fromData:imageData];

这里的任务是创建一个会话和NSData图片上传。 还有上传使用文件或流的方法。

NSURLSessionDownloadTask

NSURLSessionDownloadTask 能作到下载文件从远程服务,暂停和恢复下载。 这个比其他两个子类有点不同。

  • 这种类型的任务将直接写入一个临时文件。
  • 下载会议期间将调用URLSession:downloadTask:didWriteData:totalBytesWritten:totalBytesExpectedToWrite:更新状态信息
  • 当任务完成后,URLSession:downloadTask:didFinishDownloadingToURL:。 这是当你可以保存文件的一个永久的一个临时位置。
  • 当下载失败或取消了你可以得到数据恢复下载。

这个特性非常有用当俱乐部一个字节位置全景照片下载到你的设备的摄像头。 你看到一个例子下载任务在上面的代码片段下载一个图像。

上面所有的

上述所有任务创建挂起状态;在创建一个您需要调用它的恢复方法如下证明:

[uploadTask resume];

taskIdentifier属性允许您将惟一地标识一个任务在一个会话当你管理多个任务。
就是这样! 现在你知道的主要类 NSURLSession 套房,让我们试试。

与NSURLSession分享笔记

好吧,这不是《死亡诗社》这是字节俱乐部! 是时候开始看看这个网络代码的行动。

您需要一种方法来发送消息到其他字节俱乐部的成员。 既然你已经建立了一个访问令牌,下一个步骤是先实例化NSURLSesssion并让你调用Dropbox API。

创建一个NSURLSession

添加以下属性 NotesViewController.m 刚刚NSArray *笔记:

@property (nonatomic, strong) NSURLSession *session;

您将创建所有的仆从上面从会话。

添加以下方法 NotesViewController.m 略高于initWithStyle:

- (id)initWithCoder:(NSCoder *)aDecoder
{
    self = [super initWithCoder:aDecoder];
    if (self) {
        // 1
        NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
 
        // 2
        [config setHTTPAdditionalHeaders:@{@"Authorization": [Dropbox apiAuthorizationHeader]}];
 
        // 3
        _session = [NSURLSession sessionWithConfiguration:config];
    }
    return self;
}

这里有一个comment-by-comment解释上面的代码:

  1. 应用程序调用initWithCoder当实例化一个视图控制器从故事板,因此这是个完美的地方初始化和创建 NSURLSession 。 你不想要激进的缓存或持久性,所以你使用ephemeralSessionConfiguration便利方法,它返回一个会话缓存没有持久性存储,饼干,或凭据。 这是一个“隐私浏览”配置。
  2. 接 下来,您将授权HTTP头添加到配置对象。 的apiAuthorizationHeader我写的是一个助手方法,它返回一个字符串,在OAuth规范格式。 这个字符串包含访问令牌,令牌机密和Dropbox应用API键。 记住,这是必要的,因为每个API调用Dropbox需要身份验证。
  3. 最后,您将创建 NSURLSession 使用上面的配置。

这个会议现在准备创建的任何网络任务,你需要在你的应用程序。

指出通过Dropbox API

模拟报告被另一个用户添加,添加任何你选择的文本文件的文件夹设置根Dropbox文件夹。 下面的例子显示了文件 用法 坐在 byteclub Dropbox文件夹:

example file

等到Dropbox确认同步文件,然后继续下面的代码。

将下面的代码添加到空的 notesOnDropBox 方法 NotesViewController.m:

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
// 1
NSURL *url = [Dropbox appRootURL];
 
// 2
NSURLSessionDataTask *dataTask =
[self.session dataTaskWithURL:url
            completionHandler:^(NSData *data,
                                NSURLResponse *response,
                                NSError *error) {
    if (!error) {            
        // TODO 1: More coming here!
    }                
}];
 
// 3  
[dataTask resume];

这种方法的目的是获取列表的文件在应用程序的Dropbox文件夹。 我们这是如何工作的一段一段的。

  1. 在Dropbox,你可以看到一个文件夹的内容通过一个经过验证的GET请求特定的URL,比如https://api.dropbox.com/1/metadata/dropbox/byteclub。 我创建了一个方便的方法在Dropbox类为您生成这个URL。
  2. NSURLSession 有方便的方法来轻松地创建各种类型的任务。 给你创建一个数据任务为了执行GET请求URL。 当请求完成后,你的completionHandler块。 你会添加一些代码在一个时刻。
  3. 记得一个任务默认为暂停状态,所以你需要调用恢复方法开始运行。

这是所有您需要做的开始一个GET请求,现在让我们添加代码来解析结果。 后添加以下行“TODO 1”评论:

// 1
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
if (httpResp.statusCode == 200) {
 
    NSError *jsonError;
 
    // 2
    NSDictionary *notesJSON =
      [NSJSONSerialization JSONObjectWithData:data                                                                        
        options:NSJSONReadingAllowFragments                                                                             
        error:&jsonError];
 
    NSMutableArray *notesFound = [[NSMutableArray alloc] init];
 
    if (!jsonError) {                    
        // TODO 2: More coming here!
    }
}

这里有两个主要部分:

    1. 你知道你做了一个HTTP请求,响应将是一个HTTP响应。 这里你把NSURLResponse NSHTTPURLRequest反应所以你可以访问statusCode财产。 如果你收到一个HTTP状态码200年一切都好。

HTTP错误代码示例:

400 -坏输入参数。 错误信息应该显示哪一个,为什么。

401 -坏或过期的令牌。 这可能发生,如果用户或Dropbox撤销或过期一个访问令牌。 来解决,你应该认证用户。

403 -坏OAuth请求(错误的消费的关键,坏nonce,到期时间戳…)。 不幸的是,认证用户不会帮助。

404 -文件或文件夹没有找到指定的路径。

405 -请求方法(一般应该是GET或POST)。

429 -你的应用使太多的请求和速度有限。 429年代在每个应用或用户的基础上可以触发。

503 -如果响应包括Retry-After头,这意味着你的OAuth 1.0应用程序速度有限。 否则,这表明瞬态服务器错误,应用程序应该重试请求。

507 -用户/ Dropbox存储配额。

5 xx -服务器错误。

  1. Dropbox API将数据作为JSON返回。 如果你收到一个200响应,然后将数据转换成JSON使用iOS的JSON建成的反序列化。 了解更多关于JSON和NSJSONSerialization,看看23章 iOS 5的教程 “使用JSON。”

返回的JSON数据Dropbox将看起来像这样:

{
    "hash": "6a29b68d106bda4473ffdaf2e94c4b61",
    "revision": 73052,
    "rev": "11d5c00e1cf6c",
    "thumb_exists": false,
    "bytes": 0,
    "modified": "Sat, 10 Aug 2013 21:56:50 +0000",
    "path": "/byteclub",
    "is_dir": true,
    "icon": "folder",
    "root": "dropbox",
    "contents": [{
        "revision": 73054,
        "rev": "11d5e00e1cf6c",
        "thumb_exists": false,
        "bytes": 16,
        "modified": "Sat, 10 Aug 2013 23:21:03 +0000",
        "client_mtime": "Sat, 10 Aug 2013 23:21:02 +0000",
        "path": "/byteclub/test.txt",
        "is_dir": false,
        "icon": "page_white_text",
        "root": "dropbox",
        "mime_type": "text/plain",
        "size": "16 bytes"
    }],
    "size": "0 bytes"
}

所以添加代码的最后一位是代码,拿出你感兴趣的部分从JSON。 特别是,你想要遍历的“内容”数组任何“is_dir”设置为false。

要做到这一点,后添加以下行“TODO 2”评论:

// 1
NSArray *contentsOfRootDirectory = notesJSON[@"contents"];
 
for (NSDictionary *data in contentsOfRootDirectory) {
    if (![data[@"is_dir"] boolValue]) {
        DBFile *note = [[DBFile alloc] initWithJSONData:data];
        [notesFound addObject:note];
    }
}
 
[notesFound sortUsingComparator:
  ^NSComparisonResult(id obj1, id obj2) {
    return [obj1 compare:obj2];                    
}];
 
self.notes = notesFound;
 
// 6
dispatch_async(dispatch_get_main_queue(), ^{
    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
    [self.tableView reloadData];
});

这里有两个部分:

  1. 你退出的数组对象从“内容”键,然后遍历该数组。 每个数组条目是一个文件,所以你为每个文件创建一个相应的DBFile模型对象。
    DBFile助手类我为你拿出的信息创建一个文件从JSON字典——快速peek所以你可以看到它是如何工作的。
    当你完成,你将所有的音符添加到自我。 notes属性。 设置表格视图来显示这个数组的任何条目。

现在您已经表视图的数据源更新,您需要重新加载表数据。 当你处理异步网络电话,你要确保更新UIKit在主线程。

聪明的读者可能会注意到在上面的代码没有错误处理;如果你感觉更(和大多数字节俱乐部的成员都是!)添加一些代码(以及后续代码块中您将添加),将重试的错误和警告用户。

构建和运行您的应用程序,您应该看到文件添加到您的Dropbox文件夹出现在列表中,如下面的示例所示:

file added

这是小事,但它是活生生的证据,你正确地调用Dropbox API。

下一步是把笔记和问题挑战其他俱乐部成员,再次使用Dropbox API作为交付机制。

文章指出通过Dropbox API

在右上角点击加号,你会看到注意添加/编辑屏幕出现,如下图所示:

add edit screen

starter应用程序已经建立了通过DBFile模型对象 NoteDetailsViewController prepareForSegue 发送者:。

如果你看一眼这个方法,你会发现NoteViewController设置的 NoteDetailsViewController的 委托。 这种方式,NoteDetailsViewController可以通知NoteViewController当用户完成编辑的报告,或取消编辑。

NotesViewController开放。 prepareForSegue m和添加以下行:发送方:,刚刚showNote。 委托=自我;

showNote.session = _session;

NoteDetailsViewController已经有一个 NSURLSession 会话属性,因此您可以设置在prepareForSegue:发送方:之前加载。

现在细节视图控制器将共享相同的 NSURLSession ,所以细节视图控制器可以使用它来让DropBox API调用。

取消 完成 按钮已经出现在应用程序;你只需要添加一些逻辑背后保存或取消请注意,在进步。
在NoteDetailsViewController。 米,发现以下行(IBAction)完成:(id)发送者:

// - UPLOAD FILE TO DROPBOX - //
    [self.delegate noteDetailsViewControllerDoneWithDetails:self];

…,代之以下列:

// 1
NSURL *url = [Dropbox uploadURLForPath:_note.path];
 
// 2
NSMutableURLRequest *request = 
  [[NSMutableURLRequest alloc] initWithURL:url];
[request setHTTPMethod:@"PUT"];
 
// 3
NSData *noteContents = [_note.contents dataUsingEncoding:NSUTF8StringEncoding];
 
// 4
NSURLSessionUploadTask *uploadTask = [_session      
  uploadTaskWithRequest:request                                                                  
  fromData:noteContents                                                        
  completionHandler:^(NSData *data,                                                                             
  NSURLResponse *response,                                                                               
  NSError *error) 
{   
   NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
 
   if (!error && httpResp.statusCode == 200) {
 
       [self.delegate noteDetailsViewControllerDoneWithDetails:self];
   } else {
       // alert for error saving / updating note
   }
}];
 
// 5
[uploadTask resume];

这实现了所有你需要保存和分享你的笔记。 如果你仔细看看每一个评论部分,你会发现代码如下:

  1. 再次上传文件Dropbox,您需要使用一个特定的API的URL。 就像之前你需要列出一个目录中的文件的URL,我已经创建了一个辅助方法为您生成的URL。 你叫这个。
  2. 接下来是你的老朋友NSMutableURLRequest。 简单的url和新的api可以使用 NSURLRequest 对象,但你需要可变形式符合Dropbox API希望这个请求PUT请求。 设置HTTP方法把信号Dropbox,你想创建一个新文件。
  3. 下一个你从UITextView编码文本NSData对象。
  4. 现在,你创建的请求和NSData对象,下创建一个你 NSURLSessionUploadTask 和 设置完成处理程序块。 在成功之后,您调用委托方法noteDetailsViewControllerDoneWithDetails:关闭模式内容。 在生产级应用程序可以通过一个新的DBFile回委托和同步你的持久数据。 对于本应用程序,您只需刷新NotesViewController新的网络电话。
  5. 再次,所有任务都创建为暂停所以你必须打电话给简历他们开始。 懒惰的奴才!

构建和运行您的应用程序,点击加号Notes选项卡。 输入您的名字 挑战的名字 字段,输入一些文本 请注意 场提供了一个挑战雷,类似于下面的例子:

enter note

当你点击 完成 ,NoteViewController将返回,列出你的新注意如下所示:

view note

你正式的挑战,发出挑战雷;然而,他的朋友在很高的地方,所以你最好把你的最好的游戏!

但是有一个重要的功能缺失。 你能告诉这是什么吗?

点击包含挑战的注意;NoteDetailsViewController礼物本身,但注意的是空白的文本。

射线不会发现你的挑战很威胁如果他不能读它!

现在,应用程序只调用Dropbox元数据API来检索文件的列表。 你需要添加一些代码来获取注意的内容。

NoteDetailsViewController开放。 m和空白retreiveNoteText实现替换为以下几点:

-(void)retreiveNoteText
{
    // 1
    NSString *fileApi = 
      @"https://api-content.dropbox.com/1/files/dropbox";
    NSString *escapedPath = [_note.path 
      stringByAddingPercentEscapesUsingEncoding:
      NSUTF8StringEncoding];
 
    NSString *urlStr = [NSString stringWithFormat: @"%@/%@",
      fileApi,escapedPath];
 
    NSURL *url = [NSURL URLWithString: urlStr];
 
    [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
 
    // 2
    [[_session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
 
        if (!error) {
            NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 200) {
                // 3
                NSString *text = 
                 [[NSString alloc]initWithData:data 
                   encoding:NSUTF8StringEncoding];
                dispatch_async(dispatch_get_main_queue(), ^{
                    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
                    self.textView.text = text;
                });
 
            } else {
                // HANDLE BAD RESPONSE //
            }
        } else {
            // ALWAYS HANDLE ERRORS :-] //
        }
        // 4
    }] resume];
}

上面的代码(无错误检查)在表格下面的备注说明:

  1. 设置请求的URL路径和文件你希望检索;/文件端点Dropbox API将返回一个特定的文件的内容。
  2. 创建任务和一个URL指向的数据文件。 这叫应该开始看你经过这个程序很熟悉。
  3. 如果你的响应代码表明,所有是好的,在主线程上设置textView你前面步骤中检索的文件内容。 记住,UI更新必须派出主线程。
  4. 一旦初始化任务,调用的简历。 这是比以前稍微不同的方法,因为简历直接被称为任务没有分配任何东西。

构建和运行您的应用程序,在列表中点击你的挑战和内容正确显示在视图中,如下所示:

note display

你可以扮演雷和应对挑战的一部分通过输入文本注;将尽快更新你的文件 完成

共享应用程序和Dropbox文件夹与代码的一些朋友,让他们测试你的应用程序通过添加和编辑笔记之间彼此。 毕竟,字节俱乐部更有趣有不止一个人!

上传照片与NSURLSessionTask代表

您已经了解了如何使用 NSURLSession 异步的简便方法。 但是如果你想关注一个文件传输,如上传一个大文件和显示一个进度条?

对于这种类型的异步的,耗时的任务你需要实现协议的方法 NSURLSessionTaskDelegate 。 通过实现这种方法,您可以接收回调函数,当一个任务接收数据并完成接收数据。

您可能已经注意到, PanoPhotos 标签是空的,当你启动应用。然而,字节俱乐部的创始成员有自己的慷慨地提供了一些全景照片来填补你的应用。

下载这些 全景照片 我们一起给你。 解压缩文件,并将照片目录复制到你的应用在Dropbox文件夹。 你的文件夹应类似于以下内容:

photos folder

Dropbox核心API可以提供照片的缩略图,这听起来像是完美的用于UITableView细胞。

PhotosViewController开放。 m并将下面的代码添加到tableView:cellForRowAtIndexPath:刚刚评论说“去缩略图”:

[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSURLSessionDataTask *dataTask = [_session dataTaskWithURL:url
  completionHandler:^(NSData *data, NSURLResponse *response, 
  NSError *error) {
    if (!error) {
      UIImage *image = [[UIImage alloc] initWithData:data];
      photo.thumbNail = image;
      dispatch_async(dispatch_get_main_queue(), ^{
        [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
        cell.thumbnailImage.image = photo.thumbNail;
      });
    } else {
      // HANDLE ERROR //
    }
}];
[dataTask resume];

上面的代码显示了照片的缩略图在表视图单元格…或者至少,目前如果_photoThumbnails数组不是空的。

找到 refreshPhotos 用以下代码替换它的实现:

- (void)refreshPhotos
{
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
    NSString *photoDir = [NSString stringWithFormat:@"https://api.dropbox.com/1/search/dropbox/%@/photos?query=.jpg",appFolder];
    NSURL *url = [NSURL URLWithString:photoDir];
 
    [[_session dataTaskWithURL:url completionHandler:^(NSData 
      *data, NSURLResponse *response, NSError *error) {
        if (!error) {
            NSHTTPURLResponse *httpResp = 
             (NSHTTPURLResponse*) response;
            if (httpResp.statusCode == 200) {
 
                NSError *jsonError;
                NSArray *filesJSON = [NSJSONSerialization  
                  JSONObjectWithData:data                                                                     
                  options:NSJSONReadingAllowFragments                                                                           
                  error:&jsonError];
                NSMutableArray *dbFiles = 
                  [[NSMutableArray alloc] init];
 
                if (!jsonError) {
                    for (NSDictionary *fileMetadata in 
                      filesJSON) {
                        DBFile *file = [[DBFile alloc] 
                          initWithJSONData:fileMetadata];
                        [dbFiles addObject:file];
                    }
 
                    [dbFiles sortUsingComparator:^NSComparisonResult(id obj1, id obj2) {
                        return [obj1 compare:obj2];
                    }];
 
                    _photoThumbnails = dbFiles;
 
                    dispatch_async(dispatch_get_main_queue(), ^{
                        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
                        [self.tableView reloadData];
                    });
                }
            } else {
                // HANDLE BAD RESPONSE //
            }
        } else {
            // ALWAYS HANDLE ERRORS :-] //
        }
    }] resume];
}

这非常类似于你之前写的代码加载所面临的挑战。 这一次,API调用查看 照片 目录,只请求文件。 jpg格式扩展。

现在_photoThumbnails数组填充,缩略图将显示在表视图和异步更新。

构建和运行您的应用程序切换到PanoPhotos选项卡,缩略图将加载和显示如下:

thumbnails

这些照片看起来很好,只是小心的Matthijs code-shredding猫!

上传PanoPhoto

你的应用可以下载照片,但它如果它还可以上传图片和显示上传的进度。

一个上传的进步跟踪, PhotosViewController 必须是一个委托的吗 NSURLSessionDelegate NSURLSessionTaskDelegate 协议,这样你就可以获得进步回调。

修改 PhotosViewController 接口声明的 PhotosViewController.m 通过添加
NSURLSessionTaskDelegate ,如下:

@interface PhotosViewController ()<UITableViewDelegate, UITableViewDataSource, UIImagePickerControllerDelegate, UINavigationControllerDelegate, NSURLSessionTaskDelegate>

接下来,添加以下私人财产:

@property (nonatomic, strong) 
  NSURLSessionUploadTask *uploadTask;

上面的指针引用任务对象;这样,你就可以访问对象的成员的进步跟踪上传任务。

当用户选择照片上传, didFinishPickingMediaWithInfo 调用 uploadImage: 执行文件上传。 现在,这个方法是空的——这是你的职责肉出来。

用下面的代码替换uploadImage::

- (void)uploadImage:(UIImage*)image
{
    NSData *imageData = UIImageJPEGRepresentation(image, 0.6);
 
    // 1
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPMaximumConnectionsPerHost = 1;
    [config setHTTPAdditionalHeaders:@{@"Authorization": [Dropbox apiAuthorizationHeader]}];
 
    // 2
    NSURLSession *upLoadSession = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
 
    // for now just create a random file name, dropbox will handle it if we overwrite a file and create a new name..
    NSURL *url = [Dropbox createPhotoUploadURL];
 
    NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url];
    [request setHTTPMethod:@"PUT"];
 
    // 3
    self.uploadTask = [upLoadSession uploadTaskWithRequest:request fromData:imageData];
 
    // 4
    self.uploadView.hidden = NO;
    [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
 
    // 5
    [_uploadTask resume];
}

这是发生了什么在上面的代码中:

  1. 以前,您使用会话设置在initWithCoder和相关的便利方法创建异步任务。 这一次,你用一个NSURLSessionConfiguration只允许一个连接到远程主机,因为你一次处理一个文件上传过程。
  2. 上传和下载任务报告信息反馈给他们的代表,您很快就会实现这些。
  3. 在这里你设置uploadTask属性使用JPEG图像从UIImagePicker获得。
  4. 接下来,您显示UIProgressView PhotosViewController隐藏在。
  5. 启动任务——呃,对不起,恢复任务。

现在委托已设置,您可以实现的 NSURLSessionTaskDelegate 更新进度视图的方法。

将下面的代码添加到年底PhotosViewController.m:

#pragma mark - NSURLSessionTaskDelegate methods
 
- (void)URLSession:(NSURLSession *)session 
  task:(NSURLSessionTask *)task 
  didSendBodyData:(int64_t)bytesSent 
  totalBytesSent:(int64_t)totalBytesSent 
  totalBytesExpectedToSend:(int64_t)totalBytesExpectedToSend
{
    dispatch_async(dispatch_get_main_queue(), ^{
        [_progress setProgress:
          (double)totalBytesSent / 
          (double)totalBytesExpectedToSend animated:YES];
    });
}

上述委托方法定期报告上传任务信息返回给调用者。 它还更新 UIProgressView (_progress)来显示totalBytesSent / totalBytesExpectedToSend比显示信息(更加)完成百分比。

唯一留下的是表示当上传任务完成。 添加以下方法PhotosViewController.m结束:

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    // 1
    dispatch_async(dispatch_get_main_queue(), ^{
        [[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:NO];
        _uploadView.hidden = YES;
        [_progress setProgress:0.5];
    });
 
    if (!error) {
        // 2
        dispatch_async(dispatch_get_main_queue(), ^{
            [self refreshPhotos];
        });
    } else {
        // Alert for error
    }
}

这里没有太多的代码,但它执行两个重要的任务:

  1. 关闭网络活动指标,然后隐藏_uploadView的清理一旦完成了上传。
  2. 刷新PhotosViewController包括刚才上传的图片因为你的演示应用程序不是在本地存储任何东西。 在真实的应用程序,你可能应该在本地存储或缓存图像。

构建和运行您的应用程序,导航到PanoPhotos选项卡,点击相机图标来选择一个映像。

select image

 

注意: 如果你使用模拟器来测试应用程序,你用Mac显然不能拍照,所以就复制一个全景的模拟器和上传照片。 要做到这一点,确保没有其他Xcode项目目前正在连接到模拟器和在Xcode中选择 Xcode \开放开发人员工具\ iOS模拟器。

拖着包括全景照片的发现者的模拟器图像将在Safari。 然后长按图像,将图像保存到照片库。

选择图片上传后,uploadView显示在屏幕中间随着UIProgressView如下所示:

image upload

您可能已经注意到,一个图像上传可以花一些时间由于“更好的质量比例因子设置上传任务。 信不信由你,有些用户在他们的移动设备有点不耐烦! ☺对于那些a类型的个性,你应该提供一个取消的功能如果上传耗时太长。

上的Cancel按钮uploadView已经从故事板连接起来,所以你只需要实现逻辑清晰地杀死下载。

取代 cancelUpload :在PhotosViewController方法。 用下面的代码:

- (IBAction)cancelUpload:(id)sender {    
    if (_uploadTask.state == NSURLSessionTaskStateRunning) {
        [_uploadTask cancel];
    }
}

取消一个任务一样容易调用取消方法,您可以在这里看到。

现在构建并运行你的应用程序,选择照片上传和水龙头 取消 。 图像上传将停止和uploadView隐藏。

就是这样字节俱乐部完成!

从这里去哪里?

这是 完成项目 从这个NSURLSession教程。

如果你做到这一步,那么恭喜你享受你一辈子在字节俱乐部会员! 就不要告诉任何Android的家伙! :你现在应该能够处理任何网络任务应用程序的需求。

如果你喜欢本教程,您可能想要查看我们的新书 iOS 7教程 。 这是一个缩写版的一个29章在这本书中,覆盖所有的最新最好的api在iOS 7中,你一定会想要知道作为一名开发人员。

我差点忘了…

/slap <you the reader> have been slapped around a bit with a large trout.

(不要问为什么呵呵!)

如果你有任何问题或评论本教程或NSURLSession一般,请加入以下论坛讨论!

posted @ 2016-01-20 18:32  Bo-tree  阅读(338)  评论(0编辑  收藏  举报