Странное поведение NSURLSessionDownloadTask по сотовому (а не Wi-Fi)

Я включил фоновые режимы с задачами удаленного уведомления, чтобы загрузить небольшой файл (100kb) в фоновом режиме, когда приложение получает 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 или через Cellular, но с iPhone, подключенным к кабелю к xCode, если я отключу iPhone и получаю push-уведомление по сотовой сети, остановите код в [self.downloadTask resume]; line без вызова 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 "приостановлено",

Что из-за этого странного поведения?

Ответ 1

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

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

Говорит: для дискреционного имущества:

обсуждение

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

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

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

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

Ответ 2

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

configuration.discretionary = NO;

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

Ответ 3

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

  • (void): (UIApplication *) приложение didReceiveRemoteNotification: (NSDictionary *) userInfo fetchCompletionHandler: (void (^) (UIBackgroundFetchResult)) completeHandler {}

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

Ответ 4

Единственный реальный способ - сбросить 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