iOS7后台数据下载教程

本文介绍如何使用iOS7的BackgroundTransferService进行后台数据传输。通过具体实例,详细讲解了利用NSURLSession进行文件下载的过程,包括进度追踪、错误处理及下载完成后的操作。

原文:iOS 7 SDK: Background Transfer Service

这个教程将会教会你如何在后台传输数据,如何使用iOS7提供的多任务API。我将会教会你如何在后台下载一个文件,并且在文件下载完成时弹出一个本地的提醒。   后台数据传输起源于iOS6,允许在前台或者后台下载数据,但是时间是受限制的。最大的问题就是时间是受限制的让用户无法上传或下载大文件。这就是为什么苹果在iOS7上要提升框架的原因。 在iOS7中,这个功能有了很大变化,包括:

1.iOS系统管理上传和下载任务。

2.当用户关闭应用程序时后台仍然可以传输数据

3.时间不受限制

4.它可以在任意时间加入队列(前台或者后台)

5.应用程序需要被唤醒来获取验证,错误,或者完成情况

6.应用程序会有一个进度展示视图 后台传输可以应用在几个非常有用的地方:上传照片或者视频,结合后台提取和远程通知,用于保持应用程序在最新时间。

下面是范例工程。

注:原文,作者是去下载了一个PDF,由于下载的苹果那个PDF感觉比较慢,就改成下载我自己服务器上的一个mp3文件了。

1.创建工程 我们需要一个MainViewController来做这里的主要工作:

1)包含一个按钮,一个ProgeressView

像这样:

Snip20131213_3

 

然后,我们需要在工程头文件里面添加我们需要的成员。

完成之后,我们的MainViewController.h会像这样

 

#import <AVFoundation/AVFoundation.h>

@interface MainViewController : UIViewController<NSURLSessionDelegate,NSURLSessionTaskDelegate,NSURLSessionDownloadDelegate,UIDocumentInteractionControllerDelegate>
@property (weak, nonatomic) IBOutlet UIProgressView *progressView;

@property (nonatomic)NSURLSession *session;
@property (nonatomic)NSURLSessionDownloadTask *downloadTask;
@property (strong,nonatomic)UIDocumentInteractionController *documentInteractionController;
@property (nonatomic,strong)AVPlayer *player;

- (IBAction)startDownload:(id)sender;

下一步我们需要使用NSURLSession来完成任务。

我们要在viewDidLoad中初始化我们需要的session成员

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view from its nib.
    
    self.session = [self backgroundSession];
    self.progressView.progress = 0;
    self.progressView.hidden = YES;
    
}

 
然后我们要完成一个单一的session,使用dispatch_once来确保它会是同一个对象,里面有个要点,请仔细看注释
 

- (NSURLSession *)backgroundSession {
    static NSURLSession *session = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        //这个sessionConfiguration 很重要, com.zyprosoft.xxx  这里,这个com.company.这个一定要和 bundle identifier 里面的一致,否则ApplicationDelegate 不会调用handleEventsForBackgroundURLSession代理方法
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfiguration:@"com.zyprosoft.backgroundsession"];
        session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
    });
    return session;
}

我们再给开始按钮方法添加开始下载任务的执行

- (IBAction)startDownload:(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;
    
}

再接着我们需要来完成最重要的事情,把Session的代理方法完成

首先我们完成 NSURLSessionDownloadTaskDelegate

//这个方法用来跟踪下载数据并且根据进度刷新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(@"下载任务: %@ 进度: %lf", downloadTask, progress);
        dispatch_async(dispatch_get_main_queue(), ^{
            self.progressView.progress = progress;
        });
    }
}

//下载任务完成,这个方法在下载完成时触发,它包含了已经完成下载任务得 Session Task,Download Task和一个指向临时下载文件得文件路径
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location
{
    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:location toURL:destinationURL error:&errorCopy];
    if (success) {
        dispatch_async(dispatch_get_main_queue(), ^{
            //download finished - open the pdf
            
            //原文下载的苹果的pdf,感觉有点慢,自己又没找到比较大一点的pdf,干脆就放了个mp3歌曲在自己服务器上,所以下载完就播放mp3吧
//            self.documentInteractionController = [UIDocumentInteractionController interactionControllerWithURL:destinationURL];
//            // Configure Document Interaction Controller
//            [self.documentInteractionController setDelegate:self];
//            // Preview PDF
//            [self.documentInteractionController presentPreviewAnimated:YES];
//            self.progressView.hidden = YES;
            
            //播放音乐
            self.player = [AVPlayer playerWithURL:destinationURL];
            [self.player play];

            
        });
    } else {
        NSLog(@"复制文件发生错误: %@", [errorCopy localizedDescription]);
    }
}

//这个方法我们暂时用不上
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes
{
    
}

接着我们需要完成NSURLSessionTaskDelegate需要用到的代理方法

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error
{
    if (error == nil) {
        NSLog(@"任务: %@ 成功完成", task);
    } else {
        NSLog(@"任务: %@ 发生错误: %@", task, [error localizedDescription]);
    }
    double progress = (double)task.countOfBytesReceived / (double)task.countOfBytesExpectedToReceive;
    dispatch_async(dispatch_get_main_queue(), ^{
        self.progressView.progress = progress;
    });
    self.downloadTask = nil;
}

最后,我们要完成NSURLSessionDelegate代理方法

- (void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session
{
    ZYBackgroundServiceAppDelegate *appDelegate = (ZYBackgroundServiceAppDelegate *)[[UIApplication sharedApplication] delegate];
    if (appDelegate.backgroundSessionCompletionHandler) {
        void (^completionHandler)() = appDelegate.backgroundSessionCompletionHandler;
        appDelegate.backgroundSessionCompletionHandler = nil;
        completionHandler();
    }
    NSLog(@"所有任务已完成!");
}

这里我们用到了AppDelegate里面的一个成员block,用来执行当后台下载完成时调度这个方法。所以我们还要去完成我们的AppDelegate

我们在AppDelegate.h里面添加一个完成时执行的block,添加完应该是这样的

@interface ZYBackgroundServiceAppDelegate : UIResponder <UIApplicationDelegate>

@property (strong, nonatomic) UIWindow *window;
@property (copy) void (^backgroundSessionCompletionHandler)();

@end

然后,我们去AppDelegate.m里面完成我们需要弹出本地提醒的方法就大功告成了!

在AppDelegate.m里面,我们需要完成下面几个重要的方法:

添加完应该是这样的

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
    // Override point for customization after application launch.
    self.window.backgroundColor = [UIColor whiteColor];
    
    //初始化
    MainViewController *mainViewController = [[MainViewController alloc]initWithNibName:@"MainViewController" bundle:Nil];
    mainViewController.title = @"后台下载测试Demo";
    UINavigationController *rootNav = [[UINavigationController alloc]initWithRootViewController:mainViewController];
    self.window.rootViewController = rootNav;
    
    [self.window makeKeyAndVisible];
    return YES;
}

- (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier
  completionHandler:(void (^)())completionHandler {
    self.backgroundSessionCompletionHandler = completionHandler;
    //添加本地通知
    [self presentNotification];
}

-(void)presentNotification{
    UILocalNotification* localNotification = [[UILocalNotification alloc] init];
    localNotification.alertBody = @"下载完成!";
    localNotification.alertAction = @"后台传输下载已完成!";
    //提示音
    localNotification.soundName = UILocalNotificationDefaultSoundName;
    //icon提示加1
    localNotification.applicationIconBadgeNumber = [[UIApplication sharedApplication] applicationIconBadgeNumber] + 1;
    [[UIApplication sharedApplication] presentLocalNotificationNow:localNotification];
}

我们最后需要当用户点击进来时,把图标上面的数字提醒设置为0

- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    
    application.applicationIconBadgeNumber = 0;
    
}

最终我们运行效果会是这样的:

iOS 模拟器屏幕快照“2013年12月13日 下午5.29.58” iOS 模拟器屏幕快照“2013年12月13日 下午5.30.34” iOS 模拟器屏幕快照“2013年12月13日 下午5.30.44”

 

最后的最后是工程的源代码了:

ZYBackgroundService

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值