Странное поведение NSURLSessionDownloadTask через сотовую связь (не Wi-Fi)

Я включил фоновые режимы с задачами удаленного уведомления, чтобы загрузить небольшой файл (100 КБ) в фоновом режиме, когда приложение получает push-уведомление. Я настроил сеанс загрузки, используя

NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfiguration:sessionIdentifier];
[backgroundConfiguration setAllowsCellularAccess:YES];


self.backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration
                                                       delegate:self
                                                  delegateQueue:[NSOperationQueue mainQueue]];

и активируйте его с помощью

 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[hostComponents URL]];

[request setAllowsCellularAccess:YES];


NSMutableData *bodyMutableData = [NSMutableData data];
[bodyMutableData appendData:[params dataUsingEncoding:NSUTF8StringEncoding]];
[request setHTTPMethod:@"POST"];
[request setHTTPBody:[bodyMutableData copy]];


_downloadTask =  [self.backgroundSession downloadTaskWithRequest:request];

[self.downloadTask resume];

Теперь все работает правильно, только если я подключен через Wi-Fi или через сотовую связь, но с iPhone, подключенным кабелем к xCode, если я отключу iPhone и получу push-уведомление по сотовой связи, код остановится на строке [self.downloadTask resume]; без вызова URL.

Класс, который обрабатывает эту операцию, представляет собой NSURLSessionDataDelegate, NSURLSessionDownloadDelegate, NSURLSessionTaskDelegate и поэтому реализует:

    - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didWriteData:(int64_t)bytesWritten totalBytesWritten:(int64_t)totalBytesWritten totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didResumeAtOffset:(int64_t)fileOffset expectedTotalBytes:(int64_t)expectedTotalBytes

- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask didFinishDownloadingToURL:(NSURL *)location

Я пытаюсь вставить строку отладки с UILocalNotification (представлена ​​«сейчас») после [self.downloadTask resume], но вызывается через 5 минут и говорит, что self.downloadTask.state «приостановлено»

С чем связано такое странное поведение?


person Cers    schedule 09.07.2014    source источник
comment
Я испытываю точно такое же поведение в сети ATT с IOS 7.0. Поскольку этого не происходит при подключении к Xcode, я сохраняю журналы на телефоне, чтобы увидеть, когда перезапущу приложение. Обнаружено, что задачи NSURLSessionDownloadTasks остаются с состоянием = NSURLSessionTaskStateRunning, но действуют так, как будто они приостановлены. Если подключиться к Wi-Fi, когда приложение все еще находится в фоновом режиме, эти остановленные загрузки завершатся успешно. Если я переведу приложение на передний план, оно останется в тупике.   -  person Sani Elfishawy    schedule 03.09.2014
comment
От Sani Elfshishawy ответ ниже при подключении к источнику питания и по Wi-Fi. Мне кажется, что подключение к XCODE изменяет состояние подключения к источнику питания.   -  person Ryan Heitner    schedule 06.11.2014
comment
Я тоже столкнулся с таким же случаем. Я комбинирую iBeacon с фоновой фоновой задачей NSURLSession, чтобы выполнить некоторую проверку, когда пользователь находится рядом с маяками. И я обнаружил, что задача загрузки недоступна, когда пользователь использует сотовую сеть. У вас есть предложения?   -  person Tony Fung Choi Fung    schedule 11.12.2014


Ответы (4)


Документация для ссылки на класс NSURLSessionConfiguration здесь:

https://developer.apple.com/Library/ios/documentation/Foundation/Reference/NSURLSessionConfiguration_class/Reference/Reference.html#//apple_ref/occ/instp/NSURLSessionConfiguration/discretionary

Говорит: для дискреционной собственности:

Обсуждение

Если этот флаг установлен, передачи с большей вероятностью будут происходить при подключении к источнику питания и по Wi-Fi. Это значение по умолчанию ложно.

Это свойство используется только в том случае, если объект конфигурации сеанса был изначально создан путем вызова метода backgroundSessionConfiguration: и только для задач, запущенных, когда приложение находится на переднем плане. Если задача запускается, когда приложение находится в фоновом режиме, эта задача обрабатывается так, как если бы дискреционная имела значение true, независимо от фактического значения этого свойства. Для сеансов, созданных на основе других конфигураций, это свойство равно игнорируется.

Кажется, это означает, что если загрузка запущена в фоновом режиме, ОС всегда имеет право решать, следует ли и когда продолжать загрузку. Кажется, что ОС всегда ждет подключения к Wi-Fi, прежде чем выполнять эти задачи.

Мой опыт подтверждает это предположение. Я обнаружил, что могу отправить несколько уведомлений о загрузке, когда устройство находится в сотовой связи. Они остаются застрявшими. Когда я переключаю устройство на Wi-Fi, они все проходят.

person Sani Elfishawy    schedule 03.09.2014
comment
ВТФ?! Почему Apple делает ЭТО?! Конечно, пользователь часто не находится в WiFi, когда запускаются задачи загрузки. В моем приложении я полагаюсь на загрузку ОЧЕНЬ небольших объемов данных в фоновом режиме И в сотовой сети (например, из-за пробуждения приложения при изменении местоположения). Это как-то возможно? - person blackjacx; 21.11.2014
comment
Это должно быть помечено как ответ, на своем месте. Я видел несколько загадочных проблем, когда загрузка волшебным образом начиналась только при подключении через кабель, и всегда это было связано с этой настройкой. - person Matt; 29.01.2015

У меня такая же проблема, наконец-то я поставил

configuration.discretionary = NO;

все работает отлично, для backgroundConfiguration , discretionary = YES по умолчанию, кажется, задача начинается в связи с WIFI и батареей обоих. надеюсь полезно

person jiangzf    schedule 13.03.2016

Что ты делаешь в

  • (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{}

Вы вызываете завершениеHandler сразу перед завершением загрузки? Я считаю, что это не влияет на работу в режиме Wi-Fi или при подключении к Xcode. Но почему-то в фоновом режиме на сотовой связи загрузка останавливается, пока вы не перейдете к Wi-Fi.

person Sani Elfishawy    schedule 03.09.2014

Единственный реальный способ обойти это — сбросить NSURLSession, когда приложение находится в фоновом режиме, и использовать сокеты CF. Я могу успешно выполнять HTTP-запросы по сотовой связи, пока приложение находится в фоновом режиме, если я использую CFStreamCreatePairWithSocketToHost для открытия CFStream.

#import "Communicator.h"

@implementation Communicator {
    CFReadStreamRef readStream;
    CFWriteStreamRef writeStream;

    NSInputStream *inputStream;
    NSOutputStream *outputStream;
    CompletionBlock _complete;
}

- (void)setupWithCallBack:(CompletionBlock) completionBlock {
    _complete = completionBlock;
    NSURL *url = [NSURL URLWithString:_host];

    //NSLog(@"Setting up connection to %@ : %i", [url absoluteString], _port);

    CFStreamCreatePairWithSocketToHost(kCFAllocatorDefault, (__bridge CFStringRef)[url host], _port, &readStream, &writeStream);

    if(!CFWriteStreamOpen(writeStream)) {
        NSLog(@"Error, writeStream not open");

        return;
    }
    [self open]; 

    //NSLog(@"Status of outputStream: %lu", (unsigned long)[outputStream streamStatus]);

    return;
}

- (void)open {
    //NSLog(@"Opening streams.");

    inputStream = (__bridge NSInputStream *)readStream;
    outputStream = (__bridge NSOutputStream *)writeStream;

    [inputStream setDelegate:self];
    [outputStream setDelegate:self];

    [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [inputStream open];
    [outputStream open];
}

- (void)close {
    //NSLog(@"Closing streams.");

    [inputStream close];
    [outputStream close];

    [inputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
    [outputStream removeFromRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

    [inputStream setDelegate:nil];
    [outputStream setDelegate:nil];

    inputStream = nil;
    outputStream = nil;
}

- (void)stream:(NSStream *)stream handleEvent:(NSStreamEvent)event {
    //NSLog(@"Stream triggered.");

    switch(event) {
        case NSStreamEventHasSpaceAvailable: {
            if(stream == outputStream) {
                if (_complete) {
                    CompletionBlock copyComplete = [_complete copy];
                    _complete = nil;
                    copyComplete();
                }
            }
            break;
        }
        case NSStreamEventHasBytesAvailable: {
            if(stream == inputStream) {
                //NSLog(@"inputStream is ready.");

                uint8_t buf[1024];
                NSInteger len = 0;

                len = [inputStream read:buf maxLength:1024];

                if(len > 0) {
                    NSMutableData* data=[[NSMutableData alloc] initWithLength:0];

                    [data appendBytes: (const void *)buf length:len];

                    NSString *s = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];

                    [self readIn:s];

                }
            } 
            break;
        }
        default: {
            //NSLog(@"Stream is sending an Event: %lu", (unsigned long)event);

            break;
        }
    }
}

- (void)readIn:(NSString *)s {
    //NSLog(@"reading : %@",s);
}

- (void)writeOut:(NSString *)s{
    uint8_t *buf = (uint8_t *)[s UTF8String];

    [outputStream write:buf maxLength:strlen((char *)buf)];

    NSLog(@"Writing out the following:");
    NSLog(@"%@", s);
}

@end
person Zayin Krige    schedule 15.07.2016