Я пытаюсь реализовать выборку фона в приложении для iOS. В официальной документации указано:
"Вам не нужно выполнять всю фоновую сетевую активность с фоновыми сессиями ... .... Приложения, которые объявляют соответствующие фоновые режимы, могут использовать сеансы URL-адресов по умолчанию и задачи с данными, как если бы они были в на переднем плане. "
Более того, в этом WWDC 2014 видео в 51:50 между лучшими практиками указано:
"Одна распространенная ошибка, которую, как мы думаем, некоторые люди совершили в прошлом, предполагает, что при работе в фоновом режиме для обработки таких вещей, как обновление фоновой выборки ... ... они были обязаны использовать фоновый сеанс Теперь, используя фоновую загрузку или загрузку ... ... работает очень хорошо, но особенно для больших загрузок. Когда вы запускаете обновление для выборки в фоновом режиме ... у вас есть от 30 до 60 секунд для запуска в фоновом режиме. «[до того, как приложение будет приостановлено]» Если у вас есть такая же небольшая сетевая задача, которая может быть завершена в течение этого времени, то вполне нормально сделать это в рамках NSURLSession в процессе или по умолчанию ... ... "
Я точно нахожусь в ситуации небольшой сетевой задачи: я хочу связаться с 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: executeFetchWithCompletionHandler: метод, пока пользователь не перезапустит приложение
Я использую несколько тестовых случаев с несколькими комбинациями устройств, заблокированных / разблокированных, заряжающих / не заряжающих, все в сети Wi-Fi, потому что у них нет симов
Когда я запускаю вышеупомянутый вид кода, используя Xcode -> Debug -> Simulation background fetch или используя схему сборки с выбранной опцией «запуск из-за события фоновой выборки», все идет хорошо: сервер получает мой пост-запрос, приложение правильно вернуло ответ сервера, и блок dispatch_group_notify выполняется. Тестируя код на реальном устройстве, я получаю взамен никакого результата, даже при обращении к серверу