Mar*_*rio 53 objective-c ios afnetworking afnetworking-2
我有点混淆如何利用新的iOS 7 NSURLSession后台传输功能和AFNetworking(版本2和3).
我看到了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];
Run Code Online (Sandbox Code Playgroud)
如您所见,我使用完成块PhotoMessage根据我得到的响应更新该对象.
如何通过后台传输实现这一目标?这个完成块不会被调用,因此我无法更新newPhotoMsg.
Rob*_*Rob 77
几点想法:
您必须确保执行URL加载系统编程指南的处理iOS后台活动部分中概述的必要编码:
如果您
NSURLSession在iOS 中使用,则下载完成后您的应用会自动重新启动.您的应用程序的application:handleEventsForBackgroundURLSession:completionHandler:app delegate方法负责重新创建适当的会话,存储完成处理程序,并在会话调用会话委托的URLSessionDidFinishEventsForBackgroundURLSession:方法时调用该处理程序.
该指南展示了您可以做的一些示例.坦率地说,我认为在WWDC 2013视频后期讨论的代码示例中,基础网络的新功能更加清晰.
AFURLSessionManager如果应用程序仅被暂停,则基本实现将与后台会话一起使用(假设您已完成上述操作,您将看到在网络任务完成时调用的块).但是,正如您所猜测的那样,任何特定于任务的块参数都会在"应用程序终止或崩溃"时丢失,而这些参数将传递给AFURLSessionManager您创建NSURLSessionTask上传和下载的方法.
对于后台上传,这是一个烦恼(因为您在创建任务时指定的任务级信息进度和完成块不会被调用).但是如果你使用会话级别的再现(例如setTaskDidCompleteBlock和setTaskDidSendBodyDataBlock),那么它将被正确调用(假设你在重新实例化会话管理器时总是设置这些块).
事实证明,丢失块的这个问题实际上对于后台下载来说更成问题,但是那里的解决方案非常相似(不要使用基于任务的块参数,而是使用基于会话的块,例如setDownloadTaskDidFinishDownloadingBlock).
另一种方法是,您可以坚持使用默认(非背景)NSURLSession,但如果用户在任务进行过程中离开应用程序,请确保您的应用程序请求一点时间完成上传.例如,在创建之前NSURLSessionTask,您可以创建一个UIBackgroundTaskIdentifier:
UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// handle timeout gracefully if you can
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}];
Run Code Online (Sandbox Code Playgroud)
但请确保网络任务的完成块正确通知iOS它已完成:
if (taskId != UIBackgroundTaskInvalid) {
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}
Run Code Online (Sandbox Code Playgroud)
这不像背景那么强大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
Run Code Online (Sandbox Code Playgroud)
和
//
// 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
Run Code Online (Sandbox Code Playgroud)确保app delegate保存完成处理程序(根据需要实例化后台会话):
- (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;
}
Run Code Online (Sandbox Code Playgroud)然后开始下载:
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];
}
Run Code Online (Sandbox Code Playgroud)
请注意,我不提供任何与任务相关的块,因为这些块在后台会话中不可靠.(即使在应用程序终止并且这些块已经很久消失之后,后台下载仍会继续.)必须依赖会话级别,setDownloadTaskDidFinishDownloadingBlock只能轻松重新创建.
显然,这是一个简单的例子(只有一个后台会话对象;只使用URL的最后一个组件作为文件名将文件保存到docs文件夹;等等),但希望它能说明模式.
回调是否是块应该没有任何区别。当您实例化 an 时AFURLSessionManager,请确保使用 实例化它NSURLSessionConfiguration backgroundSessionConfiguration:。另外,请确保使用回调块调用管理器setDidFinishEventsForBackgroundURLSessionBlock- 这是您应该编写通常在 NSURLSessionDelegate 的方法中定义的代码的地方:
URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session。此代码应调用应用程序委托的后台下载完成处理程序。
关于后台下载任务的一点建议 - 即使在前台运行时,它们的超时也会被忽略,这意味着您可能会“卡在”没有响应的下载上。这在任何地方都没有记录,并且让我发疯了一段时间。第一个嫌疑人是 AFNetworking,但即使直接调用 NSURLSession 后,行为仍然相同。
祝你好运!