AFNetworking和后台传输

我对如何利用新的iOS 7 NSURLSession后台传输功能和AFNetworking(版本2和3)有点混淆。

我看到了WWDC 705 - What's New in Foundation Networking会议中WWDC 705 - What's New in Foundation Networking ,并且他们演示了在应用程序终止或崩溃后继续下载的后台下载。

这是通过使用新的API application:handleEventsForBackgroundURLSession:completionHandler:以及会话委托最终将获得回调并完成其任务的事实。

所以我想知道如何在AFNetworking中使用它(如果可能的话)以继续在后台下载。

问题是,AFNetworking方便地使用基于块的API来执行所有请求,但是如果应用程序终止或崩溃,那些块也不见了。 那么我该如何完成这项任务?

或者我在这里错过了一些东西......

让我解释我的意思:

例如,我的应用程序是一个照片消息应用程序,可以说我有一个PhotoMessage对象表示一个消息,并且此对象具有像

  • state - 描述照片下载的状态。
  • resourcePath - 最终下载的照片文件的路径。
  • 所以,当我从服务器收到新消息时,我创建了一个新的PhotoMessage对象,并开始下载其照片资源。

    PhotoMessage *newPhotoMsg = [[PhotoMessage alloc] initWithInfoFromServer:info];
    newPhotoMsg.state = kStateDownloading;
    
    self.photoDownloadTask = [[BGSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:^NSURL *(NSURL *targetPath, NSURLResponse *response) {
        NSURL *filePath = // some file url
        return filePath;
    } completionHandler:^(NSURLResponse *response, NSURL *filePath, NSError *error) {
        if (!error) {
            // update the PhotoMessage Object
            newPhotoMsg.state = kStateDownloadFinished;
            newPhotoMsg.resourcePath = filePath;
        }
    }];
    
    [self.photoDownloadTask resume];   
    

    正如你所看到的,我使用完成块来根据我得到的响应来更新PhotoMessage对象。

    我如何通过后台传输来实现? 该完成块将不会被调用,因此我无法更新newPhotoMsg


    一些想法:

  • 您必须确保您执行URL加载系统编程指南的处理iOS背景活动部分中所述的必要编码:

    如果您在iOS中使用NSURLSession ,则在下载完成时,您的应用程序会自动重新启动。 您的应用程序的application:handleEventsForBackgroundURLSession:completionHandler:应用程序委托方法负责重新创建适当的会话,存储完成处理程序,并在会话调用会话委托的URLSessionDidFinishEventsForBackgroundURLSession:方法时调用该处理程序。

    该指南展示了你可以做什么的一些例子。 坦率地说,我认为在WWDC 2013视频的后半部分中讨论的代码示例更加清晰。

  • 如果应用程序仅仅是暂停的, AFURLSessionManager的基本实现将与后台会话一起工作(假设你已经完成了上述任务,当网络任务完成时你会看到你的块被调用)。 但是,正如你猜测的那样,如果应用程序终止或崩溃,传递给AFURLSessionManager方法的任何特定于任务的块参数都将AFURLSessionManager ,而这些NSURLSessionTask用于创建用于上载和下载的NSURLSessionTask

    对于后台上传,这是一个烦恼(因为创建任务时指定的任务级信息进度和完成块不会被调用)。 但是,如果您使用会话级别的翻译(例如, setTaskDidCompleteBlocksetTaskDidSendBodyDataBlock ),那么将会正确调用(假设您在重新实例化会话管理器时始终设置这些块)。

    事实证明,丢失块的这个问题实际上对于后台下载来说更成问题,但是其中的解决方案非常相似(不使用基于任务的块参数,而是使用基于会话的块,例如setDownloadTaskDidFinishDownloadingBlock )。

  • 另一种方法是,您可以坚持使用默认(非后台) NSURLSession ,但如果用户在任务正在进行时离开应用程序,请确保您的应用程序请求一点时间来完成上传。 例如,在创建NSURLSessionTask之前,可以创建一个UIBackgroundTaskIdentifier

    UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
        // handle timeout gracefully if you can
    
        [[UIApplication sharedApplication] endBackgroundTask:taskId];
        taskId = UIBackgroundTaskInvalid;
    }];
    

    但请确保网络任务的完成模块正确地通知iOS它已完成:

    if (taskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:taskId];
        taskId = UIBackgroundTaskInvalid;
    }
    

    这不像后台NSURLSession那样强大(例如,你的时间有限),但在某些情况下,这可能很有用。


  • 更新:

    我想我会添加一个实例来说明如何使用AFNetworking进行后台下载。

  • 首先定义你的背景经理。

    //
    //  BackgroundSessionManager.h
    //
    //  Created by Robert Ryan on 10/11/14.
    //  Copyright (c) 2014 Robert Ryan. All rights reserved.
    //
    
    #import "AFHTTPSessionManager.h"
    
    @interface BackgroundSessionManager : AFHTTPSessionManager
    
    + (instancetype)sharedManager;
    
    @property (nonatomic, copy) void (^savedCompletionHandler)(void);
    
    @end
    

    //
    //  BackgroundSessionManager.m
    //
    //  Created by Robert Ryan on 10/11/14.
    //  Copyright (c) 2014 Robert Ryan. All rights reserved.
    //
    
    #import "BackgroundSessionManager.h"
    
    static NSString * const kBackgroundSessionIdentifier = @"com.domain.backgroundsession";
    
    @implementation BackgroundSessionManager
    
    + (instancetype)sharedManager {
        static id sharedMyManager = nil;
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            sharedMyManager = [[self alloc] init];
        });
        return sharedMyManager;
    }
    
    - (instancetype)init {
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:kBackgroundSessionIdentifier];
        self = [super initWithSessionConfiguration:configuration];
        if (self) {
            [self configureDownloadFinished];            // when download done, save file
            [self configureBackgroundSessionFinished];   // when entire background session done, call completion handler
            [self configureAuthentication];              // my server uses authentication, so let's handle that; if you don't use authentication challenges, you can remove this
        }
        return self;
    }
    
    - (void)configureDownloadFinished {
        // just save the downloaded file to documents folder using filename from URL
    
        [self setDownloadTaskDidFinishDownloadingBlock:^NSURL *(NSURLSession *session, NSURLSessionDownloadTask *downloadTask, NSURL *location) {
            if ([downloadTask.response isKindOfClass:[NSHTTPURLResponse class]]) {
                NSInteger statusCode = [(NSHTTPURLResponse *)downloadTask.response statusCode];
                if (statusCode != 200) {
                    // handle error here, e.g.
    
                    NSLog(@"%@ failed (statusCode = %ld)", [downloadTask.originalRequest.URL lastPathComponent], statusCode);
                    return nil;
                }
            }
    
            NSString *filename      = [downloadTask.originalRequest.URL lastPathComponent];
            NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
            NSString *path          = [documentsPath stringByAppendingPathComponent:filename];
            return [NSURL fileURLWithPath:path];
        }];
    
        [self setTaskDidCompleteBlock:^(NSURLSession *session, NSURLSessionTask *task, NSError *error) {
            if (error) {
                // handle error here, e.g.,
    
                NSLog(@"%@: %@", [task.originalRequest.URL lastPathComponent], error);
            }
        }];
    }
    
    - (void)configureBackgroundSessionFinished {
        typeof(self) __weak weakSelf = self;
    
        [self setDidFinishEventsForBackgroundURLSessionBlock:^(NSURLSession *session) {
            if (weakSelf.savedCompletionHandler) {
                weakSelf.savedCompletionHandler();
                weakSelf.savedCompletionHandler = nil;
            }
        }];
    }
    
    - (void)configureAuthentication {
        NSURLCredential *myCredential = [NSURLCredential credentialWithUser:@"userid" password:@"password" persistence:NSURLCredentialPersistenceForSession];
    
        [self setTaskDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLSessionTask *task, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) {
            if (challenge.previousFailureCount == 0) {
                *credential = myCredential;
                return NSURLSessionAuthChallengeUseCredential;
            } else {
                return NSURLSessionAuthChallengePerformDefaultHandling;
            }
        }];
    }
    
    @end
    
  • 确保应用程序委托保存完成处理程序(根据需要实例化后台会话):

    - (void)application:(UIApplication *)application handleEventsForBackgroundURLSession:(NSString *)identifier completionHandler:(void (^)())completionHandler {
        NSAssert([[BackgroundSessionManager sharedManager].session.configuration.identifier isEqualToString:identifier], @"Identifiers didn't match");
        [BackgroundSessionManager sharedManager].savedCompletionHandler = completionHandler;
    }
    
  • 然后开始下载:

    for (NSString *filename in filenames) {
        NSURL *url = [baseURL URLByAppendingPathComponent:filename];
        NSURLRequest *request = [NSURLRequest requestWithURL:url];
        [[[BackgroundSessionManager sharedManager] downloadTaskWithRequest:request progress:nil destination:nil completionHandler:nil] resume];
    }
    

    请注意,我不提供任何与任务相关的任何块,因为这些块与后台会话不可靠。 (即使在应用程序终止并且这些块已经消失的情况下,后台下载仍然继续。)必须依赖会话级别,只需轻松重新创建setDownloadTaskDidFinishDownloadingBlock

  • 很明显,这是一个简单的例子(只有一个后台会话对象;只需使用URL的最后一个组件作为文件名将文件保存到docs文件夹中;等等),但希望它能说明该模式。


    不管回调是否阻止,它都不会有什么区别。 当你实例化一个AFURLSessionManager ,一定要用NSURLSessionConfiguration backgroundSessionConfiguration:实例化NSURLSessionConfiguration backgroundSessionConfiguration: 此外,请务必使用回调块调用管理器的setDidFinishEventsForBackgroundURLSessionBlock - 这是您应该编写通常在NSURLSessionDelegate的方法中定义的代码的位置: URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session 。 此代码应该调用您的应用程序委托的后台下载完成处理程序。

    关于后台下载任务的一个忠告 - 即使在前台运行时,他们的超时也会被忽略,这意味着您可能在没有响应的下载中“卡住”。 这没有记录在任何地方,让我疯狂了一段时间。 第一个嫌疑犯是AFNetworking,但即使直接调用NSURLSession,行为仍然是一样的。

    祝你好运!


    AFURLSessionManager

    AFURLSessionManager根据符合<NSURLSessionTaskDelegate><NSURLSessionDataDelegate><NSURLSessionDownloadDelegate><NSURLSessionDelegate>的指定NSURLSessionConfiguration对象创建和管理NSURLSession对象。

    链接到文档这里的文档

    链接地址: http://www.djcxy.com/p/96771.html

    上一篇: AFNetworking and background transfers

    下一篇: AFNetworking 2.0, downloading image with NSURLSession