AFNetworking和后台转移 [英] AFNetworking and background transfers

查看:100
本文介绍了AFNetworking和后台转移的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm a bit confuse of how to take advantage of the new iOS 7 NSURLSession background transfers features and AFNetworking (versions 2 and 3).

我看到 WWDC 705 - 基础网络中的新功能会话,他们展示了应用程序终止甚至崩溃后继续下载的后台下载。

I saw the WWDC 705 - What’s New in Foundation Networking session, and they demonstrated background download that continues after the app terminated or even crashes.

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

This is done using the new API application:handleEventsForBackgroundURLSession:completionHandler: and the fact that the session's delegate will eventually get the callbacks and can complete its task.

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

So I'm wondering how to use it with AFNetworking (if possible) to continue downloading in background.

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

The problem is, AFNetworking conveniently uses block based API to do all the requests, but if the app terminated or crashes those block are also gone. So how can I complete the task?

或许我在这里遗漏了一些东西...

Or maybe I'm missing something here...

让我解释一下我的意思:

Let me explain what I mean:

例如我的应用程序是一个照片消息应用程序,假设我有一个 PhotoMessage 对象代表一条消息,这个对象具有类似

For example my app is a photo messaging app, lets say that I have a PhotoMessage object that represent one message and this object has properties like


  • - 描述照片下载的状态。

  • resourcePath - 最终下载的照片文件的路径。

  • state - describe the state of the photo download.
  • resourcePath - the path to the final downloaded photo file.

因此,当我从服务器收到新消息时,我创建了一个新的 PhotoMessage 对象,并开始下载其照片资源。

So when I get a new message from the server, I create a new PhotoMessage object, and start downloading its photo resource.

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 对象根据我得到的响应。

As you can see, I use the completion block to update that PhotoMessage object according to the response I get.

如何通过后台传输实现这一目标?这个完成块不会被调用,因此我无法更新 newPhotoMsg

How can I accomplish that with a background transfer? This completion block won't be called and as a result, I can't update the newPhotoMsg.

推荐答案

一些想法:


  1. 你必须确保你做了必要的编码概述在 URL加载系统编程指南的nofollow noreferrer>处理iOS后台活动部分说:

  1. You have to make sure you do the necessary coding outlined in the Handling iOS Background Activity section of the URL Loading System Programming Guide says:


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

If you are using NSURLSession in iOS, your app is automatically relaunched when a download completes. Your app’s application:handleEventsForBackgroundURLSession:completionHandler: app delegate method is responsible for recreating the appropriate session, storing a completion handler, and calling that handler when the session calls your session delegate’s URLSessionDidFinishEventsForBackgroundURLSession: method.

该指南显示了您可以执行的操作的一些示例。坦率地说,我认为在WWDC 2013视频的后半部分讨论的代码示例什么是新的在基础网络中更加清晰。

That guide shows some examples of what you can do. Frankly, I think the code samples discussed in the latter part of the WWDC 2013 video What’s New in Foundation Networking are even more clear.

AFURLSessionManager 的基本实现将有效如果应用程序仅被暂停,则与后台会话结合使用(假设您已完成上述操作,您将看到在网络任务完成时调用的块)。但正如您所猜测的那样,任何特定于任务的块参数都会传递到 AFURLSessionManager 方法,您可以在其中创建 NSURLSessionTask 如果应用程序终止或崩溃,则上传和下载会丢失。

The basic implementation of AFURLSessionManager will work in conjunction with background sessions if the app is merely suspended (you'll see your blocks called when the network tasks are done, assuming you've done the above). But as you guessed, any task-specific block parameters that are passed to the AFURLSessionManager method where you create the NSURLSessionTask for uploads and downloads are lost "if the app terminated or crashes."

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

For background uploads, this is an annoyance (as your task-level informational progress and completion blocks you specified when creating the task will not get called). But if you employ the session-level renditions (e.g. setTaskDidCompleteBlock and setTaskDidSendBodyDataBlock), that will get called properly (assuming you always set these blocks when you re-instantiate the session manager).

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

As it turns out, this issue of losing the blocks is actually more problematic for background downloads, but the solution there is very similar (do not use task-based block parameters, but rather use session-based blocks, such as setDownloadTaskDidFinishDownloadingBlock).

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

An alternative, you could stick with default (non-background) NSURLSession, but make sure your app requests a little time to finish the upload if the user leaves the app while the task is in progress. For example, before you create your NSURLSessionTask, you can create a UIBackgroundTaskIdentifier:

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

    [[UIApplication sharedApplication] endBackgroundTask:taskId];
    taskId = UIBackgroundTaskInvalid;
}];

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

But make sure that the completion block of the network task correctly informs iOS that it is complete:

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

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

This is not as powerful as a background NSURLSession (e.g., you have a limited amount of time available), but in some cases this can be useful.






更新:

我想我会添加一个实际的例子如何使用AFNetworking进行后台下载。

I thought I'd add a practical example of how to do background downloads using AFNetworking.


  1. 首先定义你的后台经理。

  1. First define your background manager.

//
//  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


  • 确保应用委托保存完成处理程序(根据需要实例化后台会话):

  • Make sure app delegate saves completion handler (instantiating the background session as necessary):

    - (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;
    }
    


  • 然后开始下载:

  • Then start your downloads:

    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

    Note, I don't supply any of those task related blocks, because those aren't reliable with background sessions. (Background downloads proceed even after the app is terminated and these blocks have long disappeared.) One must rely upon the session-level, easily recreated setDownloadTaskDidFinishDownloadingBlock only.

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

    Clearly this is a simple example (only one background session object; just saving files to the docs folder using last component of URL as the filename; etc.), but hopefully it illustrates the pattern.

    这篇关于AFNetworking和后台转移的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

  • 查看全文
    登录 关闭
    扫码关注1秒登录
    发送“验证码”获取 | 15天全站免登陆