您的位置:首页 > Web前端

iOS 7 SDK:后台传输服务(Background Transfer Service)

2014-05-23 11:40 274 查看
http://www.cocoachina.com/applenews/devnews/2013/1120/7380.html

本文主要说说如何使用iOS 7多任务处理中的后台传输服务(Background Transfer Service),并讲述如何创建一个app--当不在前台运行时也能下载文件的app。一旦完成全部下载,还会弹出一个通知信息提醒用户。

后台传输服务最初是由iOS 6引入的,允许app在前台和后台传输文件,但有时间限制。最大的问题是“limited minutes”不允许用户下载或上传比较大的文件。这也是苹果在iOS 7中改进这项功能的原因。

在iOS 7中,该功能发生了以下变化:
iOS系统管理下载和上传内容
当用户关闭app后继续传输内容
没有时间限制
可以随时加入队列(前台和后台)
为了处理认证、错误或者completion事件,app会被唤醒
该app包含一个Progress View

后台传输服务可以用来处理几个不同的有用的任务,比如上传照片或视频,结合后台获取和远程通知,以及保持应用更新(比如购买书籍、TV shows、游戏内容以及其他)。

1.设置工程
要创建该服务,我们需要一个single view,并包含如下属性:
A ViewController
A NavigationController
A Bar Item (to start the Download)
An UIDocumentInterationController (to open the PDF document download)

首先,开始一个新的Xcode iPhone工程。然后创建一个Single View app。接着到Main.Storyboard,在view中添加一些对象。想要添加NavigationController,选中Default View Controller。在Xcode菜单中,选择Editor > Embed In > Navigation Controller。把Bar Item和Progress View拖放至View Controller。一旦完成,View Controller

看起来是以下样式:



然后,添加必要的属性来与我们之前添加的对象进行交互。在ViewController.h添加以下代码:
@property (weak, nonatomic) IBOutlet UIProgressView *progressView; 
- (IBAction)start:(id)sender;

现在改变ViewController.m的视图。你会看到一个警示,不过不用担心,我们稍后再修复它。回到Main.Storyboard,并链接好相关的属性和动作,这些步骤比较琐碎。

2.NSURLSession
NSURLSession类和相关的类提供了一个可以通过HTTP上传或者下载内容的API,主要用来管理一系列传输任务。我们需要创建三个与这个类直接相关的对象:NSURLSession, NSURLSessionDownloadTask以及 UIDocumentInteractionController。

ViewController.h看起来会是这样:
@property (nonatomic) NSURLSession *session; 
@property (nonatomic) NSURLSessionDownloadTask *downloadTask; 
@property (strong, nonatomic) UIDocumentInteractionController *documentInteractionController;

另外,你还要声明四个协议:NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate以及UIDocumentInteractionControllerDelegate。

@interface应该是像这样的:
@interface ViewController : UIViewController < NSURLSessionDelegate, NSURLSessionTaskDelegate, NSURLSessionDownloadDelegate,UIDocumentInteractionControllerDelegate>

我们将要添加的属性对于session的实例化和操作,以及下载处理非常有好处:可以恢复、暂停、取消或检索状态信息。最后一个属性--UIDocumentInterationController在本文中是用来展示下载的PDF文档。

打开ViewController.m。第一个需要完成的任务是把字符串添加至文件下载的位置。你应该使用一个标准的苹果PDF。
static NSString *DownloadURLString = @"https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/ObjC_classic/FoundationObjC.pdf";

在viewDidLoad 方法中, 我们实例化并设置session和progress view:
self.session = [self backgroundSession]; 
self.progressView.progress = 0; 
self.progressView.hidden = YES;



由于你执行了一个并不存在的backgroundSession方法,所以你必须声明它,该方法主要用来产生一个在后台运行的会话。NSURLSessionConfiguration接收的NSString参数是我们创建的会话的ID,对于每个NSURLSession实例,这个ID必须是独一无二的。

完整的方法如下:
- (NSURLSession *)backgroundSession { 
  static NSURLSession *session = nil; 
  static dispatch_once_t onceToken; 
  dispatch_once(&onceToken, ^{ 
    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.example.apple-samplecode.SimpleBackgroundTransfer.BackgroundSession"]; 
    session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; 
  }); 
  return session; 
}

(IBAction)start:(id)sender方法开启了文档下载。你将会初始化一个NSURL和 NSURLRequest,并使用downloadTask属性把请求对象传递至downloadTaskWithRequest方法,完整的方法如下:
- (IBAction)start:(id)sender { 
  if (self.downloadTask) { 
        return; 
    } 
  NSURL *downloadURL = [NSURL URLWithString:DownloadURLString]; 
  NSURLRequest *request = [NSURLRequest requestWithURL:downloadURL]; 
  self.downloadTask = [self.session downloadTaskWithRequest:request]; 
    [self.downloadTask resume]; 
    self.progressView.hidden = NO; 
}


3.协议
在这一点上,你会注意到3个警示,它们表示应该执行协议方法。NSURLSessionDownloadDelegate协议定义了处理下载任务的方法。想要执行下载,它需要使用三个委托方法,所以添加以下三个方法:
1. (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:
(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite {
2. (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL {
3. (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes

(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite方法主要用来跟踪整个下载进程,同时也相应地更新
progressView。整个方法如下:

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite { 
    if (downloadTask == self.downloadTask) { 
        double progress = (double)totalBytesWritten / (double)totalBytesExpectedToWrite; 
        NSLog(@"DownloadTask: %@ progress: %lf", downloadTask, progress); 
        dispatch_async(dispatch_get_main_queue(), ^{ 
            self.progressView.progress = progress; 
        }); 
    } 
}


4.下载任务
(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL方法处理数据(origin and destination)。它在下载完成后控制文件。简化起见,它通知delegate下载任务已经完成。它包含已经完成的session task和download task,以及文件URL(可以在此找到临时文件)。看起来应该是这样:
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)downloadURL { 
    NSFileManager *fileManager = [NSFileManager defaultManager]; 
    NSArray *URLs = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask]; 
    NSURL *documentsDirectory = [URLs objectAtIndex:0]; 
    NSURL *originalURL = [[downloadTask originalRequest] URL]; 
    NSURL *destinationURL = [documentsDirectory URLByAppendingPathComponent:[originalURL lastPathComponent]]; 
    NSError *errorCopy; 
    // For the purposes of testing, remove any esisting file at the destination. 
    [fileManager removeItemAtURL:destinationURL error:NULL]; 
    BOOL success = [fileManager copyItemAtURL:downloadURL toURL:destinationURL error:&errorCopy]; 
    if (success) { 
        dispatch_async(dispatch_get_main_queue(), ^{ 
            //download finished - open the pdf 
            self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:destinationURL]; 
            // Configure Document Interaction Controller 
            [self.documentInteractionController setDelegate:self]; 
            // Preview PDF 
            [self.documentInteractionController presentPreviewAnimated:YES]; 
            self.progressView.hidden = YES; 
        }); 
    } else { 
        NSLog(@"Error during the copy: %@", [errorCopy localizedDescription]); 
    } 
}

最后 (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes必须也被声明,但要清楚我们不会进一步使用它。
-(void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes { 
}

5.Session Tasks
你几乎完成了这个类,只剩下两个方法: (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error和 - (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session.

第一个方法通知delegate任务已经完成了数据传输,你也应该用它来跟踪发生的任何错误。
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { 
    if (error == nil) { 
        NSLog(@"Task: %@ completed successfully", task); 
    } else { 
        NSLog(@"Task: %@ completed with error: %@", task, [error localizedDescription]); 
    } 
    double progress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive; 
  dispatch_async(dispatch_get_main_queue(), ^{ 
    self.progressView.progress = progress; 
  }); 
    self.downloadTask = nil; 
}

最后,你需要添加 (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session方法,它通知delegate所有的session消息列队已经交付。为了加载UILocalNotification,它实例化AppDelegate.你应该使用如下方法:
- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { 
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate]; 
    if (appDelegate.backgroundSessionCompletionHandler) { 
        void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler; 
        appDelegate.backgroundSessionCompletionHandler = nil; 
        completionHandler(); 
    } 
    NSLog(@"All tasks are finished"); 
}

由于你没有把AppDelegate.h移植到你的类中,所以会出现几个错误:
#import "AppDelegate.h"

把两个对象添加至AppDelegate.h:
@property (strong, nonatomic) UIWindow *window; 
@property (copy) void (^backgroundSessionCompletionHandler)();

在AppDelegate.m中,你应该执行delegate方法(void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler.它通知delegate--与URL session相关的事件正等待处理,并调用自定义方法(presentNotification)通知用户,在文件加载完毕时。完整的方法如下:
- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier 
  completionHandler:(void (^)())completionHandler { 
  self.backgroundSessionCompletionHandler = completionHandler; 
    //add notification 
    [self presentNotification]; 
}

6.本地通知
presentNotification方法用UILocalNotification类来创建本地通知。它创建一个声音,并使用在通知中使用徽标系统--badge system,完整方法如下:
-(void)presentNotification{ 
    UILocalNotification* localNotification = [[UILocalNotification alloc] init]; 
    localNotification.alertBody = @"Download Complete!"; 
    localNotification.alertAction = @"Background Transfer Download!"; 
    //On sound 
    localNotification.soundName = UILocalNotificationDefaultSoundName; 
    //increase the badge number of application plus 1 
    localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication] applicationIconBadgeNumber] + 1; 
    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification]; 
}

顺便提下,为了重新计算app icon左上角的counter,你必须在AppDelegate中把下边一行代码添加至 (void)applicationDidBecomeActive:(UIApplication *)application方法。
application.applicationIconBadgeNumber = 0;

通知应该类似于以下这张图:



现在运行app并测试后台加载。

总结
最后,你应该理解后台传输服务以及知道如何使用它。
原文:iOS 7 SDK: Background Transfer Service
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: