iOS7多任务处理

第11章-中级-多任务

Chapter 11 - Intermediate Multitasking

By Pietro Rea

在上一章中你学习了如何实现后台获取,它可以使你的应用内容及时更新,这样用户每次启动应用时都能看到最新的内容。

In the previous chapter,you learned how to implement background fechting,which allows you to keep your app content up to date so the user always sees fresh content when they launch your app

在本章你将会学习Apple在iOS 7引入的另外两个多任务处理API:后台传输和静音推送通知。

In this chapter, you’ll learn about the other two multitasking APIs Apple introduced in iOS 7: background transfers and silent push notifications. 

你将会继续修改从本书本件中下载的NASA TV应用程序。本章中的更改并不受最后一章中更改的影响,所以如果你在最后一章中没有实现所有的功能,请不用担心,你只需从最开始的工程中进行即可。

You’ll continue to make modifications to the NASA TV application that you downloaded with this book’s files. The changes in this chapter are not dependent on last chapter’s changes, so if you didn’t implement all of the functionality in the last chapter, don’t worry – you can just begin with the starter project as-is.

后台传输

Background transfers

在之前版本的iOS中,如果在上传或者下载期间退出你的应用程序,那么就意味着你的传输进程将会被暂停(或者完全关闭)。在本章中,你将会对NSURLSession感兴趣,因为它可以让后台传输不中断变成可能。

In previous versions of iOS, quitting an app in the middle of an upload or download meant that your transfer would be paused — or killed altogether. In this chapter, you are going to focus on NSURLSession, which makes background transfers possible.

注意:如果你想学习更多关于NSURLSession的内容,请阅读第16章中的“使用NSURLSession联网”,此章节深入研究了iOS7里的API配置以实现一般的网络任务。

Note: If you want to learn more about NSURLSession, make sure to read Chapter 16, “Networking with NSURLSession.” It deals in depth with the new set of APIs in iOS 7 to perform common networking tasks.

本章的第一个任务就是在NASA TV应用中实现下载视频到本地观看的功能。但必须保证即使应用程序不在前台运行时,一个活动的下载进程继续执行而不会被中断。

Your first task in this chapter is to add the ability to download videos for offline viewing in NASA TV. An active download should continue even if the app isn’t running in the foreground.

开始之前,你需要添加一个“download”按钮和一个进度条到VideoDetailViewController中。打开Main.storyboard并在左侧选择VideoDetailViewController场景。

To start out, you’re going to add a “Download” button and a progress bar to VideoDetailViewController. Open Main.storyboard and select the VideoDetailViewController scene on the far right.

将一个UIBarttonItem拖拽到VideoDetailViewController导航条的右侧。在属性查看器中,将UIBarttonItem的Title属性改为Download,如下图所示:

Drag a UIBarButtonItem to the right side of VideoDetailViewController’s navigation bar. In the Attributes inspector, change the UIBarButtonItem’s Title property to Download as shown below: 

同样的,拖拽一个UIProgressView到刚刚放置在导航条上的UIBarButtonItem的左侧,并将其默认的颜色改为白色。

Similarly, drag a UIProgressView and place it to the left of the Download UIBarButtonItem you just dragged into the navigation bar, and change the default tint color to white.

接下来,在代码里将UIBarButton和UIProgressView连接到IBOutlets。在storyboard上选择VideoDetailViewController上的黄色视图控制器图标。

Next, connect the UIBarButton and UIProgressView to IBOutlets in code. Select the yellow View Controller icon on VideoDetailViewController’s dock in the storyboard. 

然后,选择Xcode右上角的Assistant Editor图标;此图标有点像一个男管家系着一条弯曲的领带。这样便会在storyboard之后打开VideoDetailViewController.m文件。

After that, select the Assistant Editor icon on the top right in Xcode; it looks like a butler wearing a bow tie. This will open VideoDetailViewController.m next to the storyboard.

按住control键将Download按钮拖拽到VideoDetailViewController.m的@interface部分,并将其命名为downloadButton。对UIProgressView执行相同的操作(确保你在barbutton里选择的是progress view,而不是bar button),并将其命名为progressView。

Control-drag the Download button to the @interface section of VideoDetailViewController.m and name it downloadButton. Do the same with your UIProgressView (make sure you select the progress view inside the bar button item, not the bar button item itself) and name it progressView.

到此,Xcode应该已经为你创建了两个新的属性,如下所示:

At this point, Xcode should have created two new properties for you:

@property (weak, nonatomic) IBOutlet 

  UIBarButtonItem *downloadButton;

@property (weak, nonatomic) IBOutlet 

  UIProgressView *progressView;

在代码中,下载按钮已经连接到了一个IBOutlet,但是点击它并不会触发任何事情。为了解决这个问题,按住Control键将此按钮拖拽到VideoDetailViewController.m中的@implementation部分。

The download button is now connected to an IBOutlet in code, but it won’t trigger anything when tapped. To fix this, control-drag from the button to the @implementation section in VideoDetailViewController.m.

这个连接将会是一个action(动作)而不是一个outlet(插槽)。将IBAtion的方法命名为downloadButtonTapped;此代码如下:

This connection is going to be an action instead of an outlet. Name the IBAction method downloadButtonTapped; its code representation will look like the following:

- (IBAction)downloadButtonTapped:(id)sender {

}

构建并运行你的工程;点击Videos标签显示视频列表,然后点击视图列表里的任意一个视频。出现在视图中的VideoDetailViewController将会是如下图所示的那样:

Build and run your project; tap on the Videos tab to display the list of videos and then tap any video in the collection view. The VideoDetailViewController that is pushed into view should look similar to the one below:

我们终于成功了!嘿嘿,高兴地有点早哦。下载按钮和进度视图看起来很酷,但是现在它们还不能做任何事情。你需要重新回到VideoDetailViewController.m文件中,并添加一些代码来实现这些功能。

Houston, we have liftoff! Well, not quite. The download button and the progress view look great but they don’t do anything at the moment. You need to revisit VideoDetailViewController.m and add some code to do perform those tasks.

下载视频

Downloading videos

为了下载视频文件,先在VideoDetailViewController.m文件声明的@interface中添加如下协议:

To add the code to download the video files, start by adding the following protocols to the @interface declaration in VideoDetailViewController.m:

@interface VideoDetailViewController() <NSURLSessionDelegateNSURLSessionTaskDelegateNSURLSessionDownloadDelegate>

这三个协议是追踪下载状态所需的。另外,它们也允许你更新刚刚加到用户接口中的UIProgressView。

Those three protocols are necessary for monitoring the status of the download. Among other things, they allow you to update the UIProgressView that you just added to the user interface.

接下来将如下三个属性添加到@interface部分:

Next, add the following three properties to the @interface section:

@property (strongnonatomicNSURLSession* urlSession;

@property (strongnonatomic

NSURLSessionDownloadTask* downloadTask;

@property (strongnonatomicNSString* videosDirectoryPath;

NSURLSession和NSURLSessionDownloadTask对象是执行下载操作的重要任务,而videoDirectoryPath则指向用于存储视频的文件系统的目录。

The NSURLSession and NSURLSessionDownloadTask objects perform the heavy lifting in the download operation, while videosDirectoryPath points to the directory in the file system where the videos are going to be stored. 

下载和保存视频是一个相当大的任务,所以你需要将其进行分解。现在先把重心放在进度条视图上,当点击下载按钮时使其能够正常工作。

The methods that that download and save the video are fairly large in size, so you’re going to tackle it in small chunks. For now, just focus on making the progress view work properly when you tap on the download button.

找到viewWillAppear:这个方法,并添加如下两行代码到此方法的最上面:

Go to viewWillAppear: and add the following two lines to the top of the method:

- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    

    self.progressView.progress = 0.0f;

    self.progressView.hidden = YES;

这两行代码使进度视图隐藏,并且在进度视图第一次被推送到视图中时将其重置。

Those two lines ensure that the progress view is hidden and reset when the view controller first gets pushed into view.

接下来,大概地浏览一下如下所示的downloadButtonTapped:实现代码:

Next, flesh out the implementation of downloadButtonTapped: as shown below:

- (IBAction)downloadButtonTapped:(id)sender {

    

    //1

    if ([self.video.availableOffline boolValue]) return;

    

    //2

    self.downloadButton.enabled = NO;

    self.progressView.hidden = NO;

    

    //3

    if (!self.urlSession) {

        

        NSURLSessionConfiguration* config =

        [NSURLSessionConfiguration defaultSessionConfiguration];

        

        self.urlSession =

        [NSURLSession

         sessionWithConfiguration:config

         delegate:self

         delegateQueue:[NSOperationQueue mainQueue]];

    }

    

    NSURLRequest *request = [NSURLRequest

                             requestWithURL:self.videoURL];

    

    self.downloadTask = [self.urlSession

                         downloadTaskWithRequest:request];

    

    //4

    [self.downloadTask resume];

}

这个方法实现不长,但是之后将会变得很重要。在上面的代码里,主要执行了以下一些动作:

00001The method is short but it has important pieces that will come into play later. In the code above, you take the following actions:

1.检查当前视频的availableOffline的属性是否为@(YES),如果是则退出程序;此属性的意义是视频已经开始下载并防止你重复下载。

1. Check if the current video's availableOffline property is set to @(YES) and if so, exit; this property indicates that a video has already been downloaded and prevents you from downloading it twice.

2. 使下载按钮变成不可用状态,这样可以防止在下载操作进行中再次启动一个NDURLSessionDownloadTask副本;另外,其将用于显示下载进度的进度条显示给用户。

2.Disable the download button while the download operation is in progress to prevent launching a duplicate NSURLSessionDownloadTask; additionally, reveal the progress bar to show the download progress to the user.

3.延迟NSURLSession的实例化。默认的配置对一个简单的下载任务是可行的,但是你将需要更改它,使其能启动后台传输。你将会在稍后完成此任务。

3. Lazy instantiation of NSURLSession. The default configuration is fine for a simple download task but you’ll have to change it to something else to enable background transfers. You’ll deal with this a bit later.

4. 最后,开始执行下载任务。

4.Finally, start the download task.

在downloadButtonTapped:方法下面添加如下方法的实现代码:

Below downloadButtonTapped: add the following method implementation:

#pragma mark - NSURLSessionDownloadTask methods

- (void)URLSession:(NSURLSession *)session

      downloadTask:(NSURLSessionDownloadTask *)downloadTask

      didWriteData:(int64_t)bytesWritten

 totalBytesWritten:(int64_t)totalBytesWritten

totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {

    

    dispatch_async(dispatch_get_main_queue(), ^{

        self.progressView.progress =

        (double)totalBytesWritten /

        (double)totalBytesExpectedToWrite;

    });

}

这个方法分配给主线程去更新VideoDetailViewController导航栏上的UIProgressView,这样用户就可以看到下载的进度了。

This method dispatches to the main thread to update the UIProgressView in VideoDetailViewController’s navigation bar so the user can see how the download is progressing.

构建并运行你的应用;选择任意一个视频并点击Download;你将会看到进度条沿着左侧向右侧移动。下载完了,但是视频文件在哪呢?

Build and run your app; navigate to any video and tap Download; you should see the progress bar moving along from left to right. The download clearly finishes…but where’s the video file? 

NSURLSessionDownloadTask将视频下载到了一个临时文件中,但是如果你没有及时将其复制并保存到永久存储的位置上,这个文件将会消失。所以你需要将其保存到某个地方。

NSURLSessionDownloadTask downloads the video into a temporary file — but if you don’t immediately copy it to a permanent location, the file will simply vanish. Looks like you need to persist it somewhere. 

还是在VideoDetailViewController.m文件中,重写VideoDirectoryPath的属性的getter,如下所示:

Still in VideoDetailViewController.m, override the getter for the videosDirectoryPath property as shown below: 

- (NSString*)videosDirectoryPath {

    

    if (!_videosDirectoryPath) {

        

        NSArray* paths =

        NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

                                            NSUserDomainMask,

                                            YES);

        _videosDirectoryPath = [paths[0]

                                stringByAppendingPathComponent:

                                @"com.razeware.videos"];

        

        BOOL directoryExists =

        [[NSFileManager defaultManager]

         fileExistsAtPath:_videosDirectoryPath];

        

        if (!directoryExists) {

            

            NSError* error;

            if (![[NSFileManager defaultManager]

                  createDirectoryAtPath:_videosDirectoryPath

                  withIntermediateDirectories:NO

                  attributes:nil

                  error:&error]) {

                

                /* Could not create directory */

                /* Handle NSFileManager error */

            }

        }

    }    

    return _videosDirectoryPath;

}

上面的代码在你的Caches目录里创建了一个文件夹(如果需要的话),并返回此目录的一个引用给调用者。

Caches是推荐使用的一个本地存储,也许在将来它会被重复产生或者重复被下载。

The code above creates a folder (if necessary) in your Caches directory and returns the directory reference to the caller. Caches is the recommended location to store files that may be regenerated or re-downloaded in the future.

接下来,在你之前在NSURLSessionDownloadTask中实现的第一个委托方法的下面添加如下方法:

Next, add the following method below the first NSURLSessionDownloadTask delegate method you implemented earlier:

- (void)URLSession:(NSURLSession *)session

      downloadTask:(NSURLSessionDownloadTask *)downloadTask

didFinishDownloadingToURL:(NSURL *)downloadURL {

    

    //1

    NSString* lastPathComponent =

    [downloadTask.originalRequest.URL lastPathComponent];

    

    //2

    NSString* destinationPath =

    [self.videosDirectoryPath

     stringByAppendingPathComponent:lastPathComponent];

    

    NSURL* destinationURL =

    [NSURL fileURLWithPath:destinationPath];

    

    //3

    NSError* error;

    

    BOOL copySuccessful =

    [[NSFileManager defaultManager]

     copyItemAtURL:downloadURL

     toURL:destinationURL

     error:&error];

    

    if (!copySuccessful) {

        /* Could not copy file to destinationURL */

        /* Handle NSFileManager error */        

    }

    

    //4

    dispatch_async(dispatch_get_main_queue(), ^{        

        self.video.availableOffline = @(YES);

        NSManagedObjectContext* moc =

        self.video.managedObjectContext;

        

        NSError* error;

        [moc save:&error];

        

        if (error) NSLog(@"Core Data error");

        
        //5

        self.progressView.hidden = YES;

        self.downloadButton.title = @"Downloaded";

    });

}

这里有一大堆的代码,但是下面将会进行详细的解释:

1.http://192.168.1.111:44447/videos/video-name.mp4,这是视频文件URL的格式,此路径的最后部分是为文件名服务的。这里整段路径是为提取文件名来建立一个合理的文件URL。使用它你可以将视频永久保存起来。

The video URLs are in the format http://192.168.1.111:44447/videos/video-name.mp4, where the path’s lastComponent serves as the name of the file. The goal here is to extract the file name to build a reasonable file URL to which you can save the video permanently.

2.destinationPath代表用于视频文件存储的完全文件URL;它是videoDirectoryPath和视频文件名合起来的结果。例如,discovery.mp4将会被存储在…/Caches/com.razeware.videos/discovery.mp4.

2.destinationPath represents the full file URL where the video is to be saved; it’s the result of concatenating videosDirectoryPath to the video’s file name. For example, discovery.mp4 would be saved as …/Caches/com.razeware.videos/discovery.mp4.

3.这里神奇的事情发生了。第一个参数是将视频下载到临时位置的URL,而第二个参数提供了在文件消失之前用于存储视频的位置。

3.This is where the magic happens. The first parameter is the URL for the downloaded video in its temporary location, and the second parameter gives the permanent location to save the video to before the file vanishes in a puff of digital smoke.

4.更新video的videoAvailableOffline属性来提示这个视频已经下载成功,downloadButtonTapped:则用于避免重复下载视频。在self.video指向的video被分配给主线程时这个动作必须先分配给主线程。

4.Update the videoAvailableOffline attribute of video to indicate that this video has been successfully downloaded, which downloadButtonTapped: uses to avoid re-downloading a video. This action must be dispatched to the main thread since the video that self.video points to was fetched on the main thread.

注意:在本章中,Core Data的细节并不重要,但是如果你想阅读更多关于使用多线程里的Core Data,请阅读苹果官方文档Core Data Programming Guide.

Note: The Core Data details are not important for this chapter, but if you want to read more about using Core Data from multiple threads make sure to read Apple’s Core Data Programming Guide.

5.最后,隐藏进度条并将导航栏里的文本内容“Download”改为“Downloaded”。现在看来这些后期细小视觉效果并不重要,但是之后当你实现后台传输的时候将会对你有很大的帮助。

5. Finally, hide the progress bar and change the text in the navigation bar from “Download” to “Downloaded”. These small visual cues may seem unimportant now, but they will be helpful later when you’re implementing background transfers.

还是在VideoDetailViewController.m文件中,添加如下空白方法:

Still in VideoDetailViewController.m, add the following empty method:

- (void)URLSession:(NSURLSession *)session

      downloadTask:(NSURLSessionDownloadTask *)downloadTask

 didResumeAtOffset:(int64_t)fileOffset

expectedTotalBytes:(int64_t)expectedTotalBytes {

    

    

}

这是NSURLSessionDownloadTask委托所需的方法;你并不准备使用它,将其放置此处是为了避免来自Xcode的警告。

This is a required NSURLSessionDownloadTask delegate method; you’re not going to use it but it’s included here to silence an Xcode warning.

现在你还没有做任何错误处理的工作。虽然在移动网络上进行下载文件的过程中并没有产生任何的错误,但是这样做是一个很好的习惯,但是还是要防止一些意外发生。

You haven’t yet taken care of the error handling. Although nothing ever goes wrong when downloading files over mobile networks, it’s good practice to have it there just in case something glitches. .J

如果下载时遇到了一个错误,一般的做法是将硬盘上的临时文件删除,因为通常此文件是没有用的。添加如下方法实现:

If you encounter an error with the download, delete the temporary file from disk as the file will likely be of no use. Add the method implementation as shown below:

#pragma mark - NSURLSessionTaskDelegate methods

- (void)URLSession:(NSURLSession *)session

              task:(NSURLSessionTask *)task

didCompleteWithError:(NSError *)error {

    

    if (error) {

        NSString* lastPathComponent =

        [task.originalRequest.URL lastPathComponent];

        

        NSString* filePath =

        [self.videosDirectoryPath

         stringByAppendingPathComponent:lastPathComponent];

        

        [[NSFileManager defaultManager]

         removeItemAtPath:filePath error:nil];

    }

}

如果错误发生了,上面的代码将从视频的URL产生这个文件的URL(如同你之前做的),并且删除这个临时文件。

If an error occurs, the above code generates the file URL from the video URL as you did before and deletes the temporary file.

构建并运行你的应用程序。尝试如下试验:选择一个视频,下载它,然后(确保程序一直开着)切换到Setting(设置)并打开Airplane mode(飞行模式)来模拟离线。然后切换回你的应用程序并尝试播放你下载的视频。没有任何响应!为什么?

Build and run your app. Try the following experiment: choose a video, download it, then (with the app still open) switch to Settings and go into Airplane mode to simulate being offline. Switch back to your app and attempt to play your downloaded video — nothing happens! Why?

启用离线视图

Enabling offline viewing

视频出现在了文件系统中,但是影音播放器并不知道如何找到它。为解决这个问题,修改viewWillAppear:里的代码,如下所示:

The video is present in the file system, but the movie player doesn’t know how to find it. Solve this problem by modifying the code in viewWillAppear: as follows:

- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:animated];

    

    self.progressView.progress = 0.0f;

    self.progressView.hidden = YES;

    

    //1

    BOOL videoAvailableOffline =

    [self.video.availableOffline boolValue];

    

    NSURL* playbackVideoURL;

    

    //2

    if (videoAvailableOffline) {

        self.downloadButton.enabled = NO;

        self.downloadButton.title = @"Downloaded";

        

        /* Play local content if available */

        NSString* lastPathComponent =

        [self.videoURL lastPathComponent];

        

        NSString* videoPath =

        [self.videosDirectoryPath

         stringByAppendingPathComponent:lastPathComponent];

        

        playbackVideoURL = [NSURL fileURLWithPath:videoPath];

    }

    

    else {

        self.downloadButton.enabled = YES;

        playbackVideoURL = self.videoURL;

    }

    //3

    self.moviePlayerViewController =

    [[MPMoviePlayerController alloc]

     initWithContentURL:playbackVideoURL];

    

    [self.moviePlayerViewController prepareToPlay];

    

    [self.moviePlayerViewController

     setControlStyle:MPMovieControlStyleDefault];

    

    [self.moviePlayerViewController.view

     setFrame:self.view.bounds];

    

    [self.view addSubview:self.moviePlayerViewController.view];

    

    [self.moviePlayerViewController play];

}

现在花点时间来看看刚刚你添加的那些代码:

1.记住在下载成功时你需要更新availableOffline属性。你需要将这个BOLL进行拆箱(unbox)操作,因为Core Data将其保存成一个NSNumber类型的数据。

1.Recall that you had to update the availableOffline property when the download finished successfully. You have to unbox this BOOL because Core Data saves it as an NSNumber.

2.如果视频在本地是可用的,那么将产生此文件的URL;否则使用self.video里的URL流。如果需要的话,你可以利用availableOffline来启用或者禁用下载按钮,因为如果将视频保存到本地之后就没必要让下载按钮保持可用状态了。

2.If the video is available locally, generate the file URL like you’ve been doing all along; otherwise, use the streaming URL in self.video. You can also use availableOffline to enable or disable the download button as needed, as there’s no need to keep the download button active if you already have the video saved locally.

这里你给MPMoviePlayerController添加了一个名为playbackVideoURL临时变量,它代表self.videoURL。根据视频在本地是否可用来确定playbackVideoURL是否是正确的URL。

3.Here you’re feeding MPMoviePlayerController a temporary variable named playbackVideoURL instead of self.videoURL. playbackVideoURL should have the correct URL based on whether or not the video is available locally.

构建并运行NASA TV应用,选择任一视频并点击Download。下载完毕后,保持应用一直开着,切换到Setting并禁用Wi-Fi,拔掉以太网卡,或者做任何可以断开网络的操作。切换回你的应用程序,找到刚才那个视频并打开它,这时发现你的视频现在可以播放了。

00002Build and run NASA TV, navigate to any video and tap Download. After the download is complete, with the app still open switch to Settings and disable Wi-Fi, unplug the Ethernet cable, or do whatever you need to do to disconnect from the Internet. Switch back to your app, navigate to the same video and voila — your video now plays. 

如果你有一个真实的设备,在此设备上构建并运行你的应用程序;在真实设备上下载明显变慢了,这就你你接下来要处理的任务。

00003If you have a physical device, build and run your app on that as well; downloads are noticeably slower on physical devices, which will come in handy for your next task. 

重新连接到互联网,找到任一视频,再一次点击下载。但是,这次在下载完成之前快速按下Home键。记住你退出NASA TV时进度条的进度节点。

等大概十秒钟,然后重新启动你的NASA TV应用;下载进度将会重新回到你离开时那个节点的位置。对于用户来说这可能并不是一件非常愉悦的事情,但是这正是在后台传输中我们将要修正的地方。

00004Wait about ten seconds, then restore your NASA TV app; the download should resume from exactly the same point at which you left it. That’s not terribly pleasing to the user — but that’s exactly what you’re going to fix with background transfers.

执行后台传输

Performing background transfers

不同于其他的后台模式,使用后台传输并需要你在应用的Info.plist上注册一个特殊的后台模式。

添加如下方法到AppDelegate.m文件中:

Unlike other background modes, using background transfers does not require you to register for a special background mode in your application’s Info.plist.

Add the following method to AppDelegate.m:

#pragma mark - Background Transfer

- (void)application:(UIApplication *)application

handleEventsForBackgroundURLSession:(NSString *)identifier

  completionHandler:(void (^)())completionHandler {

    

    NSDictionary* userInfo =

  @{@"completionHandler" : completionHandler,

    @"sessionIdentifier" : identifier};

    

    [[NSNotificationCenter defaultCenter]

     postNotificationName:@"BackgroundTransferNotification"

     object:nil

     userInfo:userInfo];

}

当一个后台传输完成后,系统会调用application:handleEventsForBackGroundURLSession:completionhandler:方法,此方法会传给你一个完成处理器,和之前一章中介绍的后台获取中的是一样的。

When a background transfer completes, the system calls application:handleEventsForBackgroundURLSession:completionHandler: which hands you a completion handler, just as was demonstrated in the previous chapter with background fetch. 

在这种情况下,处理后台传输的工作将在其他地方完成。这个委托方法通过将其包含在通知里的userInfo目录来传递完成处理器。

In this case, the work to handle the background download work is done elsewhere. The delegate method delivers the completion handler by including it in the notification’s userInfo dictionary. 

一个应用程序可以有几个传输在传输队列中,所以NSURLSession的身份标识同样会被发布,这样接受者就能明确那个传输已经完成了。

An app can have several transfers queued up, so the NSURLSession identifier is posted as well so the receiver can identify which transfer completed.

打开VideoDetailViewController.m文件,并添加如下代码到viewWillAppear的底部:

Open VideoDetailViewController.m and add the following snippet of code to the bottom of viewWillAppear:

[[NSNotificationCenter defaultCenter]

 addObserver:self

 selector:@selector(handleBackgroundTransfer:)

 name:@"BackgroundTransferNotification"

 object:nil]; 

这里添加了一个观察者到在AppDelegate.m发布的后台传输通知中。

This simply adds an observer to the background transfer notification posted in AppDelegate.m.

现在定位到VideoDetailViewController.m的底部并添加如下代码:

Now scroll to the bottom of VideoDetailViewController.m and add the following:

#pragma mark - Background Transfers

- (void)handleBackgroundTransfer:(NSNotification*)notification {

    

    // 1

    NSString* sessionIdentifier =

    notification.userInfo[@"sessionIdentifier"];

    

    NSArray* components =

    [sessionIdentifier componentsSeparatedByString:@"."];

    

    NSString* videoID = [components lastObject];

    

    // 2

    if ([self.video.videoID integerValue] ==

        [videoID integerValue]) {

        

        // 3

        dispatch_async(dispatch_get_main_queue(), ^{

            self.downloadButton.title = @"Downloaded";

            self.progressView.hidden = YES;

            

            void(^completionHandler)(void) =

            notification.userInfo[@"completionHandler"];

            

            if (completionHandler) {

                completionHandler();

            }

        });

    }

当一个下载完成的通知来了,handlebackgroundTransfer:方法将会被调用。在这个方法中引入了一些新的概念:

handleBackgroundTransfer: executes when a download complete notification arrives. There’s a few new concepts introduced in this method:

1.解包来自通知的userInfo目录中的NSURLSession身份标识。此身份标识的格式和反转的DNS标记法类似,所以最后的那部分包含的是视频的ID。

1. Unpack the NSURLSession identifier from the notification’s userInfo dictionary. The identifier is formatted similar to reverse DNS notation so the last component contains the video’s ID.

2.如果有多个下载进度,那么每个都会对应一个VideoDetailViewController实例。如果在屏幕上的视频和通知里的身份标识不是同一个,那么在继续执行之前将会检查他们是否匹配。

3.If there are multiple downloads in progress, each one will have a VideoDetailViewController instance. Since it’s possible that the video on the screen is not the same one identified in the notification, check that they match before continuing.

3.在主线程中执行UI的更新:将“Download”改为“Downloaded”并隐藏进度条。完成之后,执行保存在userInfo目录里的完成事件。

2. Perform the UI updates on the main thread: change “Download” to “Downloaded” and hide the progress bar. After that’s done, execute the completion handler stored in the userInfo dictionary.

你已经在viewWillAppear:方法中完成了注册,所以同样别忘了修改viewWillDisappear:方法对通知进行解除注册:

You’ve registered for the notification in viewWillAppear: so be sure to modify viewWillDisappear: to unregister for the notification as well:

- (void)viewWillDisappear:(BOOL)animated {

    [super viewWillDisappear:animated];

    [self.moviePlayerViewController stop];

    

    [[NSNotificationCenter defaultCenter]

    removeObserver:self];

}

现在,这些通知和处理器会帮助文件在后台传输中顺利完成传输,剩下的事情就是配置NSURLSession,使下载视频任务继续在后台执行。

The notifications and handlers now facilitate file transfers that will run to completion in the background. All that’s left to do is configure NSURLSession to continue downloading the video in the background.

定位到downloadButtonTapped:方法,并找到你l延迟下载SURLSession的地方并修改如下代码:

Go to downloadButtonTapped: and find the place where you lazy-load NSURLSession. Change that block of code as shown below: 

    //3

    if (!self.urlSession) {

        

        NSString* sessionID =

        [@"com.razeware.backgroundsession."

         stringByAppendingFormat:@"%d",

         [self.video.videoID integerValue]];

        

        NSURLSessionConfiguration* config =

        [NSURLSessionConfiguration

         backgroundSessionConfiguration:sessionID];

        

        self.urlSession =

        [NSURLSession

         sessionWithConfiguration:config

         delegate:self

         delegateQueue:

         [NSOperationQueue mainQueue]];

    }

创建一个后台NSURLSessionConfiguration需要一段ID;此ID与委托调用的application:handleEventForBackgroundURLSession:completionHandler:方法返回的ID是一样的。

Creating a background NSURLSessionConfiguration requires a session ID; this is the same ID that comes back in the delegate call application:handleEventsForBackgroundURLSession:completionHandler:. 

在本段代码中,你使用一个反向DNS标记并在最后添加此视频的ID。在创建唯一的连续性ID时,要小心点。这些ID会显示在debugger里,并如果出现某些意外错误时可以帮助你追踪相关的视频。

In this case, you are using a reverse DNS notation and appending the video ID to the end. Take care to create unique session IDs— these IDs will also show up in the debugger and help you track down the pertinent video if something goes wrong.

别着急着测试,你得先分别在AppDelegate.m里的application:handleEventsForBackgroundURLSession:completionHandler:和VideoViewController.m里的handleBackgroundTransfer:的两个方法中的完成处理器执行的后一句添加一个段点。

You’re almost ready to test. But first add a breakpoint to AppDelegate.m inside application:handleEventsForBackgroundURLSession:completionHandler: and another one to VideoViewController.m inside handleBackgroundTransfer: right after the completion handler gets executed.

在你的真实设备上构建并运行此应用程序;在设备上运行时的慢速下载给你创造了更多的时间在下载期间退出此应用,从而完成测试工作。

Build and run on your physical device; the slower download speed on the device gives you a little more time to quit the app mid-download to test your work.

现在找到任一视频,点击Download并在进度条没跑完之前按Home键。

Now navigate to any video, tap Download and press the Home button once the progress bar is halfway done. 

稍稍离开一下你的电脑休息一下吧。你可以吃点三明治或者喝杯咖啡,更甚者去解决一下千禧年大奖的问题,反正随你意。当你回来的时候,你发现Xcode的debugger将会停在刚才你在AppDelegate.m设置的断点处,如下图所示:

Step away from your computer for a bit; maybe grab a sandwich or a coffee, or solve a Millennium Prize Problem, whatever you choose. When you come back, Xcode’s debugger will be paused at the breakpoint you inserted in AppDelegate.m, as shown below:

点击Continue跳转到下一个断点,在VideoDetailViewController.m的completion handler执行完之后立即使debugger暂停。再次点击Continue恢复正常运行。

Click on Continue to move to the next breakpoint, which pauses the debugger immediately after executing the completion handler in VideoDetailViewController.m. Click on Continue once again to resume normal execution.

这样就证明了completion handler像预期的那样被执行了——但是应用的切换截图是否更新了呢?为了证明,双击你设备上Home键,进入应用切换管理器并找到NASA TV的快照,如下图所示:

按钮的右上角清楚的显示着“Downloaded”,这就表明视频已经被复制到了文件系统中。这样就没必要重新启动NASA TV去查看下载进度了,应用切换管理器中快照已经告诉你:下载已经成功完成了!这就证明了完成处理器在恰当的时候被调用了。

Notice that the button in the top right reads “Downloaded”, which means that the video has been copied permanently into the file system. At no point did you re-launch NASA TV to check on the download’s progress, yet the app switcher snapshot tells you that the download completed successfully! This is your proof that the completion handler was called at the right moment.

自由传输

Discretionary transfers

在本章前半部分,你在应用运行在前台的时候安排了一个后台传输。然而,在后台完成开始到结束传输的整个过程也就变得可能了,例如当应用因一个后台获取的操作而被唤醒,或者响应一个静音推送通知。

In the previous section you queued up a background transfer while the app was running in the foreground. However, it is also possible to start and finish a transfer entirely from the background, such as when the app wakes up for a background fetch operation or responds to a silent push notification. 

从后台开始执行的后台传输叫discretionary transfers(自由传输),这意味着它具有管理权限,并且只在Wi-Fi上工作。

Background transfers started from the background are discretionary transfers, which means they are power-managed and will only work over Wi-Fi. 

你可以将NSURLSessionConfiguration中的一个BOOL属性设置成discretionary,从而使前台传输变成自由传输。

You can optionally set foreground transfers to be discretionary by setting a BOOL property in NSURLSessionConfiguration named discretionary.

在你实现后台传输的时候请把自由传输记住在心上。用户可以通过检查Setting应用就很容易知道你的应用是否具有重要数据要更新,传输是否足够重要这完全取决于你的决定。

Keep discretionary transfers in the back of your mind as you implement background transfers. Users can easily see if your app is being a data hog by checking the Settings app, so it’s up to you to determine whether the transfer is important enough.

静音推送通知

Silent push notifications

传统的推送通知的目的是提醒用户一些新鲜有趣事物,即使此刻用户并没有在使用你的应用。比如一些重要新闻,一位好友更新了Facebook的状态或者近期你喜欢的主题杂志更新了。

The goal for traditional push notifications is to alert the user of something interesting going on — even if they’re not using your app at the moment. This could be something like breaking news, a friend responding to a Facebook status or the latest issue of your favorite magazine becoming available.

推送通知的本质是模拟多任务处理,它让你的应用看起来一直不断地在后台更新一些新的信息,一旦有新的感兴趣东西出现了就会提醒你。iOS 7通过引进静音推送通知拓展了这个概念,它允许第三方开发者在不打扰用户的情况下触发后台更新。

Push notifications essentially simulate multitasking, making it seem like your app is constantly polling new information in the background and alerting you anytime something interesting happens. iOS 7 extends this concept by introducing silent push notifications, which allow third-party developers to trigger background refreshes without bothering the user. 

当一台设备接受了一个静音推送通知,并不会在屏幕上显示提醒信息。这并不是出了什么错误,而是你的应用在后台启动了,并且在获取新的信息。

When a device receives a silent push notification there is no visual indication that anything happened. But make no mistake: your app has been launched in the background and it is now fetching new content.  

不幸的是,推送通知并不能在模拟器上工作,所以为了完成本小节的任务你应该在真实设备上运行NASA TV应用。

Unfortunately, push notifications won’t work with the simulator, so you’ll have to run NASA TV on a physical device in order to complete this section of the chapter. 

注意:如果你想学习更多关于传统推送通知的知识,你应该认真阅读以下两部分的教程:

Part 1: Push Notifications Tutorial: Getting Started | raywenderlich.com
Part 2: Push Notifications Tutorial: Getting Started | raywenderlich.com

Note: If you want to learn more about traditional push notifications, you should read the following two-part tutorial that covers them in depth:

Part 1: Push Notifications Tutorial: Getting Started | raywenderlich.com
Part 2: Push Notifications Tutorial: Getting Started | raywenderlich.com

证书与配置

Certificates and provisioning

要想发送推送通知,你需要在开发者中心设置两样东西:与苹果的推送通知服务联系的密钥和证书,还有你的应用的专用配置文件。

To send push notifications, you’ll need to set up two things in the developer portal: a key and certificate to communicate with Apple’s push notification servers, and a provisioning profile specific to your app.

如果你对此内容很熟悉了,你可以自己配置并跳过本节。否则就继续阅读本节内容。创建证书很配置文件涉及到很多的步骤,但是完成这些只需要几分钟。

If you’re an old hand at this, you can set this up yourself and jump ahead to the next section. Otherwise, read on — creating the certificate and profile involves a fair number of steps but the whole process shouldn’t take more than a few minutes.

首先你必须生成一个证书签名所需要的文件(certificate sigining reuest file),或者叫CSR。在你的Mac机器上使用Keychain Accessy应用来完成这项工作。此应用在任何一台Mac机器上都有——它在应用里的Utilitis文件夹里。

The first thing you must do is to generate a certificate signing request file, also known as a CSR. You do this using the Keychain Access application on your Mac. This application ships with every Mac – it’s in the Utilities folder inside Applications.

打开Keychain Access,在菜单选项里选择Certificate Assiatant\Request a Certificate From a Certificate Authority...,如下图所示:

With Keychain Access open, select Certificate Assistant \ Request a Certificate From a Certificate Authority… from the Keychain Access menu.

填入你的e-mail地址并输入NASA TV作为公共名。选择“Saved to disk”圆形按钮并使“CA Email Address”文本框保持空白,如下图所示:

Type in your e-mail address and enter NASA TV as the common name. Select the “Saved to disk” radio button and leave the field “CA Email Address” blank as shown below:

当你完成了你的信息输入请点击Continue,输入你选择的一个文件名并点击Save。这样就会在你的桌面上保存一个后缀名为.cerSigningRequest的文件。

Click Continue when you’re done entering your information, enter a filename of your choice and click Save. This will save a file with the extension .certSigningRequest to your desktop.

现在你需要在苹果开发者中心创建一个NASA TV的唯一的一个应用ID。登陆苹果的iOS Developer Member Center并选择Certificates,Identifiers和Profiles。

Now you need to create a unique App ID for NASA TV in Apple’s developer portal. Log into Apple’s iOS Developer Member Center and select Certificates, Identifiers & Profiles. 

选择在iOS Apps选项下Identifiers中的App IDs。它会显示出你当前所有App ID的列表。点击右上角的加号按钮创建一个新的App ID。

Select Identifiers under the iOS Apps heading, and then App IDs. This will show you a complete list of all your current App IDs. Click on the plus button in the top right corner to create a new App ID.

输入NASA TV作为你的新的App ID名字,使App ID Prefix保持默认值(Team ID),并在App ID Suffix下选中绑定ID的圆形复选框(Explicit App ID),在输入框中输入com.razeware.NASA-TV(但是要将rezeware替换成你的名字或者你公司的名字)。其实它就是在Info.plist中定义NASA TV的绑定ID。

Enter NASA TV as the name of your new App ID, leave the App ID Prefix as the default value (Team ID), and under App ID Suffix select the radio box Explicit App ID. For Bundle ID, type in com.razeware.NASA-TV (but replace razeware with your own name or company name). This is the bundle ID that identifies NASA TV in Info.plist.

最后,在App Services中的复选框中选中Push Notification。

Finally, select the checkbox next to Push Notifications under the section called App Services.

将你刚刚填入的信息核对两遍;核对完后,选择Continue,然后为你的NASA TV注册你的新App ID。

Double check all the information you just entered; if everything checks out, select Continue and then Submit to register your new App ID for NASA TV.

回到App IDs的列表,选择你刚刚为NASA TV创建的ID; 结果应该是:Game Center 和In-App Purchase 栏显示的是 Enabled ,但 Push Notifications 栏显示的是 Configurable。

Back in the list of App IDs, select the ID you just created for NASA TV; it should say that Game Center and In-App Purchase are Enabled but that Push Notifications are Configurable.

点击Edit按钮,并滚动到Push Notification部分。应该会看到两个不同的“Create Certificate..”按钮:一个是Development SSL中的,另一个是Production SSL中的。在Development SSL Certificate部分,选择Create Certificate...按钮。

Click the Edit button and scroll down to the Push Notification section. You should see two different “Create Certificate…” buttons: one for the Development SSL certificate and another one for the Production SSL certificate. In the Development SSL Certificate section, select the Create Certificate… button.

注意:development certificate是使用应用的调试构建模式测试推送通知的。而production SSL证书是用于上传到应用商店发布构建使用的。

Note: The development certificate is used for testing push notifications with debug builds of the app. The production SSL certificate would be used with the release build submitted to the App Store.

为了创建SSL证书,你需要上传你在Keychain Access中创建的.certSigningRequest文件。选择Continue,然后选择Choose File...并在你的桌面上找到CSR。然后点击Generate。

To create the SSL certificate, you’ll have to upload the .certSigningRequest file you created using Keychain Access. Select Continue and then Choose File… and navigate to the CSR on your Desktop. Then click Generate.

当上传完毕后,你可以从App ID设置界面下载SSL证书,如下图所示:

To create the SSL certificate, you’ll have to upload the .certSigningRequest file you created using Keychain Access. Select Continue and then Choose File… and navigate to the CSR on your Desktop. Then click Generate.

在SSL的证书上双击Download将其安装到你的keychain。打开Keychain Access并找到你刚刚添加到MyCertificates下的证书;它的名字叫App Development iOS Push Services:com.razeware.NASA—TV,如下截屏所示:

Double click on the downloaded SSL certificate to install it to your keychain. Open Keychain Access and find the certificate you just added under My Certificates; it’s called  Apple Development iOS Push Services: com.razeware.NASA-TV, as shown in the screenshot below:

在证书上右键选择“Export:Apple Development IOS Push Service...”,然后你将会看到如下对话框。

Right-click on the certificate and select “Export Apple Development IOS Push Services…” and you’ll be presented with the following dialog:

选择Personal Information Exchange(.pl2)作为文件的格式,并点击Save。这时你将会得到一个为.pl2文件创建密码的机会。

Select Personal Information Exchange (.p12) as the file format and click Save. At this point you will be given the option of entering a password to protect the .p12 file. 

不要输入任何密码;只要点击OK就行。你将需要输入你的OS X的密码,这样Keychain Access才能导出你的证书。

Don’t enter any password; just select OK.  You may be asked to enter your OS X password so that Keychain Access can export your certificate. 

如果一切顺利,你应该保存了一个.pl2文件,并将其用于发送推送通知。

接下来就是创建配置文件了,这样就可以注册你的设备,并接受推送通知了。

If everything went smoothly, you should have a .p12 saved and ready to be used to send push notifications. 

The next step is to create a provisioning profile so that your device can register for and receive push notifications.

注意:一个配置文件将一个开发者,一个App ID和一套程序允许运行设备绑在了一起。你可以使用你的通用配置文件(它不针对任何一个App ID)在你的开发设备上运行任何应用。

在这里你就不能这样做了,因为如果你使用通用配置文件签名的应用,那么推送通知将不会工作。你必须使用你刚刚创建的可以推送的App ID去创建一个配置文件。

Note: A provisioning profile ties together a developer, an App ID and a set of devices that the application is allowed to run on. You could normally use your wildcard provisioning profile, which is not specific to any one App ID, to get any app to run on one of your development devices. 

You can’t do this here because a push notifications won’t work if you sign your app with your wildcard provisioning profile. You have to create a provisioning profile using the push-enabled App ID you created a minute ago.

再做一遍,登录到苹果开发者中心并选择Certificates,Identifiers和Profiles。

Once again, log into Apple’s developer portal and select Certificates, Identifiers & Profiles. 

在iOS部分,选择Provisioning Profiles并点击加号按钮创建一个新的配置文件。

In the iOS section, select Provisioning Profiles and click the plus button to create a new provisioning profile.

选择iOS App Development作为配置文件的类型并点击Continue。为完成本章中目标,你不需要创建一个App Store配置文件。但是如果你想将NASA TV上传到App Store上去你就需要创建一个了。

Select iOS App Development as the provisioning profile type and click Continue. You won’t need to create an App Store provisioning profile for the purposes of this book chapter, but you’d need to create one as well if you were submitting NASA TV to the App Store.

选择你为NASA TV创建的App ID,并点击Continue。

Select the App ID that you created for NASA TV and click on Continue:

接下来选择你希望能够为这个应用签名的iOS Development证书,并点击Continue。如之前提过的,此证书将你的身份确认为开发者:

Next, select your iOS Development certificate(s) you wish to be able to sign this app and click on Continue. As mentioned before, this certificate identifies you as a developer:

下一步,要求你选择你要绑定到这个新配置的设备。选择你要用于测试静音推送通知的设备,并选择Continue。

Next, select your iOS Development certificate(s) you wish to be able to sign this app and click on Continue. As mentioned before, this certificate identifies you as a developer:

注意:如果你的设备已经被添加到了你的开发者中心,那么它将会出现在上面的列表中。如果还没被添加进去,你可以在Xcode的管理器创窗口中完成它。

Note: Your device will only appear on the list above if it’s already been added to your developer portal. If it hasn’t, you can do this in Xcode’s Organizer window.

最后,为你的配置文件选择一个描述性的名字,如NASA TV Push-Enabled Development Profile,并点击Generate按钮。

Finally, choose a descriptive name for your provisioning profile such as NASA TV Push-Enabled Development Profile and click on the Generate button:

从接下的显示窗口中下载你的可推送配置文件,并双击此文件将其安装到Xcode中。

Download your push-enabled provisioning profile from the next screen and install it in Xcode by double-clicking the downloaded file. 

如果一切顺利,你在Xcode详细账号中应该可以看到你的新配置文件。在Xcode中打开Preferences窗口并切换到Accounts栏。选择你的开发者账号并点击View Detail...按钮,你将会看到你的新配置文件,如下图所示:

If all went well, you should see it your new provisioning profile in the Xcode account details. Open the Preferences window in Xcode and switch to the Accounts tab. Select your developer account and click the View Details… button, and you’ll see your new provisioning profile, as shown below:

最后一步,使用新的配置文件去匹配你的NASA TV工程。

The final step is to match up the NASA TV project with the new provisioning profile.

在左侧菜单栏中选择工程文件。选择右上角的Build Setting栏,并将Basic替换成All,将Combined替换成Levels。这些设置选项显示的是工程权限的设置和单独目标权限的设置。

检查一下Code Singning,Identity和Provisioning Profile.

Select the project file in the left-hand menu. Select the Build Settings tab and in the top right corner select All instead of Basic and Levels instead of Combined. This set of options should show you what the settings are at the project level as well as the individual target level.

Look for Code Signing Identity as well as Provisioning Profile.

在tartget level中确保Debug选择了正确的签名身份。这应该和创建可推送通知配置文件的开发证书是一样的。

Make sure the correct signing identity is selected for Debug at the target level. This should be the same development certificate that you used to create the push-enabled provisioning profile.

在target level中同样要确保Provisioning Profile指向的是可推送配置文件。

Also make sure that Provisioning Profile points to the push-enabled provisioning profile at the Target level. 

在project level(第二栏)和target level(第三栏)更改这些设置。如果最左边的Resoved栏中的Code Signing Identity和Provisioning Profile的值被赋予了正确的值,那么这些设置正确了。

Change these settings at the project level (second column) as well as the target level (third column). You’ll know the settings are set correctly when the leftmost column Resolved has the correct values for Code Signing Identity and Provisioning Profile assigned.

使用Parse实现推送通知

Using Parse for push notifications

无论是静音推送通知还是是其他什么类型的通知,都需要一个web服务去和Apple Push Notification Service(APNS)进行沟通。我们并不会去建设一个自定义web服务,而是使用Parse这个快捷方案。

Push notifications, silent or otherwise, require a web server to talk to the Apple Push Notification Service (APNS). Instead of building a custom web service from scratch, you’re going to take a shortcut and use Parse.

将Parse整合到NASA TV上去的第一步是创建一个Parse账号,进入Parse的官方网站并点击右上角的Sign Up进行注册。

The first step in integrating Parse into NASA TV is to create a Parse account. Go to Parse’s homepage and click on Sign Up in the upper right hand corner. 

输入用户名,邮件地址和密码,点击Sign Up按钮创建一个账号,如下截屏所示:

Create an account with a username and password and click on Sign Up again, shown in the screenshot below:

接下来需要你填写你的应用名,并注册成为一名Individual Developer(个人开发者)。下一步,点击Start using Parse,如图所示:

Next, type NASA TV where it asks you to write your app’s name and sign up as an Individual Developer. Next, click on Start using Parse, as shown below:

一般情况下,现在你已经下载并安装好了Parse的SDK。但是一开始NASA TV就已经包含了最新版本的Parse SDK和所需要的框架。

Ordinarily, you’d download and install Parse’s SDK at this point. However, the starter project for NASA TV already contains the latest version of the SDK and required frameworks as of this writing.

还记得在你的桌面的应用中设置的.pl2证书文件吗?现在将它上传到Parse中。

Remember the .p12 certificate file that is sitting on your Desktop collecting dust? It’s time to upload it to Parse.

进入Parse,在左上角的下拉菜单中选择NASA TV,打开NASA TV的仪表盘,并选择左上角的Setting栏。

Go to Parse, navigate to NASA TV’s dashboard by selecting NASA TV in the top left drop down menu and click on the Settings tab in the top right corner. 

在Setting里,点击左侧菜单中的Push notifications。你将看见如下图所示的画面:

将Client push enabled切换成ON,并在Apple Push Certificate部分上传你的.pl2文件。确认一下证书绑定的标识是com.razeware.NASA-TV。同时确认一下证书的类型是Development。

Turn the Client push enabled switch to ON and upload your .p12 file in the section named Apple Push Certificate. Verify that the certificate bundle identifier is com.razeware.NASA-TV. Also verify that the certificate type is Development.

既然相关的设置已经完成,那么现在就可以将Parse SDK整合到NASA TV中去了。打开AppDelegate.m,并在文件头部加入如下导入声明:

Now that the setup is complete, you can now integrate the Parse SDK into NASA TV. Open AppDelegate.m and insert the following import statement at the top of the file:

#import <Parse/Parse.h>

接下来定位到application:didFinishLaunchingWithOptions:方法,并在return语句之前添加如下几行代码:

After that, scroll down to application:didFinishLaunchingWithOptions: and add the following lines before the return statement:

[Parse setApplicationId:@"YOUR-APP-ID"

          clientKey:@"YOUR-CLIENT-KEY"];

在Parse的NASA TV的Setting仪表盘中,选择左侧菜单栏中的Application Keys,你将会找到你的应用ID和你的客户端密钥,如下图所示:

You can find your application ID as well as your client key by navigating to NASA TV’s Settings dashboard in Parse and selecting Application keys from the left hand menu, as shown below:

在application:didiFinishLaunchingWithOptions:方法返回之前添加如下代码:

Before returning from application:didFinishLaunchingWithOptions: add the following line of code:

// Register for push notifications

[application registerForRemoteNotificationTypes:

     (UIRemoteNotificationTypeBadge |

      UIRemoteNotificationTypeAlert |

      UIRemoteNotificationTypeSound)];

registerForRemoteNotificationTypes:方法将会使iOS去询问会用是否愿意接受来自NASA TV的推送通知。

registerForRemoteNotificationTypes: prompts iOS to ask the user if they would like to receive push notifications from NASA TV. 

接下来,在AppDelegate.m文件的末尾添加如下两个方法,这两个方法是直接从Parse文档里复制过来的。

Next, scroll to the end of AppDelegate.m and add following two methods, which come straight from the Parse documentation:

#pragma mark - Push notification

- (void)application:(UIApplication *)application

didRegisterForRemoteNotificationsWithDeviceToken:

(NSData *)newDeviceToken {

    

    PFInstallation *currentInstallation =

    [PFInstallation currentInstallation];

    [currentInstallation setDeviceTokenFromData:newDeviceToken];

    [currentInstallation saveInBackground];

}

- (void)application:(UIApplication *)application

didReceiveRemoteNotification:(NSDictionary *)userInfo {

    [PFPush handlePush:userInfo];

}

上面所示的第一个委托方法是实现静音推送通知所需的方法。如果用户选择接受来自NASA TV的推送通知,那么他们的设备就会被一个设备身份标识所签名。这种标识返回的是一个名为newDeviceToken的NSData类型格式。

00005 The first delegate method shown above is required to implement silent push notifications. If the user elects to receive push notifications from NASA TV, their iOS device is assigned a device identifier token. This token comes back in the form of an opaque NSData called newDeviceToken.

你需要使用PFInstallation对象将这个标识发给Parse,这样你的设备就可以接受来自APNS的推送通知了。

00006You have to give this token to Parse using the PFInstallation object so that your device can start receiving push notifications from APNS.

第二个委托方法将会处理有一般的推送通知,并帮助你确认有关Parse的所有设置是否合理。

00007The second delegate method handles regular push notifications and will help you verify that everything was set up properly with Parse.

在你的真实设备上构建并运行你的工程;你将会被询问NASA TV是否可以给你发送推送通知。点击OK接受。

00008Build and run your project on a physical device; you should see an alert view asking you if NASA TV can send you push notifications. Tap OK to accept.

注意:你只会看到这个推送通知提示一次。如果你不小心选择了“Don’t Allow”,你将需要去Setting应用的Notification Center部分更改设置。

Note: You’ll only see this push notification alert view once. If you select “Don’t Allow” by accident, you’ll have to go to the Notification Center section of the Settings app to change the setting.

现在回到Parse,并选择NASA TV应用的Push NOtification栏,点击右上角的Send a Push。如果你看到如下图所示的“1 recipient”,这就意味着你的设备已经成功将它的设备标识上传给了Parse。

Now go back to Parse and select the Push Notifications tab of the NASA TV app. 

Click on the Send a Push button in the top-right. If you see “1 recipient” on the right side of the screen, as shown below, this means your device successfully uploaded its device token to Parse.

要想发送一个推送通知,只要在“Compose message”下面输入一小段信息,并在屏幕的底部点击“Send Notification”按钮。

To send a push notification, type a short message under “Compose message” and click the “Send Notification” button at the bottom of the screen.

你的iOS设备应该立即接收到此推送通知,并显示刚刚你在Parse中输入的信息,如下图所示:

Your iOS device should immediately receive the push notification and present the message you just typed into Parse front and center, like so:

实现静音推送通知

Implementing silent push notifications

好的,一般的推送通知好像工作的很正常。现在是时候去实现静音推送通知了。

当今天有了一张新的NASA图片,你想让NASA TV应用通过后台下载这张图片来保持更新。你将会使用一个静音推送通知去告诉此应用:这张图片可以下载了。

When there’s a new NASA photo of the day, you want the NASA TV app to stay up to date by downloading the photo in the background. You’ll use a silent push notification to let the app know the photo is available.

就像后台获取那样,第一件事就是在Xcode中注册远程通知。在Xcode中,点击工程文件并导航到新的Gapabilities栏,如下图所示:

Like background fetching, the first thing you have to do is register for remote notifications in Xcode. In Xcode, click on the project file and navigate to the new Capabilities tab, as so:

在最后一章中BackgroundModes后面的开关应该已经设置成了ON。如果不是的话,那么现在将其打开。

The switch next to Background Modes should already be set to ON from the last chapter. If, not, turn it on now. 

然后选择Remote notification后面的复选框。你也可以在Info.plist中手动将remote-notification密钥添加到UIBackgroundModes。

Then select the checkbox next to Remote notifications. This is the same as adding the remote-notification key to the UIBackgroundModes array in Info.plist manually.

接下来,打开AppDelegate.h文件,并添加以下属性到公共接口:

Next, open AppDelegate.h and add the following block property to the public interface:

@property (copynonatomic)

void(^silentRemoteNotificationCompletionHandler)

(UIBackgroundFetchResult);

    //1

    self.silentRemoteNotificationCompletionHandler =

    completionHandler;

    //2

    UIStoryboard* sb =

    [UIStoryboard storyboardWithName:@"Main" bundle:nil];

    

    PhotoViewController* photoViewController =

    [sb instantiateViewControllerWithIdentifier:

     @"PhotoViewController"];

    

    UINavigationController *navController =

    [[UINavigationController alloc]

     initWithRootViewController:photoViewController];

    

    UITabBarController* rootViewController =

    (UITabBarController *)self.window.rootViewController;

    

    //3

    [rootViewController

     presentViewController:navController

     animated:YES

     completion:nil];

}

当你的应用接受到一个静音推送通知时,这个新的委托方法将会执行。就像前一章中介绍的后台获取委托方法那样,这个方法接受了一个(void(^)(UIBackgroundFetchResult reult)类型的完成处理器。

This new delegate method runs when your application receives a silent push notification.  Just like the background fetching delegate method from the previous chapter, this method receives a completion handler of type (void(^)(UIBackgroundFetchResult result). 

为了理解静音推送通知是如何被处理的,请认真看如下解释:

00009To see how the silent push notifications are handled, head through the code step-by-step:

1.在公共属性silentRemoteNotificationCompletionhandler区域保存完成器。不需要将完成处理器交给PhotoViewController。在这里,当完成处理器在下载今天的NASA TV图片时,PhotoViewCOntroller会自动检查它。

1. Save the completion handler in the public block property silentRemoteNotificationCompletionHandler. Instead of handing the completion handler to PhotoViewController, PhotoViewController will look for it here when it’s done downloading NASA’s photo of the day.

2.在main storyboard中创建一个PhotoViewController的实例。如果你查看Main.storyboard,你将会看到PhotoViewController并没有前驱和后续,所以这是其中一个获得一个控制器的方法。

2. Instantiate an instance of PhotoViewController from the main storyboard. If you inspect Main.storyboard, you’ll see that the PhotoViewController scene has no incoming or outgoing segues, so this is one of the few ways of getting a hold of one.

3.最后,将PhotoViewController从根UITabBarController中显示出来。此PhotoViewController负责响应下载NASA的日常图片,并调用完成处理器。

3. Finally, present the PhotoViewController from the root UITabBarController. The PhotoViewController is responsible for downloading NASA’s daily photo and calling the completion handler.

现在切换至PhotoViewController.m文件,在文件的头部添加如下导入声明:

Now switch to PhotoViewController.m and add the following import statement at the top of the file:

#import "AppDelegate.h"

接下来,在文件的头部导入声明语句的下方,添加如下属性和协议:

Next, add the following properties and protocols to the top of the file, beneath the imports:

@interface PhotoViewController () <NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate>

@property (strongnonatomic) NSURLSession* urlSession;

@property (strongnonatomic

  NSURLSessionDownloadTask* downloadTask;

@property (strongnonatomic) NSString* photosDirectoryPath;

@end

NSURLSession和下载任务会负责下载照片。另外,photoDirectoryPath将会指向用于保存所有你的日常照片的存储目录。

The NSURLSession and download task will download the photo. photosDirectoryPath, on the other hand, will point to the directory that stores all of your daily photos.

接下来,实现viewWillAppear:方法,如下所示:

Next, implement viewWillAppear: as follows:

- (void)viewWillAppear:(BOOL)animated {

    [super viewWillAppear:(BOOL)animated];

    

    //1

    NSString* baseURLString =

    [[NSUserDefaults standardUserDefaults]

     objectForKey:@"baseURLString"];

    

    NSString* urlString =

    [NSString stringWithFormat:@"%@%@",

     baseURLString, @"/photos/dailyphoto.jpg"];

    

    NSURL *photoURL = [NSURL URLWithString:urlString];

    

    NSURLRequest *request =

    [NSURLRequest requestWithURL:photoURL];

    //2

    NSString* sessionIdentifier =

    @"com.razeware.backgroundsession.dailyphoto";

    

    NSURLSessionConfiguration *configuration =

    [NSURLSessionConfiguration

     backgroundSessionConfiguration:sessionIdentifier];

    

    self.urlSession =

    [NSURLSession

     sessionWithConfiguration:configuration

     delegate:self

     delegateQueue:nil];

    

    //3

    self.downloadTask = [self.urlSession

                         downloadTaskWithRequest:request];

    

    [self.downloadTask resume];

}

这些和后台传输所做的事情大部分是相同的。关于各个部分的解释简要概括成如下:

1.其实,你应该直接从NASA TV的网站中下载图片。但是考虑到本教程的用意,你将会从本地MAMP服务中下载一张图片。

1.In reality, you’d be downloading the image straight from NASA’s website. However, for the purposes of this tutorial you’ll download an image from your local MAMP server.

2.使用com.reeware.backgroundsession.dailyphoto身份标识创建一个后台NSURLSession。身份标识是很重要的,因为它可以防止和程序中的其他后台传输发生冲突。

2.Create a background NSURLSession with identifier com.razeware.backgroundsession.dailyphoto. It’s important that the identifier is unique to avoid collisions with other background transfers elsewhere in the app.

3.最后,激活所有的NSURLSessionTask对象,包括其所有子类对象。Resume实例方法将恢复开始操作。

3.Finally, recall that all NSURLSessionTask objects, including all of its subclasses, start in a suspended state. The instance method resume starts the operation.

定位到文件的底部,并重写photoDirectoryPath的getter,如下所示:

00010Scroll to the bottom of the file and override the photosDirectoryPath getter as follows:

- (NSString*)photosDirectoryPath {

    

    if (!_photosDirectoryPath) {

        

        NSArray* paths =

        NSSearchPathForDirectoriesInDomains(NSCachesDirectory,

                                            NSUserDomainMask,

                                            YES);

        _photosDirectoryPath =

        [paths[0stringByAppendingPathComponent:

         @"com.razeware.photos"];

        

        BOOL directoryExists =

        [[NSFileManager defaultManager]

         fileExistsAtPath:_photosDirectoryPath];

        

        if (!directoryExists) {

            

            NSError* error;

            if (![[NSFileManager defaultManager]

                  createDirectoryAtPath:_photosDirectoryPath

                  withIntermediateDirectories:NO

                  attributes:nil

                  error:&error]) {

                /* Could not create directory */

                /* Handle NSFileManager error */

            }

        }

    }

    return _photosDirectoryPath;

}

self.photosDirectoryPath第一次被访问时,此getter方法将会在,,,/Libarary/Caches/中创建一个名为com.razeware.photos的目录,就像本章节前面为视频文件所做的那样。

00011The first time self.photosDirectoryPath is accessed, this getter method creates a directory called com.razeware.photos in …/Library/Caches/, just as you did for videos earlier in this chapter.

现在实现URLSession:downloadTask:didFinishDownloadingToURL:,如下所示:

00012Now implement URLSession:downloadTask:didFinishDownloadingToURL: as shown below:

#pragma mark - NSURLSessionDownloadTaskDelegate methods

- (void)URLSession:(NSURLSession *)session

      downloadTask:(NSURLSessionDownloadTask *)downloadTask

didFinishDownloadingToURL:(NSURL *)downloadURL {

    

    NSString* lastPathComponent =

    [downloadTask.originalRequest.URL lastPathComponent];

    

    NSString* destinationPath =

    [self.photosDirectoryPath

     stringByAppendingPathComponent:lastPathComponent];

    

    NSURL* destinationURL =

    [NSURL fileURLWithPath:destinationPath];

    

    BOOL fileExists =

    [[NSFileManager defaultManager]

     fileExistsAtPath:destinationPath];

    

    NSError* error;

    

    if (fileExists) {

        [[NSFileManager defaultManager]

         removeItemAtPath:destinationPath error:&error];

    }

    

    BOOL copySuccessful =

    [[NSFileManager defaultManager]

     copyItemAtURL:downloadURL

     toURL:destinationURL

     error:&error];

    

    if (copySuccessful) {

        dispatch_async(dispatch_get_main_queue(), ^{

            UIImage *image = [UIImage imageWithContentsOfFile:

                              [destinationURL path]];

            self.imageView.image = image;

        });

    }

    else {

        NSLog(@"Error: %@", error.localizedDescription);

    }

}

当下载任务完成,并且下载的文件已经保存到了本地临时文件中时,那么上面的委托方法将会执行。将你新下载的图片保存到self.pjhotosDirectoryPath中是比较安全的做法。

00013The above delegate method runs when the download task completes and the downloaded file has been saved to a temporary location; it safely stores your newly downloaded photo to the location in self.photosDirectoryPath.

下载已经完成,也就将你的照片保存到了你的本地文件系统中,这似乎是一件很让人欢欣鼓舞的事情。但是别高兴的太早,这并不是静音推送通知的所有内容。你还需要去调用完成处理器。

The download completed, you saved the photo to the file system and all is happy in the world. Not so fast, though — wasn’t this part of a silent push notification? That’s right – you still need to call the completion handler.

为了将所有东西都整合在一起,那得实现URLSession:task:didCompleteWithError:方法,如下所示:

To tie this all together, implement URLSession:task:didCompleteWithError: as shown below:

#pragma mark - NSURLSessionTaskDelegate methods

- (void)URLSession:(NSURLSession *)session

              task:(NSURLSessionTask *)task

didCompleteWithError:(NSError *)error {

    

    //1

    AppDelegate* appDelegate =

    (AppDelegate*)[[UIApplication sharedApplicationdelegate];

    

    void(^completionHandler)(UIBackgroundFetchResult) =

    appDelegate.silentRemoteNotificationCompletionHandler;

    

    //2

    if (error) {

        if (completionHandler) {

            completionHandler(UIBackgroundFetchResultFailed);

        }

        NSLog(@"Error : %@", error.localizedDescription);

    }

    else if (completionHandler) {

        [self postLocalNotification];

        completionHandler(UIBackgroundFetchResultNewData);

    }

    

    //3

    appDelegate.silentRemoteNotificationCompletionHandler = nil;

}

不要太在意这个方法的名字,它将调用委托中的成功和失败方法。以下解释了它是如何工作的:

Despite this method’s pessimistic name, it’s called both on success and failure of the delegate. Here’s how it works:

1.正如约定的那样,一旦下载完成,PhotoViewController将调用保存在AppDelegate中的完成处理器。

1. As promised, PhotoViewController retrieves the completion handler stored in AppDelegate once the download is complete.

2.就像后台获取那样,执行完成处理器需要传入一个参数,这个参数是三个UIBackgroundFetchResult值中的一个,它们是new data,no data和failure。如果没有出现错误,请使用UIBackgroundFetchResultNewData作为参数调用完成处理器,并发送一个本地通知提醒用户它们的照片已经准备好了。否则,使用UIBackgroundEetchResultNodata调用完成处理器。

2. Just as in background fetch, execute the completion handler passing in one of three possible UIBackgroundFetchResult values: new data, no data, or failure. If there is no error, call the completion handler with UIBackgroundFetchResultNewData and post a local notification to alert the user that their photo is ready. Otherwise, call the completion handler with UIBackgroundFetchResultNoData.  

3.为安全起见,将AppDelegate里的完成处理器置为nil。你应该不希望被调用的完成控制器是过期了的吧。

3. For safety, set the completion handler in AppDelegate to nil. You don’t want to be calling a stale completion handler, do you?

注意:这里假设这个特殊的静音推送通知只会在新照片可用时才会被发出去;因此,在这种情况下没必要去实现UIBackgroundFetchResultNoData。但是不保证在其他所有应用中的静音推送通知都是是这样的。

Note: The assumption here is that this particular silent push notification will only be sent out when a new photo is available; therefore in this case there’s no need to implement UIBackgroundFetchResultNoData. However, this may not be true for all applications of silent push notifications.

最后实现postLocalNotification:这个方法,如下所示:

Finally, implement postLocalNotification as shown below. 

- (void)postLocalNotification {

    

    UILocalNotification* localNotification =

    [[UILocalNotification allocinit];

    

    localNotification.fireDate = [NSDate date];

    localNotification.alertBody =

    @"Astronomy Picture of the Day Available";

    localNotification.applicationIconBadgeNumber++;

    

    [[UIApplication sharedApplication]

     presentLocalNotificationNow:localNotification];

}

对于用户来说,这有点像传统的远程推送通知。其实,现在已经结束了从静音推送通知到后台传输再到本地通知的整个过程。这种模式允许你100%确保你下载的内容在用户看到本地通知时,此内容已经整备就绪并且立即可以被查看到。

To the user, this looks like a traditional remote push notification. In reality, it’s the end of a journey that took you from silent push notification to background transfer to local notification. This pattern allows you to be 100% sure that the asset you downloaded is ready for viewing by the time the user sees the local notification.

在测试你的代码之前,添加如下两个下载任务委托方法:

Before you test out the code, add the following two download task delegate methods:

- (void)URLSession:(NSURLSession *)session

      downloadTask:(NSURLSessionDownloadTask *)downloadTask

      didWriteData:(int64_t)bytesWritten

 totalBytesWritten:(int64_t)totalBytesWritten

totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {

    

}

- (void)URLSession:(NSURLSession *)session

      downloadTask:(NSURLSessionDownloadTask *)downloadTask

 didResumeAtOffset:(int64_t)fileOffset

expectedTotalBytes:(int64_t)expectedTotalBytes {

    

}

添加它们只是为了符合编译器的要求,不然会抛出异常。你可以不现实它们,因为在此应用中并不会使用到它们。

You’re just adding them to satisfy the compiler, or else it will throw warnings. You can just leave them blank as you’re not using them in this app.

测试静音推送通知

Testing silent push notifications

发送你的第一个静音推送通知的所有工作都准备好了!在你的真实设备上构建并运行你的工程,并且你的设备已经连接上了Wi-Fi。为什么呢?因为图片下载将会在后台排队等候被处理,下载是在自由选择模式下工作的,而这只能在Wi-Fi上工作。

Everything is in place to send your first silent push notification! Build and run your project on a physical device, and ensure your device is connected to Wi-Fi. Why? Since the photo download will be queued in the background, the download will work in discretionary mode and only work over Wi-Fi.

NASA TV运行在前台时,按下Home键返回到Springboard中。确保Xcode上依然显示的是“Running NASA TV on......”

With NASA TV running in the foreground, press the Home button to go back to Springboard. Make sure Xcode still says “Running NASA TV on…”

回到Parse,并导航到NASA TV的推送通知面板上。点击Send a Push。这就是本章之前第一次发送推送通知的地方。在Compose message的下方将开关切换至JSON,如下图所示:

Go back to Parse and navigate to NASA TV’s push notification dashboard. Click Send a Push. This is where you sent the first push notification earlier in the chapter. Under Compose message flip the switch from Message to JSON, like so:

每个推送通知都有一个JSON包。你第一次使用的Message设置只是简单的将你的信息打包在一个JSON字典中。

Every push notification has a JSON payload. The Message setting you used the first time around simply wrapped your message in a JSON dictionary. 

之前发送的“Houston,we have a problem.”信息,其发送的格式是这样的:

The earlier message of “Houston, we have a problem.” was actually sent in this format:

{

    "aps": {

        "alert": "Houston, we have a problem.",

        "sound": "default"

    }

}

推送通知的信息被包含在一个叫aps的JSON字典中,要发送一个静音推送通知,你需要包含在aps字典中的东西只有content-available标记,如下所示:

The push notification information is contained inside a JSON dictionary called aps. 

To send a silent push notification, the only thing you have to include in the aps dictionary is the content-available flag, shown below:

{

    "aps": {

        "content-available": 1

    }

}

将上面的静音推送通知数据包粘贴到Parse的Compose message区域中,然后点击屏幕底部的Send notification按钮。

Paste the silent push notification payload above into the Compose message field in Parse, then click Send notification at the bottom of the screen. 

你应该会在你的设备屏幕上看到本地通知弹出来的画面。此外,NASA TV的应用图标应该有一个标记。

You should see the local notification pop up on your device's screen. In addition, NASA TV's app icon should have a badge of one. 

注意:如果你没有接收到来自Parse你发送的静音推送通知,请耐心等待一会。日常照片的下载是任意性的,所以完成下载可能比较缓慢。

Note: Be patient if you don't immediately see the local notification after you send the silent push notification from Parse. The daily photo download is discretionary so it may be slow to complete.

点击通知栏或者将要查看图片的应用图标:

干的漂亮!静音推送通知并不需要关联本地通知,但是在此情形下将它们整合起来去下载日常图片,并通知用户完成将,将会取得很好的效果。

Great job! Silent push notifications don't have to be coupled with local notifications but in this case they worked well together to download the daily photograph and notify the user on completion.

何去何从?

Where To Go From Here?

祝贺你!你已经成功实现了后台传输和静音推送通知,这是在iOS 7中的三个新的多任务处理API中的两个。前一章已经给你介绍了如何实现其中的第三个,即后台获取。

新的iOS 7多任务处理API使你能够完成之前的iOS中不能做的事情。从自动更新新闻到下载,甚至到你的应用崩溃,天空是唯一的限制了。

The new iOS 7 multitasking APIs enable you to do things that were never before possible in iOS. From auto-refreshing news feeds to downloads that continue even if your app crashes, the sky is the limit. J 

但是我们都知道,权力越大责任就越重。虽然本书没有全部涵盖,你应该引起注意的是:多任务处理API可以将数据保护和用户隐私问题变得更复杂。

However, as you well know — with great power comes great responsibility. Although not covered in the book, you should be aware that the new multitasking APIs can complicate issues of data protection and user privacy. 

你应该在后台应用切换管理器上保持你的应用快照保持为最新状态,但是如果你的快照可能会包含登陆凭证或者其他敏感信息(如你经常登陆所需的数据),那么你就得做额外的预防措施了。

You should aim to keep your app snapshot up to date in the new app switcher, but take extra precautions if your snapshot could contain login credentials or other sensitive information such as data you'd normally have to log in to access. 

把警告放一边,现在你已经掌握了让你的用户感觉好像你的应用一直在工作的知识。所以回到现实世界并使用这些新的多任务处理API去提前满足你的用户需求。他们将会为你所做的感到高兴。

Warnings aside, you now have the knowledge to leave your users feeling like your app "just works". So go out into the world and use the new multitasking APIs to anticipate your user's needs. They'll be glad you did.  

挑战

Challenges

完成本章的挑战将会给你更多关于静音推送通知(和一般的推送通知)的实践。惯例,完整的解决方案包含在你下载的本章资源文件夹里。

Completing this chapter's challenge will give you more practice with silent push notifications (and push notifications in general). As always, the complete solution is included in the resources folder you downloaded for this chapter.

如果你感觉你遇到了困难,不要急于去看解决方案。在寻求帮助之前先尽量克服这些困难和挑战;这才是学习的真正意义!

Don't rush to the solution if you feel like you're getting stuck. Work through the challenge as much as possible before seeking help; that's where the real learning happens!

挑战1:“非静音”推送通知

Challenge 1: "Unsilent" push notifications 

如果你是跟着本章做的,那么你应该有了一个完全具备从Parse接受推送通知的能力的应用。关于这个挑战,就是通过一个一般推送通知代替静音推送通知来完成下载今天NASA上的太空图片。

这里有一些提示:

If you've followed along with this chapter, you should have an app that's fully provisioned to receive push notifications from Parse. For this challenge, instead of downloading NASA's Astronomy Picture of the Day from a silent push notification, kick off the operation via a regular push notification.

Here are some hints:

· 定义处理一般推送通知的UIApplicationDelegate方法。此方法是在AppDelegate.m文件中包含来自Parse SDK的样板代码的两个方法中的其中一个方法。

· Identify the UIApplicationDelegate method that handles regular push notifications. It is one of the two methods that contains boilerplate code from the Parse SDK in AppDelegate.m. 

·将显示PhotoViewController的代码复制到应用处理一般推送通知的委托方法中。记住,PhotoViewController必须包含在一个UINavigationController中。

· Copy the code that presents PhotoViewController into the app delegate method that handles regular push notifications. Remember that PhotoViewController has to be embedded in a UINavigationController.

·进入Parse并发送一个测试推送通知。这里就没必要发送纯JSON数据包了。NASA TV应用应该显示一个提示框并且PhotoViewController应该显示在它的后面。

Go to Parse and send out a test push notification. There's no need to send raw JSON. The NASA TV app should show an alert view and a PhotoViewController should be presented behind it.

挑战2:使用metadata实现静音推送通知

Challenge 2: Silent push notifications with metadata 

虽然静音推送通知不会警告(提示)接受者,但是这并不意味者你不能将任意信息的数据包发给你的应用。你可以将其看做是应用和服务器沟通的另一种方式(用户永远看不到的信息)。唯一的限制就是通知数据包的长度不能超过256个字节。

Just because silent push notifications don't alert the recipient doesn't mean you can't send arbitrary information to your application embedded in the payload. You can think of this as another way to communicate between your application and your backend server — one that the user never gets to see! The only limitation is that the notification payload must not exceed 256 bytes.

在这个挑战中,添加一个自定义信息到静音推送通知(图片的名字)并将其显示在PhotoViewController的导航栏中。

In this challenge, add a custom field to the silent push notification — the name of the photo —and display it on PhotoViewController's navigation bar.

下面是你准备从Parse发送的JSON数据包:

This is the JSON payload you are going to send from Parse:

{

    "aps": {

        "content-available": 1

    },

    "photoTitle": "Nebula"

}

你的任务就是在应用接受到静音推送通知时在PhotoViewController的导航栏上显示“Nebula”。

Your task is to display "Nebula" in PhotoViewController's navigation bar when the app receives the silent push notification.

下面是一些提示:

Here are some hints:

·将数据包字典传递到处理金鹰推送通知的应用委托方法中。提取字典中的photo title并将其存储在一个NSString中。

· The payload dictionary is passed to the app delegate method that handles silent push notifications. Extract photoTitle from this dictionary and store it in an NSString.

·在AppDelegate.m中,显示完PhotoViewController之后将PhotoViewController的title属性改为photoTitle。

· Change PhotoViewController's title property to photoTitle after presenting PhotoViewController from AppDelegate.m.

·进入Parse,并在应用运行在前台时发送上面显示的通知。不要忘了发送纯JSON数据包,而不是使用Parse的消息接口。

· Go to Parse and send the notification shown above while the app is in the foreground. Don't forget to send raw JSON instead of using Parse's message interface.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值