- mongodb - 在 MongoDB mapreduce 中,如何展平值对象?
- javascript - 对象传播与 Object.assign
- html - 输入类型 ="submit"Vs 按钮标签它们可以互换吗?
- sql - 使用 MongoDB 而不是 MS SQL Server 的优缺点
我有点困惑如何利用新的 iOS 7 NSURLSession
后台传输功能和 AFNetworking (版本 2 和 3)。
我看到了 WWDC 705 - What's New in Foundation Networking
session ,他们演示了在应用程序终止甚至崩溃后继续进行后台下载。
这是使用新的 API application:handleEventsForBackgroundURLSession:completionHandler:
完成的,并且 session 的委托(delegate)最终将获得回调并可以完成其任务。
所以我想知道如何将它与 AFNetworking(如果可能)一起使用以继续在后台下载。
问题是,AFNetworking 方便地使用基于 block 的 API 来执行所有请求,但如果应用程序终止或崩溃,这些 block 也会消失。那么如何才能完成任务呢?
或者我在这里遗漏了一些东西......
让我解释一下我的意思:
例如,我的应用程序是一个照片消息应用程序,假设我有一个 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];
如您所见,我使用完成 block 根据收到的响应更新该 PhotoMessage
对象。
如何通过后台传输来实现这一点?不会调用此完成 block ,因此我无法更新 newPhotoMsg
。
最佳答案
一些想法:
您必须确保完成Handling iOS Background Activity 中列出的必要编码。 URL 加载系统编程指南 部分说:
If you are using
NSURLSession
in iOS, your app is automatically relaunched when a download completes. Your app’sapplication: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’sURLSessionDidFinishEventsForBackgroundURLSession:
method.
该指南显示了您可以做什么的一些示例。坦率地说,我认为 WWDC 2013 视频 What’s New in Foundation Networking 后半部分讨论的代码示例更清晰。
如果应用只是挂起,AFURLSessionManager
的基本实现将与后台 session 一起工作(假设您已经在网络任务完成时看到调用 block )完成上述)。但正如您所猜测的,如果应用程序终止或崩溃,任何传递给您创建 NSURLSessionTask
上传和下载的方法的特定于任务的 block 参数都会丢失。”
对于后台上传,这是一个烦恼(因为您在创建任务时指定的任务级信息进度和完成 block 不会被调用)。但是,如果您使用 session 级别的再现(例如 setTaskDidCompleteBlock
和 setTaskDidSendBodyDataBlock
),它将被正确调用(假设您在重新实例化 session 管理器时始终设置这些 block )。
事实证明,这个丢 block 的问题其实对于后台下载来说问题更大,但是那里的解决方案非常相似(不要使用基于任务的 block 参数,而是使用基于 session 的 block ,例如setDownloadTaskDidFinishDownloadingBlock
)。
另一种选择,您可以坚持使用默认(非背景)NSURLSession
,但如果用户在执行任务时离开应用程序,请确保您的应用程序请求一点时间来完成上传正在处理。例如,在您创建 NSURLSessionTask
之前,您可以创建一个 UIBackgroundTaskIdentifier
:
UIBackgroundTaskIdentifier __block taskId = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^(void) {
// handle timeout gracefully if you can
[[UIApplication sharedApplication] endBackgroundTask:taskId];
taskId = UIBackgroundTaskInvalid;
}];
但是要确保网络任务的完成 block 正确地通知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
确保应用委托(delegate)保存完成处理程序(根据需要实例化后台 session ):
- (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];
}
注意,我不提供任何与任务相关的 block ,因为这些 block 在后台 session 中不可靠。 (即使在应用程序终止并且这些 block 早已消失后,后台下载也会继续进行。)必须依赖于 session 级,仅可轻松重新创建 setDownloadTaskDidFinishDownloadingBlock
。
显然这是一个简单的示例(只有一个后台 session 对象;只是使用 URL 的最后一个组件作为文件名将文件保存到 docs 文件夹等),但希望它说明了这种模式。
关于ios - AFNetworking 和后台传输,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21350125/
从 0 开始搭建一套后台管理系统,成本巨大,所以都会选择一套成熟的组件库,基于此,再堆叠业务逻辑。我们公司的组件库基于 Ant Design。Ant Design 包含一套完整的后台解决方案,不仅
在我的 IOS 应用程序中,我有一个标记为 retain 的 NSDate* 属性 当我的应用程序再次激活时,属性值已被释放。 我是否误解了属性和内存管理的工作原理,我该如何防范? 最佳答案 很明显,
我有一个使用 BackgroundWorker 组件的示例 WinForms 应用程序。它工作正常,但是当我点击 Cancel 按钮取消后台线程时,它并没有取消线程。当我点击 Cancel 按钮调用
我目前正在开发一个应用程序,该应用程序在启动时会对服务器执行 ping 操作,该服务器会为每个连接的设备返回一个唯一标识符。设备每 5 秒从服务器检索另一页以获取一组不同的数据。这个唯一的 ID 可以
我正在开发一个应用程序,当它通过主页按钮在后台按下时,计时器应该启动,当应用程序返回前台并且计时器已经过了一定时间时,应该是执行。 我的问题是 当我的应用程序转到背景/前景? 是否有特殊的方法或其他技
我有 map View ,其中几乎没有 MKPointAnnotation。 一切正常,但是, View 的 MKPoiintAnnotation 的“背景”是“不可见的”,因此不是很“可见”。 我想
我在 iOS 中开发广告数据应用程序。我的应用程序广告数据在前台很好。但我想在 ios 后台宣传信标数据。我设置了背景外设设置。和广告数据 advertisingData = [CBAdvertise
如果我有一组操作,我想根据特定条件在后台工作程序中运行,例如,我有 10 个条件 if(a) BackgroundWorker doA = new backgroundworker() if(
我想独立运行一个函数。从我调用的函数中,我想在不等待其他函数结束的情况下返回。 我试过用 threadind,但这会等待,结束。 thread = threading.Thread(target=my
我想在用户在线时立即执行一些任务,即使他在后台也是如此。我正在使用 Reachability 类来检查互联网。但是当我在后台时,这个类没有通知我。我知道有人早些时候问过这个问题,但没有找到任何解决方案
我在后台播放文本转语音时出现间歇性(哎呀!)问题,由 Apple Watch 触发。我已经正确设置了后台模式、AVSession 类别和 WatchKitExtensionRequest 处理程序。
我有一个相当复杂的程序,所以我不会在这里转储整个程序。这是一个简化版本: class Report { private BackgroundWorker worker; public
我有一个任务在 backgroundworker 中运行。单击开始按钮,用户将启动该过程,并获得一个取消按钮来取消处理。 当用户点击取消时,我想显示一个消息框“进程尚未完成,你想继续吗”。 这里我希望
我有一个按以下方式编码的脚本。我想将它作为后台/守护进程运行,但是一旦我启动脚本,如果我关闭它从程序运行的终端窗口终止。我需要做什么来保持程序运行 loop do pid = fork do
我正在制作一个使用 ActivityRecognition API 在后台跟踪用户 Activity 的应用,如果用户在指定时间段(例如 1 小时)内停留在同一个地方,系统就会推送通知告诉用户去散步.
当尝试使用 URLSession 的 dataTaskPublisher 方法发送后台请求时: URLSession(configuration: URLSessionConfiguration.ba
当我编译这段代码时,我得到了他的错误,对象引用设置为null,错误位置在Dowork中,argumenttest.valueone = 8; public partial class Form1 :
有什么方法可以使用最小化或不活动的应用程序吗?我可以打开我的应用程序,然后打开并使用另一个应用程序,然后按一个按钮来激活我的程序吗? 例如,打开我的应用程序,打开 Safari,按下按钮(F1 或任何
我的具体要求是一个在后台运行的应用程序,被通知显示器即将进入休眠状态或者设备已经或即将达到空闲超时 - 然后唤醒并执行一些(简短的)一段代码。 我在这里找到了有关应用程序被置于后台或暂停的通知的引用:
我有一个 LSUIElement 设置为 1 的应用程序。它有一个内置编辑器,因此我希望该应用程序在编辑器打开时出现在 Cmd+Tab 循环中。 -(void)stepIntoForegrou
我是一名优秀的程序员,十分优秀!