我正在尝试在iOS应用中实现后台抓取。官方documentation中指出:
”“您不必使用后台会话进行所有后台网络活动……。声明适当后台模式的应用程序可以使用默认的URL会话和数据任务,就像它们在前景。”
此外,在WWDC 2014 video中,最佳做法之间的时间为51:50:
”“我们认为某些人过去犯的一个常见错误是,假设在后台运行时处理诸如后台获取更新之类的事情…………要求他们使用后台会话。现在,使用后台上传或下载... ...确实很好,但是特别适用于大型下载。当您在运行后台获取更新时...在后台运行大约需要30到60秒“ [在应用程序暂停之前]“如果您有相同的小型网络任务可以在这段时间内完成,则完全可以在进程内或默认的NSURLSession中完成此任务……” >
我正处于一项小型网络任务的情况下:我想与服务器上的rest api通信,以发布相同的数据并获取数据响应。我正在创建一个默认会话并执行一个或两个任务,并使用调度组将其同步,如本例所示:
- (void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"AppDelegate application:performFetchWithCompletionHandler:");
BOOL thereIsALoggedInUser = [self thereIsALoggedInUser];
if (!(thereIsALoggedInUser && [application isProtectedDataAvailable])){
completionHandler(UIBackgroundFetchResultNoData);
NSLog(@"completionHandler(UIBackgroundFetchResultNoData): thereIsALoggedInUser: %@, isProtectedDataAvailable: %@",
thereIsALoggedInUser ? @"YES" : @"NO",
[application isProtectedDataAvailable] ? @"YES" : @"NO");
return;
}
NSArray<NSString*> *objectsIWantToSend = [self getObjectsIWantToSend];
if (!objectsIWantToSend) {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
completionHandler(UIBackgroundFetchResultNoData);
NSLog(@"No post data");
} else {
[self sendToServer:objectsIWantToSend usingCompletionHandler:completionHandler];
}
}
-(void) sendToServer:(NSArray<NSString*> *)objectsArray usingCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
NSLog(@"AppDelegate - sendToServer:usingCompletionHandler:");
__block BOOL thereWasAnError = NO;
__block BOOL thereWasAnUnsuccessfullHttpStatusCode = NO;
dispatch_group_t sendTransactionsGroup = dispatch_group_create();
NSURLSession *postSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
for (NSString *objectToPost in objectsArray) {
dispatch_group_enter(sendTransactionsGroup);
NSMutableURLRequest *request = [NSMutableURLRequest new];
[request setURL:[NSURL URLWithString:@"restApiUrl"]];
[request setHTTPMethod:@"POST"];
request = [Util setStandardContenttypeAuthorizationAndAcceptlanguageOnRequest:request];
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:@{ @"appData": objectToPost } options:kNilOptions error:nil]];
NSURLSessionDataTask *dataTask = [postSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSInteger statusCode = -1;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
statusCode = [(NSHTTPURLResponse *)response statusCode];
}
if (error) {
NSLog(@"Error");
thereWasAnError = YES;
} else if (statusCode != 201) {
NSLog(@"Error: http status code: %d", httpResponse.statusCode);
thereWasAnUnsuccessfullHttpStatusCode = YES;
} else {
[self parseAndStoreData:data];
}
dispatch_group_leave(sendTransactionsGroup);
}];
[dataTask resume];
}
dispatch_group_notify(sendTransactionsGroup, dispatch_get_main_queue(),^{
if (thereWasAnError) {
completionHandler(UIBackgroundFetchResultFailed);
} else if (thereWasAnUnsuccessfullHttpStatusCode) {
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
completionHandler(UIBackgroundFetchResultNoData);
} else {
completionHandler(UIBackgroundFetchResultNewData);
}
});
}
在我的应用程序中:didFinishLaunchingWithOptions:方法我正在使用以下方法将BackgroundFetchInterval设置为MinimumInterval:
[[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
我不会强制退出设备上的应用程序,因为我知道在那种情况下,系统将不会调用AppDelegate application:performFetchWithCompletionHandler:方法,直到用户重新启动应用程序为止
我正在使用几个测试用例,这些设备在wifi网络下都具有锁定/解锁,充电/不充电的几种组合,因为它们没有模拟物
当我使用Xcode->调试->模拟后台获取或使用选择“由于后台获取事件而启动”选项的构建方案来运行上述代码时,一切顺利:服务器收到了我的发布请求,该应用正确返回了服务器响应,并且执行了dispatch_group_notify块。通过实际的设备测试代码我没有结果,甚至没有调用服务器
答案 0 :(得分:0)
由于未在此处的代码中修改NSURLSession,因此应使用
NSURLSession *defaultSession = [NSURLSession sharedSession];
代替
NSURLSession *postSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
在此处查看已接受的答案:
NSURLSessionDataTask dataTaskWithURL completion handler not getting called
希望这会有所帮助。
答案 1 :(得分:0)
后台获取未启动或在后台获取过程中出现了问题。我们需要进行一些调试,以找出问题所在。
因此,有几个观察结果:
我是否正确假设您是通过应用程序委托的performFetchWithCompletionHandler
方法调用此函数,并传递了完成处理程序参数?也许您可以分享此方法的实现。
能够在应用程序在物理设备上运行但未附加到Xcode调试器的情况下对其进行监视(这是因为从调试器运行它会更改应用程序生命周期),这非常有用。
为此,我建议使用Unified Logging而不是NSLog
,这样即使您未连接,也可以从macOS控制台轻松监视设备os_log
的语句到Xcode(与从Xcode运行不同,统一日志记录不会影响应用程序生命周期)。然后,在macOS控制台中观看,您可以确认后台提取的启动,并查看后台提取是否成功以及在何处失败。参见WWDC视频Unified Logging and Activity Tracing。
因此,导入os.log
:
@import os.log;
为您的log
定义一个变量:
os_log_t log;
设置:
NSString *subsystem = [[NSBundle mainBundle] bundleIdentifier];
log = os_log_create([subsystem cStringUsingEncoding:NSUTF8StringEncoding], "background.fetch");
然后,您现在可以记录消息,例如
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
os_log(log, "%{public}s", __FUNCTION__);
return YES;
}
然后使用它进入控制台,包括信息和调试消息,并观看设备日志语句出现在macOS控制台上,即使没有通过Xcode调试器运行应用程序,例如:
有关更多信息,请参见that video。
无论如何,有了此日志记录,您就可以确定问题是未启动后台获取还是例程中的某些问题。
此外,我认为您不是要在设备上强制退出该应用程序吗?即您应该只在设备上运行该应用,然后返回主屏幕或转到另一个应用。如果您强制退出某个应用程序,则可以完全停止某些后台操作。
您等待启动后台提取多长时间了?设备是否处于允许及时调用后台获取的状态?
最重要的是,您无法控制何时进行。例如,请确保您已连接电源并处于wifi状态,以使其具有快速启动的最佳机会。 (操作系统在决定何时获取数据时会考虑这些因素。)我上次检查此模式时,在这种最佳情况下发生了大约10分钟的第一次背景获取。
顺便说一句,与手头的问题无关,您确实应该检查以确保response
是NSHTTPURLResponse
。如果不是这样,您是否想在尝试检索statusCode
时使应用崩溃?因此:
os_log(log, "%{public}s", __FUNCTION__);
__block BOOL thereWasAnError = NO;
__block BOOL thereWasAnUnsuccessfullHttpStatusCode = NO;
dispatch_group_t sendTransactionsGroup = dispatch_group_create();
NSURLSession *postSession = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
for (NSString *objectToPost in objectArray) {
dispatch_group_enter(sendTransactionsGroup);
NSMutableURLRequest *request = [NSMutableURLRequest new];
[request setURL:[NSURL URLWithString:@"restApiUrl"]];
[request setHTTPMethod:@"POST"];
request = [Util setStandardContenttypeAuthorizationAndAcceptlanguageOnRequest:request];
[request setHTTPBody:[NSJSONSerialization dataWithJSONObject:@{ @"appData": objectToPost } options:kNilOptions error:nil]];
NSURLSessionDataTask *dataTask = [postSession dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error){
NSInteger statusCode = -1;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
statusCode = [(NSHTTPURLResponse *)response statusCode];
}
if (error) {
os_log(log, "%{public}s: Error: %{public}@", __FUNCTION__, error);
thereWasAnError = YES;
} else if (statusCode < 200 || statusCode > 299) {
os_log(log, "%{public}s: Status code: %ld", __FUNCTION__, statusCode);
thereWasAnUnsuccessfullHttpStatusCode = YES;
} else {
[self parseAndStoreData:data];
}
dispatch_group_leave(sendTransactionsGroup);
}];
[dataTask resume];
}
dispatch_group_notify(sendTransactionsGroup, dispatch_get_main_queue(),^{
if (thereWasAnError) {
completionHandler(UIBackgroundFetchResultFailed);
} else if (thereWasAnUnsuccessfullHttpStatusCode) {
// [[UIApplication sharedApplication] setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalNever];
completionHandler(UIBackgroundFetchResultNoData);
return;
} else {
completionHandler(UIBackgroundFetchResultNewData);
}
});
我个人也不会因为失败而更改获取间隔,尽管这取决于您。在确定下一次何时启动对应用程序的后台提取时,操作系统会考虑到这一点。